diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec8b448e9..2f015883d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,10 +26,10 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-latest, ubuntu-latest] # TODO: Add macos-latest later + os: [windows-latest, ubuntu-22.04] # TODO: Add macos-latest later compiler: [gcc, msvc] # TODO: add clang later include: - - os: ubuntu-latest + - os: ubuntu-22.04 compiler: gcc version: 13 generator: Ninja @@ -38,7 +38,7 @@ jobs: version: 14 generator: "Visual Studio 17 2022" exclude: - - os: ubuntu-latest + - os: ubuntu-22.04 compiler: msvc - os: windows-latest compiler: gcc @@ -63,13 +63,13 @@ jobs: detached: true # Run in the background and wait for connection - name: Add Ubuntu toolchain repository - if: ${{ matrix.os == 'ubuntu-latest' && matrix.compiler == 'gcc'}} + if: ${{ matrix.os == 'ubuntu-22.04' && matrix.compiler == 'gcc'}} run: | sudo add-apt-repository --yes --update ppa:ubuntu-toolchain-r/test -y sudo apt-get update - name: Pre-install GCC with cache - if: ${{ matrix.os == 'ubuntu-latest' && matrix.compiler == 'gcc' }} + if: ${{ matrix.os == 'ubuntu-22.04' && matrix.compiler == 'gcc' }} uses: awalsh128/cache-apt-pkgs-action@latest with: packages: cpp-13 g++-13 gcc-13 gcc-13-base libgcc-13-dev libhwasan0 \ @@ -103,7 +103,7 @@ jobs: toolset: ${{ matrix.version }} - name: Install required system dependencies for Ubuntu - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} uses: awalsh128/cache-apt-pkgs-action@latest with: packages: libxrandr-dev libx11-dev @@ -113,8 +113,8 @@ jobs: libxcb-icccm4-dev libxcb-keysyms1-dev libxcb-dri2-0-dev libxcb-dri3-dev libxcb-glx0-dev libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev - libegl1-mesa-dev mono-complete - version: 1.0 + libegl1-mesa-dev mono-complete libxtst-dev + version: 1.1 execute_install_scripts: true - name: Install .NET SDK 9.0 @@ -127,7 +127,7 @@ jobs: git submodule update --init --recursive - name: Install sonar-scanner - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} uses: sonarsource/sonarcloud-github-c-cpp@v3 - name: Install latest CMake and Ninja @@ -201,14 +201,14 @@ jobs: working-directory: 'build' - name: Collect coverage into XML report for SonarCloud - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} run: | pip install gcovr gcovr build --verbose --sonarqube -o coverage.xml --gcov-executable gcov-${{ matrix.version }} cat coverage.xml - name: Run sonar-scanner - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }} @@ -250,7 +250,7 @@ jobs: cpack -G NSIS --verbose - name: Generate DEB installer - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} shell: bash run: | cd build @@ -338,7 +338,7 @@ jobs: test-deb-installer: name: Test DEB installer - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: DOTNET_INSTALL_DIR: "./.dotnet" needs: build @@ -347,7 +347,7 @@ jobs: id: download uses: actions/download-artifact@v4 with: - pattern: 'nexo-engine-installer-gcc13-ubuntu-latest' + pattern: 'nexo-engine-installer-gcc13-ubuntu-22.04' - name: Install .NET SDK 9.0 uses: actions/setup-dotnet@v4 @@ -360,7 +360,7 @@ jobs: sudo apt-get update sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update - sudo apt-get -f install ${{steps.download.outputs.download-path}}/nexo-engine-installer-gcc13-ubuntu-latest/*.deb + sudo apt-get -f install ${{steps.download.outputs.download-path}}/nexo-engine-installer-gcc13-ubuntu-22.04/*.deb - name: Write NEXO run script uses: "DamianReeves/write-file-action@master" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2b3729810..d8eca0d8e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -66,8 +66,8 @@ jobs: libxcb-icccm4-dev libxcb-keysyms1-dev libxcb-dri2-0-dev libxcb-dri3-dev libxcb-glx0-dev libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev - libegl1-mesa-dev mono-complete - version: 1.0 + libegl1-mesa-dev mono-complete libxtst-dev + version: 1.1 execute_install_scripts: true - name: Install .NET SDK 9.0 diff --git a/.gitignore b/.gitignore index f8b9d9755..85285cd06 100644 --- a/.gitignore +++ b/.gitignore @@ -135,6 +135,7 @@ fabric.properties .cmake CMakeCache.txt cmake_install.cmake +CMakeUserPresets.json B-CPP-500_rtype.pdf diff --git a/CMakeLists.txt b/CMakeLists.txt index ad9c58771..56b06c5be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,13 +17,21 @@ set(NEXO_GRAPHICS_API "OpenGL" CACHE STRING "Graphics API to use") if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") set(NEXO_COMPILER_FLAGS_ALL --std=c++${CMAKE_CXX_STANDARD}) set(NEXO_COMPILER_FLAGS_DEBUG -g -Wmissing-field-initializers -Wall -Wextra -Wpedantic) - set(NEXO_COMPILER_FLAGS_RELEASE -O3) + set(NEXO_COMPILER_FLAGS_RELEASE -O3 -DNDEBUG) set(NEXO_COVERAGE_FLAGS -O0 --coverage) + + set(NEXO_LINKER_FLAGS_ALL "") + set(NEXO_LINKER_FLAGS_DEBUG "") + set(NEXO_LINKER_FLAGS_RELEASE "-flto") elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set(NEXO_COMPILER_FLAGS_ALL /nologo /W4 /std:c++${CMAKE_CXX_STANDARD} /Zc:preprocessor /utf-8) set(NEXO_COMPILER_FLAGS_DEBUG /Zi /Od /Zc:preprocessor /MDd /D_DEBUG /D_ITERATOR_DEBUG_LEVEL=2 /D_SECURE_SCL=1) - set(NEXO_COMPILER_FLAGS_RELEASE /O2 /Zc:preprocessor) + set(NEXO_COMPILER_FLAGS_RELEASE /O2 /Zc:preprocessor /DNDEBUG /MD) set(NEXO_COVERAGE_FLAGS "") # MSVC doesn't support coverage in the same way + + set(NEXO_LINKER_FLAGS_ALL "") + set(NEXO_LINKER_FLAGS_DEBUG "") + set(NEXO_LINKER_FLAGS_RELEASE "/LTCG") else() message(WARNING "Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}, using default flags") endif() @@ -34,6 +42,12 @@ add_compile_options( "$<$:${NEXO_COMPILER_FLAGS_RELEASE}>" ) +add_link_options( + "${NEXO_LINKER_FLAGS_ALL}" + "$<$:${NEXO_LINKER_FLAGS_DEBUG}>" + "$<$:${NEXO_LINKER_FLAGS_RELEASE}>" +) + # Prevent Visual Studio (or other build tools) from creating per config sub-directories (e.g. Debug, Release) # Useful to look for resource files relative to the executable path set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_BINARY_DIR}>) diff --git a/CMakePresets.json b/CMakePresets.json index 81a154bbb..e6f7f1f49 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -16,6 +16,10 @@ "type": "FILEPATH", "value": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake" }, + "CMAKE_BUILD_TYPE": { + "type": "STRING", + "value": "Debug" + }, "CMAKE_EXPORT_COMPILE_COMMANDS": { "type": "BOOL", "value": true diff --git a/common/math/Bounds.cpp b/common/math/Bounds.cpp new file mode 100644 index 000000000..ac017be93 --- /dev/null +++ b/common/math/Bounds.cpp @@ -0,0 +1,74 @@ +//// Bounds.cpp /////////////////////////////////////////////////// +// +// ⢀⢀⢀⣤⣤⣤⡀⢀⢀⢀⢀⢀⢀⢠⣤⡄⢀⢀⢀⢀⣠⣤⣤⣤⣤⣤⣤⣤⣤⣤⡀⢀⢀⢀⢠⣤⣄⢀⢀⢀⢀⢀⢀⢀⣤⣤⢀⢀⢀⢀⢀⢀⢀⢀⣀⣄⢀⢀⢠⣄⣀⢀⢀⢀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⣿⣷⡀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡟⡛⡛⡛⡛⡛⡛⡛⢁⢀⢀⢀⢀⢻⣿⣦⢀⢀⢀⢀⢠⣾⡿⢃⢀⢀⢀⢀⢀⣠⣾⣿⢿⡟⢀⢀⡙⢿⢿⣿⣦⡀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⡛⣿⣷⡀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡙⣿⡷⢀⢀⣰⣿⡟⢁⢀⢀⢀⢀⢀⣾⣿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⣿⡆⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⡈⢿⣷⡄⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣇⣀⣀⣀⣀⣀⣀⣀⢀⢀⢀⢀⢀⢀⢀⡈⢀⢀⣼⣿⢏⢀⢀⢀⢀⢀⢀⣼⣿⡏⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡘⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⡈⢿⣿⡄⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣿⢿⢿⢿⢿⢿⢿⢿⢇⢀⢀⢀⢀⢀⢀⢀⢠⣾⣿⣧⡀⢀⢀⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⡈⢿⣿⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣰⣿⡟⡛⣿⣷⡄⢀⢀⢀⢀⢀⢿⣿⣇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⡈⢿⢀⢀⢸⣿⡇⢀⢀⢀⢀⡛⡟⢁⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⡟⢀⢀⡈⢿⣿⣄⢀⢀⢀⢀⡘⣿⣿⣄⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⢏⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⢀⢀⢀⣠⣾⡿⢃⢀⢀⢀⢀⢀⢻⣿⣧⡀⢀⢀⢀⡈⢻⣿⣷⣦⣄⢀⢀⣠⣤⣶⣿⡿⢋⢀⢀⢀⢀ +// ⢀⢀⢀⢿⢿⢀⢀⢀⢀⢀⢀⢀⢀⢸⢿⢃⢀⢀⢀⢀⢻⢿⢿⢿⢿⢿⢿⢿⢿⢿⢃⢀⢀⢀⢿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⡗⢀⢀⢀⢀⢀⡈⡉⡛⡛⢀⢀⢹⡛⢋⢁⢀⢀⢀⢀⢀⢀ +// +// Author: Mehdy MORVAN +// Date: 05/10/2025 +// Description: Source file for the bounds utils +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Bounds.hpp" + +namespace nexo::math { + bool AABB::empty() const + { + return min.x > max.x || min.y > max.y || min.z > max.z; + } + + AABB aabbUnion(const AABB& a, const AABB& b) + { + if (a.empty()) + return b; + if (b.empty()) + return a; + + AABB o; + o.min = glm::min(a.min, b.min); + o.max = glm::max(a.max, b.max); + return o; + } + + BSphere sphereFromAABB(const AABB& b) + { + BSphere s{}; + if (b.empty()) + return s; + s.c = 0.5f * (b.min + b.max); + const glm::vec3 e = 0.5f * (b.max - b.min); + s.r = glm::length(e); + return s; + } + + AABB aabbTransform(const AABB& local, const glm::mat4& M) + { + if (local.empty()) + return {}; + const glm::vec3 lc = 0.5f * (local.min + local.max); + const glm::vec3 le = 0.5f * (local.max - local.min); + + const glm::vec3 t = { M[3][0], M[3][1], M[3][2] }; + // upper-left 3x3 + const auto L = glm::mat3(M); + + // world center + const glm::vec3 wc = L * lc + t; + const auto A = glm::mat3( glm::abs(L[0][0]), glm::abs(L[0][1]), glm::abs(L[0][2]), + glm::abs(L[1][0]), glm::abs(L[1][1]), glm::abs(L[1][2]), + glm::abs(L[2][0]), glm::abs(L[2][1]), glm::abs(L[2][2]) ); + const glm::vec3 we = A * le; + + AABB out; + out.min = wc - we; + out.max = wc + we; + return out; + } +} diff --git a/common/math/Bounds.hpp b/common/math/Bounds.hpp new file mode 100644 index 000000000..a5478d433 --- /dev/null +++ b/common/math/Bounds.hpp @@ -0,0 +1,102 @@ +//// Bounds.hpp /////////////////////////////////////////////////////////////// +// +// ⢀⢀⢀⣤⣤⣤⡀⢀⢀⢀⢀⢀⢀⢠⣤⡄⢀⢀⢀⢀⣠⣤⣤⣤⣤⣤⣤⣤⣤⣤⡀⢀⢀⢀⢠⣤⣄⢀⢀⢀⢀⢀⢀⢀⣤⣤⢀⢀⢀⢀⢀⢀⢀⢀⣀⣄⢀⢀⢠⣄⣀⢀⢀⢀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⣿⣷⡀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡟⡛⡛⡛⡛⡛⡛⡛⢁⢀⢀⢀⢀⢻⣿⣦⢀⢀⢀⢀⢠⣾⡿⢃⢀⢀⢀⢀⢀⣠⣾⣿⢿⡟⢀⢀⡙⢿⢿⣿⣦⡀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⡛⣿⣷⡀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡙⣿⡷⢀⢀⣰⣿⡟⢁⢀⢀⢀⢀⢀⣾⣿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⣿⡆⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⡈⢿⣷⡄⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣇⣀⣀⣀⣀⣀⣀⣀⢀⢀⢀⢀⢀⢀⢀⡈⢀⢀⣼⣿⢏⢀⢀⢀⢀⢀⢀⣼⣿⡏⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡘⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⡈⢿⣿⡄⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣿⢿⢿⢿⢿⢿⢿⢿⢇⢀⢀⢀⢀⢀⢀⢀⢠⣾⣿⣧⡀⢀⢀⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⡈⢿⣿⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣰⣿⡟⡛⣿⣷⡄⢀⢀⢀⢀⢀⢿⣿⣇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⡈⢿⢀⢀⢸⣿⡇⢀⢀⢀⢀⡛⡟⢁⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⡟⢀⢀⡈⢿⣿⣄⢀⢀⢀⢀⡘⣿⣿⣄⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⢏⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⢀⢀⢀⣠⣾⡿⢃⢀⢀⢀⢀⢀⢻⣿⣧⡀⢀⢀⢀⡈⢻⣿⣷⣦⣄⢀⢀⣠⣤⣶⣿⡿⢋⢀⢀⢀⢀ +// ⢀⢀⢀⢿⢿⢀⢀⢀⢀⢀⢀⢀⢀⢸⢿⢃⢀⢀⢀⢀⢻⢿⢿⢿⢿⢿⢿⢿⢿⢿⢃⢀⢀⢀⢿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⡗⢀⢀⢀⢀⢀⡈⡉⡛⡛⢀⢀⢹⡛⢋⢁⢀⢀⢀⢀⢀⢀ +// +// Author: Mehdy MORVAN +// Date: 04/10/2025 +// Description: Header file for the bounds utils +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once +#include +#include + +namespace nexo::math { + /** + * @struct AABB + * @brief Axis-aligned bounding box defined by min/max corners. + * + * Default-constructed boxes are "empty" (min > max) so they can be + * safely adopted by the first union/merge operation. + * + * Members: + * - glm::vec3 min — minimum corner (x/y/z). + * - glm::vec3 max — maximum corner (x/y/z). + */ + struct AABB { + glm::vec3 min{ +FLT_MAX, +FLT_MAX, +FLT_MAX }; + glm::vec3 max{ -FLT_MAX, -FLT_MAX, -FLT_MAX }; + + /** + * @brief Whether this AABB currently represents no valid volume. + * + * An AABB is considered empty if any component of min is greater + * than the corresponding component of max. + * + * @return true if empty (e.g., before any points/boxes were merged), false otherwise. + */ + bool empty() const; + }; + + /** + * @struct BSphere + * @brief Bounding sphere (center + radius). + * + * Members: + * - glm::vec3 c — sphere center. + * - float r — non-negative radius. + */ + struct BSphere { + glm::vec3 c{0.0f}; + float r{0.0f}; + }; + + /** + * @brief Component-wise union of two AABBs. + * + * If one input is empty, the other is returned. If both are empty, + * an empty AABB is returned. + * + * @param a First AABB. + * @param b Second AABB. + * @return AABB that minimally encloses both @p a and @p b. + */ + AABB aabbUnion(const AABB& a, const AABB& b); + + /** + * @brief Construct a conservative bounding sphere from an AABB. + * + * The sphere center is the AABB center. The radius equals the length + * of the half-extents (distance to a farthest corner). Suitable for + * quick frustum/broad-phase rejects. + * + * @param b Source AABB (may be empty). + * @return BSphere enclosing @p b. For an empty AABB, returns {c=(0,0,0), r=0}. + */ + BSphere sphereFromAABB(const AABB& b); + + /** + * @brief Transform a local-space AABB by a 4×4 matrix and return a world-space AABB. + * + * Interprets the local AABB as an oriented box (center + half-extents) and applies: + * world_center = L * local_center + t + * world_half_extents = |L| * local_half_extents + * where L is the upper-left 3×3 of @p M and t is the translation. Using the + * absolute value of L yields a conservative axis-aligned enclosure under rotation + * and non-uniform scale. + * + * @param local Local-space AABB. + * @param M Transform from local to world space. + * @return World-space AABB that encloses the transformed local box. + */ + AABB aabbTransform(const AABB& local, const glm::mat4& M); +} diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 71ac6a4ea..acc2666df 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -47,6 +47,7 @@ set(SRCS editor/src/DocumentWindows/EditorScene/Shortcuts.cpp editor/src/DocumentWindows/EditorScene/Show.cpp editor/src/DocumentWindows/EditorScene/Shutdown.cpp + editor/src/DocumentWindows/EditorScene/Timecode.cpp editor/src/DocumentWindows/EditorScene/Toolbar.cpp editor/src/DocumentWindows/EditorScene/Update.cpp editor/src/DocumentWindows/EditorScene/DragDrop.cpp diff --git a/editor/main.cpp b/editor/main.cpp index 3ac0ee160..5db767aea 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -12,19 +12,18 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "src/Editor.hpp" +#include "src/DocumentWindows/AssetManager/AssetManagerWindow.hpp" #include "src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp" #include "src/DocumentWindows/EditorScene/EditorScene.hpp" -#include "src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp" #include "src/DocumentWindows/InspectorWindow/InspectorWindow.hpp" -#include "src/DocumentWindows/AssetManager/AssetManagerWindow.hpp" #include "src/DocumentWindows/MaterialInspector/MaterialInspector.hpp" #include "src/DocumentWindows/PrimitiveWindow/PrimitiveWindow.hpp" -#include "src/DocumentWindows/GameWindow/GameWindow.hpp" +#include "src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp" +#include "src/Editor.hpp" -#include -#include #include +#include +#include #include "scripting/native/ManagedTypedef.hpp" #include "scripting/native/Scripting.hpp" @@ -32,12 +31,11 @@ int main(int argc, char **argv) try { loguru::init(argc, argv); - loguru::g_stderr_verbosity = loguru::Verbosity_3; + loguru::g_stderr_verbosity = loguru::Verbosity_3; nexo::editor::Editor &editor = nexo::editor::Editor::getInstance(); editor.registerWindow( - std::format("Default Scene{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, 0) - ); + std::format("Default Scene{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, 0)); editor.registerWindow(NEXO_WND_USTRID_SCENE_TREE); editor.registerWindow(NEXO_WND_USTRID_INSPECTOR); editor.registerWindow(NEXO_WND_USTRID_CONSOLE); @@ -45,18 +43,20 @@ try { editor.registerWindow(NEXO_WND_USTRID_PRIMITIVE_WINDOW); editor.registerWindow(NEXO_WND_USTRID_ASSET_MANAGER); - if (const auto defaultScene = editor.getWindow(std::format("Default Scene{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, 0)).lock()) + if (const auto defaultScene = editor + .getWindow( + std::format("Default Scene{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, 0)) + .lock()) defaultScene->setDefault(); editor.init(); - while (editor.isOpen()) - { + while (editor.isOpen()) { auto start = std::chrono::high_resolution_clock::now(); editor.render(); editor.update(); - auto end = std::chrono::high_resolution_clock::now(); + auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = end - start; std::this_thread::sleep_for(std::chrono::milliseconds(16) - elapsed); diff --git a/editor/src/ADocumentWindow.cpp b/editor/src/ADocumentWindow.cpp index 48f4ba995..e8c24116e 100644 --- a/editor/src/ADocumentWindow.cpp +++ b/editor/src/ADocumentWindow.cpp @@ -17,51 +17,48 @@ namespace nexo::editor { - void ADocumentWindow::beginRender(const std::string &windowName) + void ADocumentWindow::beginRender(const std::string& windowName) { dockingUpdate(windowName); visibilityUpdate(); sizeUpdate(); } - void ADocumentWindow::dockingUpdate(const std::string &windowName) + void ADocumentWindow::dockingUpdate(const std::string& windowName) { - if (const ImGuiWindow* currentWindow = ImGui::GetCurrentWindow(); currentWindow) - { - const bool isDocked = currentWindow->DockIsActive; + if (const ImGuiWindow* currentWindow = ImGui::GetCurrentWindow(); currentWindow) { + const bool isDocked = currentWindow->DockIsActive; const ImGuiID currentDockID = currentWindow->DockId; - const auto dockId = m_windowRegistry.getDockId(windowName); + const auto dockId = m_windowRegistry.getDockId(windowName); - // If it's the first time opening the window and we have a dock id saved in the registry, then we force set it + // If it's the first time opening the window and we have a dock id saved in the registry, then we force set + // it if (m_firstOpened && (dockId && currentDockID != *dockId)) ImGui::DockBuilderDockWindow(windowName.c_str(), *dockId); // If the docks ids differ, it means the window got rearranged in the global layout - // If we are docked but we dont have a dock id saved in the registry, it means the user moved the window + // If we are docked, but we don't have a dock id saved in the registry, it means the user moved the window // In both cases, we update our docking registry with the new dock id else if ((dockId && currentDockID != *dockId) || (isDocked && !dockId)) m_windowRegistry.setDockId(windowName, currentDockID); - // If it is not docked anymore, we have a floating window without docking node, // So we erase it from the docking registry - if (!m_firstOpened && !isDocked) - m_windowRegistry.resetDockId(windowName); + if (!m_firstOpened && !isDocked) m_windowRegistry.resetDockId(windowName); m_firstOpened = false; } } void ADocumentWindow::visibilityUpdate() { - m_focused = ImGui::IsWindowFocused(); - const bool isDocked = ImGui::IsWindowDocked(); + m_focused = ImGui::IsWindowFocused(); + const bool isDocked = ImGui::IsWindowDocked(); const ImGuiWindow* window = ImGui::GetCurrentWindow(); if (isDocked) { // If the window is currently being rendered with normal content, // and not hidden or set to skip items, then it is visible m_isVisibleInDock = !window->Hidden && !window->SkipItems && window->Active; - } - else { + } else { // Not docked windows are visible if we've reached this point m_isVisibleInDock = true; } @@ -71,10 +68,10 @@ namespace nexo::editor { void ADocumentWindow::sizeUpdate() { const ImGuiWindow* window = ImGui::GetCurrentWindow(); - m_windowPos = window->Pos; - m_windowSize = window->Size; - m_contentSizeMin = ImGui::GetWindowContentRegionMin(); - m_contentSizeMax = ImGui::GetWindowContentRegionMax(); - m_contentSize = ImGui::GetContentRegionAvail(); + m_windowPos = window->Pos; + m_windowSize = window->Size; + m_contentSizeMin = ImGui::GetWindowContentRegionMin(); + m_contentSizeMax = ImGui::GetWindowContentRegionMax(); + m_contentSize = ImGui::GetContentRegionAvail(); } -} +} // namespace nexo::editor diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index a0be67f7d..0ca7b730e 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -23,91 +23,276 @@ namespace nexo::editor { - #define NEXO_WND_USTRID_INSPECTOR "###Inspector" - #define NEXO_WND_USTRID_SCENE_TREE "###Scene Tree" - #define NEXO_WND_USTRID_ASSET_MANAGER "###Asset Manager" - #define NEXO_WND_USTRID_CONSOLE "###Console" - #define NEXO_WND_USTRID_MATERIAL_INSPECTOR "###Material Inspector" - #define NEXO_WND_USTRID_PRIMITIVE_WINDOW "###Primitive Window" - #define NEXO_WND_USTRID_DEFAULT_SCENE "###Default Scene" - #define NEXO_WND_USTRID_BOTTOM_BAR "###CommandsBar" - #define NEXO_WND_USTRID_TEST "###TestWindow" - #define NEXO_WND_USTRID_GAME_WINDOW "###GameWindow" +#define NEXO_WND_USTRID_INSPECTOR "###Inspector" +#define NEXO_WND_USTRID_SCENE_TREE "###Scene Tree" +#define NEXO_WND_USTRID_ASSET_MANAGER "###Asset Manager" +#define NEXO_WND_USTRID_CONSOLE "###Console" +#define NEXO_WND_USTRID_MATERIAL_INSPECTOR "###Material Inspector" +#define NEXO_WND_USTRID_PRIMITIVE_WINDOW "###Primitive Window" +#define NEXO_WND_USTRID_DEFAULT_SCENE "###Default Scene" +#define NEXO_WND_USTRID_BOTTOM_BAR "###CommandsBar" +#define NEXO_WND_USTRID_TEST "###TestWindow" +#define NEXO_WND_USTRID_GAME_WINDOW "###GameWindow" class ADocumentWindow : public IDocumentWindow { - public: - /** - * @brief Constructs a new ADocumentWindow instance with a unique window ID. - * - * Initializes the document window by storing a reference to the provided WindowRegistry and assigning a unique window - * identifier. This setup is essential for integrating the window with the docking management system. - */ - explicit ADocumentWindow(std::string windowName, WindowRegistry &windowRegistry) : m_windowName(std::move(windowName)), m_windowRegistry(windowRegistry) - { - windowId = nextWindowId++; - }; - ~ADocumentWindow() override = default; - - [[nodiscard]] bool isFocused() const override { return m_focused; } - [[nodiscard]] bool isOpened() const override { return m_opened; } - void setOpened(bool opened) override { m_opened = opened; } - [[nodiscard]] bool isHovered() const override { return m_hovered; } - - [[nodiscard]] const ImVec2 &getContentSize() const override { return m_contentSize; } - - /** - * @brief Retrieves the open state of the document window. - * - * Returns a reference to the internal boolean that tracks whether the window is open. - * This reference is primarily intended for ImGui integration, allowing direct manipulation - * of the window's visibility state. - * - * @return A reference to the window's open state. - */ - [[nodiscard]] bool &getOpened() override { return m_opened; } - - [[nodiscard]] const std::string &getWindowName() const override { return m_windowName; } - - [[nodiscard]] const WindowState &getWindowState() const override { return m_windowState; }; - - - - WindowId windowId; - protected: - bool m_opened = true; - bool m_focused = false; - bool m_hovered = false; // TODO: make these update without user intervention - bool m_wasVisibleLastFrame = false; - bool m_isVisibleInDock = true; - - ImVec2 m_windowPos; - ImVec2 m_windowSize; - ImVec2 m_contentSizeMin; - ImVec2 m_contentSizeMax; - ImVec2 m_contentSize; - - bool m_firstOpened = true; - - std::string m_windowName; - WindowState m_windowState; - - WindowRegistry &m_windowRegistry; - - void beginRender(const std::string &windowName); - private: - /** - * @brief Initializes the docking configuration for the document window on its first display. - * - * This function retrieves the current ImGui window and checks its docking state to ensure it aligns with the expected - * configuration from the WindowRegistry. On the first open (when m_firstOpened is true), if the window is not actively - * docked or its current dock ID does not match the expected ID obtained via the provided window name, the function assigns - * the expected dock ID to the window. If the window is already docked but the dock IDs still differ, the current dock ID is - * saved to the WindowRegistry. The m_firstOpened flag is then set to false so that the docking configuration is applied only once. - * - * @param windowName The name used to look up the expected dock identifier in the WindowRegistry. - */ - void dockingUpdate(const std::string &windowName); - void visibilityUpdate(); - void sizeUpdate(); + public: + /** + * @brief Constructs a new ADocumentWindow instance with a unique window ID. + * + * Initializes the document window by storing a reference to the provided WindowRegistry and assigning a unique + * window identifier. This setup is essential for integrating the window with the docking management system. + */ + explicit ADocumentWindow(std::string windowName, WindowRegistry &windowRegistry) + : m_windowName(std::move(windowName)), m_windowRegistry(windowRegistry) + { + windowId = nextWindowId++; + }; + ~ADocumentWindow() override = default; + + /** + * @brief Checks if the document window is currently focused. + * + * This function returns a boolean indicating whether the window is currently focused and active. + * It is marked as [[nodiscard]] to encourage users to handle the return value appropriately. + * + * @return True if the window is focused, false otherwise. + */ + [[nodiscard]] bool isFocused() const override + { + return m_focused; + } + + /** + * @brief Checks if the document window is currently opened. + * + * This function returns a boolean indicating whether the window is open and should be rendered. + * It is marked as [[nodiscard]] to encourage users to handle the return value appropriately. + * + * @return True if the window is opened, false otherwise. + */ + [[nodiscard]] bool isOpened() const override + { + return m_opened; + } + + /** + * @brief Sets the open state of the document window. + * + * This function allows external code to modify the open state of the window, determining whether it should be + * rendered or hidden. It overrides the corresponding method in the IDocumentWindow interface. + * + * @param opened A boolean value indicating the desired open state of the window. + */ + void setOpened(bool opened) override + { + m_opened = opened; + } + + /** + * @brief Checks if the document window is currently hovered by the mouse cursor. + * + * This function returns a boolean indicating whether the window is currently hovered. + * It is marked as [[nodiscard]] to encourage users to handle the return value appropriately. + * + * @return True if the window is hovered, false otherwise. + */ + [[nodiscard]] bool isHovered() const override + { + return m_hovered; + } + + /** + * @brief Checks if the document window is currently visible within its docking context. + * + * This function returns a boolean indicating whether the window is visible in its docked state. + * It is marked as [[nodiscard]] to encourage users to handle the return value appropriately. + * + * @return True if the window is visible in its dock, false otherwise. + */ + [[nodiscard]] bool isVisibleInDock() const + { + return m_isVisibleInDock; + } + + /** + * @brief Retrieves the position of the document window. + * + * This function returns a constant reference to the ImVec2 structure representing the window's position + * on the screen. The returned reference is marked as [[nodiscard]] to encourage users to utilize the + * information provided by the window position. + * + * @return A constant reference to the position of the document window. + */ + [[nodiscard]] const ImVec2 &getWindowPos() const + { + return m_windowPos; + } + /** + * @brief Retrieves the size of the document window. + * + * This function returns a constant reference to the ImVec2 structure representing the window's size. + * The returned reference is marked as [[nodiscard]] to encourage users to utilize the information + * provided by the window size. + * + * @return A constant reference to the size of the document window. + */ + [[nodiscard]] const ImVec2 &getWindowSize() const + { + return m_windowSize; + } + + /** + * @brief Retrieves the minimum size of the content area within the document window. + * + * This function returns a constant reference to the ImVec2 structure representing the minimum size + * of the content area. The returned reference is marked as [[nodiscard]] to encourage users to + * utilize the information provided by the content size. + * + * @return A constant reference to the minimum content size of the document window. + */ + [[nodiscard]] const ImVec2 &getContentSizeMin() const + { + return m_contentSizeMin; + } + + /** + * @brief Retrieves the maximum size of the content area within the document window. + * + * This function returns a constant reference to the ImVec2 structure representing the maximum size + * of the content area. The returned reference is marked as [[nodiscard]] to encourage users to + * utilize the information provided by the content size. + * + * @return A constant reference to the maximum content size of the document window. + */ + [[nodiscard]] const ImVec2 &getContentSizeMax() const + { + return m_contentSizeMax; + } + + /** + * @brief Retrieves the available size of the content area within the document window. + * + * This function returns a constant reference to the ImVec2 structure representing the available size + * of the content area. The returned reference is marked as [[nodiscard]] to encourage users to + * utilize the information provided by the content size. + * + * @return A constant reference to the available content size of the document window. + */ + [[nodiscard]] const ImVec2 &getContentSize() const override + { + return m_contentSize; + } + + /** + * @brief Retrieves the open state of the document window. + * + * Returns a reference to the internal boolean that tracks whether the window is open. + * This reference is primarily intended for ImGui integration, allowing direct manipulation + * of the window's visibility state. + * + * @return A reference to the window's open state. + */ + [[nodiscard]] bool &getOpened() override + { + return m_opened; + } + + /** + * @brief Retrieves the name of the document window. + * + * This function returns a constant reference to the window's name, which is used for identification + * and docking purposes within the editor. The returned reference is marked as [[nodiscard]] to + * encourage users to utilize the information provided by the window name. + * + * @return A constant reference to the name of the document window. + */ + [[nodiscard]] const std::string &getWindowName() const override + { + return m_windowName; + } + + /** + * @brief Retrieves the current state of the window. + * + * This function returns a constant reference to the WindowState object, which encapsulates + * various attributes of the window's state, such as its position, size, focus status, and visibility. + * The returned reference is marked as [[nodiscard]] to encourage users to utilize the information + * provided by the WindowState. + * + * @return A constant reference to the WindowState of the document window. + */ + [[nodiscard]] const WindowState &getWindowState() const override + { + return m_windowState; + }; + + WindowId windowId; + + protected: + bool m_opened = true; + bool m_focused = false; + bool m_hovered = false; // TODO: make these update without user intervention + bool m_wasVisibleLastFrame = false; + bool m_isVisibleInDock = true; + + ImVec2 m_windowPos; + ImVec2 m_windowSize; + ImVec2 m_contentSizeMin; + ImVec2 m_contentSizeMax; + ImVec2 m_contentSize; + + bool m_firstOpened = true; + + std::string m_windowName; + WindowState m_windowState; + + WindowRegistry &m_windowRegistry; + + /** + * @brief Prepares the document window for rendering. + * + * This function should be called at the beginning of the window's rendering process. It handles + * docking configuration, visibility updates, and size adjustments to ensure the window is correctly + * set up before any content is drawn. The function takes the window name as a parameter to manage + * docking behavior through the WindowRegistry. + * + * @param windowName The name of the window used for docking management. + */ + void beginRender(const std::string &windowName); + + private: + /** + * @brief Initializes the docking configuration for the document window on its first display. + * + * This function retrieves the current ImGui window and checks its docking state to ensure it aligns with the + * expected configuration from the WindowRegistry. On the first open (when m_firstOpened is true), if the window + * is not actively docked or its current dock ID does not match the expected ID obtained via the provided window + * name, the function assigns the expected dock ID to the window. If the window is already docked but the dock + * IDs still differ, the current dock ID is saved to the WindowRegistry. The m_firstOpened flag is then set to + * false so that the docking configuration is applied only once. + * + * @param windowName The name used to look up the expected dock identifier in the WindowRegistry. + */ + void dockingUpdate(const std::string &windowName); + + /** + * @brief Updates the visibility status of the document window. + * + * This function checks whether the window is currently focused, docked, and active to determine its visibility + * state. If the window is docked, it verifies that it is not hidden or set to skip items to consider it + * visible. For undocked windows, reaching this function implies visibility. The function also updates the + * m_hovered status based on whether the window is currently hovered by the mouse cursor. + */ + void visibilityUpdate(); + + /** + * @brief Updates the size and position attributes of the document window. + * + * This function retrieves the current ImGui window and updates the internal state variables to reflect the + * window's position, size, and content region dimensions. It captures the overall window position and size, + * as well as the minimum and maximum coordinates of the content region, and calculates the available content + * size for rendering purposes. + */ + void sizeUpdate(); }; -} +} // namespace nexo::editor diff --git a/editor/src/DockingRegistry.cpp b/editor/src/DockingRegistry.cpp index 964563290..c22673c95 100644 --- a/editor/src/DockingRegistry.cpp +++ b/editor/src/DockingRegistry.cpp @@ -13,30 +13,28 @@ /////////////////////////////////////////////////////////////////////////////// #include "DockingRegistry.hpp" -#include #include +#include namespace nexo::editor { - void DockingRegistry::setDockId(const std::string& name, const ImGuiID id) - { - dockIds[name] = id; - } + void DockingRegistry::setDockId(const std::string& name, const ImGuiID id) + { + dockIds[name] = id; + } - std::optional DockingRegistry::getDockId(const std::string& name) const - { - const auto it = dockIds.find(name); - if (it != dockIds.end()) { - return it->second; - } - return std::nullopt; - } + std::optional DockingRegistry::getDockId(const std::string& name) const + { + if (const auto it = dockIds.find(name); it != dockIds.end()) { + return it->second; + } + return std::nullopt; + } - void DockingRegistry::resetDockId(const std::string &name) - { + void DockingRegistry::resetDockId(const std::string& name) + { const auto it = dockIds.find(name); - if (it == dockIds.end()) - return; + if (it == dockIds.end()) return; dockIds.erase(it); - } -} + } +} // namespace nexo::editor diff --git a/editor/src/DockingRegistry.hpp b/editor/src/DockingRegistry.hpp index 094081d14..7469936cd 100644 --- a/editor/src/DockingRegistry.hpp +++ b/editor/src/DockingRegistry.hpp @@ -13,10 +13,10 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include #include -#include #include +#include +#include #include "utils/TransparentStringHash.hpp" @@ -33,46 +33,45 @@ namespace nexo::editor { * It uses a TransparentStringHash for efficient string lookups and provides methods * for setting, retrieving, and removing dock ID associations. */ - class DockingRegistry { - public: - - /** - * @brief Registers or updates a docking identifier for a specified name. - * - * This method associates the provided docking ID with the given name within the registry, - * allowing for subsequent retrieval when needed. - * - * @param name The name key for the docking entry. - * @param id The docking identifier to be associated with the name. - */ - void setDockId(const std::string& name, ImGuiID id); + class DockingRegistry { + public: + /** + * @brief Registers or updates a docking identifier for a specified name. + * + * This method associates the provided docking ID with the given name within the registry, + * allowing for subsequent retrieval when needed. + * + * @param name The name key for the docking entry. + * @param id The docking identifier to be associated with the name. + */ + void setDockId(const std::string& name, ImGuiID id); - /** - * @brief Retrieves the dock ID associated with the specified name. - * - * This method searches the internal registry for the given name and returns the dock ID - * wrapped in an optional if found. If the name does not exist in the registry, it returns an empty optional. - * - * @param name The name identifier of the dock. - * @return std::optional Optional containing the dock ID if found; otherwise, std::nullopt. - */ - std::optional getDockId(const std::string& name) const; + /** + * @brief Retrieves the dock ID associated with the specified name. + * + * This method searches the internal registry for the given name and returns the dock ID + * wrapped in an optional if found. If the name does not exist in the registry, it returns an empty optional. + * + * @param name The name identifier of the dock. + * @return std::optional containing the dock ID if found; otherwise, std::nullptr. + */ + [[nodiscard]] std::optional getDockId(const std::string& name) const; - /** - * @brief Removes a dock ID association for the specified name. - * - * This method removes the association between the given name and its dock ID - * from the registry. If the name does not exist in the registry, this method - * has no effect. - * - * Removing a dock ID is useful when a window is closed or when its docking - * configuration needs to be reset to default. - * - * @param name The name identifier of the dock association to remove. - */ - void resetDockId(const std::string &name); + /** + * @brief Removes a dock ID association for the specified name. + * + * This method removes the association between the given name and its dock ID + * from the registry. If the name does not exist in the registry, this method + * has no effect. + * + * Removing a dock ID is useful when a window is closed or when its docking + * configuration needs to be reset to default. + * + * @param name The name identifier of the dock association to remove. + */ + void resetDockId(const std::string& name); - private: - std::unordered_map> dockIds; - }; -} + private: + std::unordered_map> dockIds; + }; +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp index 6fbc39d72..d40ddc0c4 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp +++ b/editor/src/DocumentWindows/AssetManager/AssetGrid.cpp @@ -54,7 +54,7 @@ namespace nexo::editor { } static void drawAssetThumbnail(const assets::GenericAssetRef& asset, const LayoutSettings& layout, - const AssetLayoutParams& params, const bool isSelected) + const AssetLayoutParams& params) { ImDrawList* drawList = ImGui::GetWindowDrawList(); @@ -129,7 +129,7 @@ namespace nexo::editor { ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * GridLayoutSizes::THUMBNAIL_HEIGHT_RATIO); const AssetLayoutParams assetLayoutParams{itemPos, itemSize, itemEnd, thumbnailEnd}; - drawAssetThumbnail(asset, m_layout, assetLayoutParams, isSelected); + drawAssetThumbnail(asset, m_layout, assetLayoutParams); drawAssetTitle(assetData, assetLayoutParams); ImGui::Selectable("###asset", isSelected, @@ -141,7 +141,7 @@ namespace nexo::editor { if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay)) { - ImGui::SetTooltip(assetData->getMetadata().location.getName().c_str()); + ImGui::SetTooltip("%s", assetData->getMetadata().location.getName().c_str()); } if (ImGui::IsItemHovered()) { // Handle on hover and selection @@ -195,20 +195,16 @@ namespace nexo::editor { { ImDrawList* drawList = ImGui::GetWindowDrawList(); - constexpr ImU32 titleBgColor = IM_COL32(0, 0, 0, 0); - const float titleAreaHeight = params.itemSize.y * (1.0f - GridLayoutSizes::THUMBNAIL_HEIGHT_RATIO); + constexpr ImU32 titleBgColor = IM_COL32(0, 0, 0, 0); + const float titleAreaHeight = params.itemSize.y * (1.0f - GridLayoutSizes::THUMBNAIL_HEIGHT_RATIO); const float titlePadding = std::max(2.0f, titleAreaHeight * 0.1f); const float availableTextWidth = params.itemSize.x - (titlePadding * 2); drawList->AddRectFilled(ImVec2(params.itemPos.x, params.thumbnailEnd.y), ImVec2(params.itemEnd.x, params.itemEnd.y), titleBgColor); - const ImVec2 textSize = ImGui::CalcTextSize(folderName.c_str()); - // const float textY = params.thumbnailEnd.y + ((titleAreaHeight - textSize.y) * 0.5f); - // const float textX = params.itemPos.x + (params.itemSize.x - textSize.x) * 0.5f; - - const ImVec2 fullTextSize = ImGui::CalcTextSize(folderName.c_str()); - std::string displayText = folderName; + const ImVec2 fullTextSize = ImGui::CalcTextSize(folderName.c_str()); + std::string displayText = folderName; // Crop text if it's too wide if (fullTextSize.x > availableTextWidth) cropText(folderName, displayText, availableTextWidth); @@ -224,8 +220,8 @@ namespace nexo::editor { const ImVec2& itemPos, const ImVec2& itemSize) { const bool isSelected = m_selectedFolders.contains(folderPath); - ImDrawList* drawList = ImGui::GetWindowDrawList(); - const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * GridLayoutSizes::THUMBNAIL_HEIGHT_RATIO); const AssetLayoutParams folderLayoutParams{itemPos, itemSize, itemEnd, thumbnailEnd}; @@ -243,7 +239,7 @@ namespace nexo::editor { if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay)) { - ImGui::SetTooltip(folderName.c_str()); + ImGui::SetTooltip("%s", folderName.c_str()); } if (ImGui::IsItemHovered()) { // Handle on hover and selection @@ -277,7 +273,7 @@ namespace nexo::editor { handleAssetDrop(folderPath); handleFolderDrag(folderPath, folderName); - handleFolderDrop(folderPath, folderName); + handleFolderDrop(folderPath); constexpr ImU32 bgColor = IM_COL32(0, 0, 0, 0); drawList->AddRectFilled(itemPos, itemEnd, bgColor, GridLayoutSizes::CORNER_RADIUS); diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index b594ebd2f..70c32d143 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -162,7 +162,7 @@ namespace nexo::editor { void deleteUsedAssetPopup(); void assetDetailsPopup(); - template + template static void drawErrorMessageInPopup(T& actionState); // menu management @@ -179,9 +179,9 @@ namespace nexo::editor { // drag and drop management void handleDroppedFiles(); static void handleAssetDrag(const assets::GenericAssetRef& asset); - void handleFolderDrag(const std::string& folderPath, const std::string& folderName); + void handleFolderDrag(const std::string& folderPath, const std::string& folderName) const; void handleAssetDrop(const std::string& path); - void handleFolderDrop(const std::string& folderPath, const std::string &folderName); + void handleFolderDrop(const std::string& folderPath); void importDroppedFile(const std::string& filePath) const; std::set m_selectedAssets; // Set of selected asset indices @@ -191,9 +191,9 @@ namespace nexo::editor { assets::AssetType m_selectedType = assets::AssetType::UNKNOWN; // Default selected asset type - std::set m_selectedFolders; // Set of selected folder paths - std::string m_currentFolder; // Currently selected folder - std::string m_hoveredFolder; // Currently hovered folder + std::set> m_selectedFolders; // Set of selected folder paths + std::string m_currentFolder; // Currently selected folder + std::string m_hoveredFolder; // Currently hovered folder std::string m_searchBuffer; PopupManager m_popupManager; diff --git a/editor/src/DocumentWindows/AssetManager/AssetsPopupUtils.cpp b/editor/src/DocumentWindows/AssetManager/AssetsPopupUtils.cpp index 56af0bdde..a6bcd26fe 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetsPopupUtils.cpp +++ b/editor/src/DocumentWindows/AssetManager/AssetsPopupUtils.cpp @@ -45,7 +45,7 @@ namespace nexo::editor { ImGui::Text("Are you sure you want to delete %s?", assetName.c_str()); ImGui::Separator(); - if (Button("Delete", ImNexo::VALIDATION)) { + if (Button("Delete", ImNexo::ButtonTypes::VALIDATION)) { // TODO: Check if the asset is used before deleting // if (m_assetActionState.assetData->isUsed()) { // m_popupManager.openPopup("Delete Used Asset Popup"); @@ -62,7 +62,7 @@ namespace nexo::editor { } // } ImGui::SameLine(); - if (Button("Cancel", ImNexo::CANCEL)) { + if (Button("Cancel", ImNexo::ButtonTypes::CANCEL)) { m_assetActionState.reset(); PopupManager::closePopup(); } @@ -80,7 +80,7 @@ namespace nexo::editor { ImGui::Text("%s is used by one or more entities.\nAre you sure you want to delete it?", assetName.c_str()); ImGui::Separator(); - if (Button("Delete", ImNexo::VALIDATION) && + if (Button("Delete", ImNexo::ButtonTypes::VALIDATION) && assets::AssetCatalog::getInstance().deleteAsset(m_assetActionState.assetData->getID())) { m_assetActionState.reset(); PopupManager::closePopup(); @@ -89,7 +89,7 @@ namespace nexo::editor { m_assetActionState.errorMessage = "Failed to delete the asset (may currently be in use)"; } ImGui::SameLine(); - if (Button("Cancel", ImNexo::CANCEL)) { + if (Button("Cancel", ImNexo::ButtonTypes::CANCEL)) { m_assetActionState.reset(); PopupManager::closePopup(); } @@ -132,9 +132,9 @@ namespace nexo::editor { ImGui::Text("Enter a new name for the asset:"); // Input text for the new asset name - std::string assetName = m_assetActionState.assetData->m_metadata.location.getName().c_str(); constexpr size_t MAX_ASSET_NAME_LENGTH = 256; - static std::string newName = assetName; + static std::string newName; + std::string assetName = m_assetActionState.assetData->m_metadata.location.getName().c_str(); if (newName.empty()) { newName = assetName; } @@ -143,19 +143,19 @@ namespace nexo::editor { ImGui::SetKeyboardFocusHere(); isFocus = false; } - ImGui::InputText("##AssetName", newName.data(), assetName.capacity(), ImGuiInputTextFlags_AutoSelectAll); + ImGui::InputText("##AssetName", newName.data(), MAX_ASSET_NAME_LENGTH, ImGuiInputTextFlags_AutoSelectAll); newName.resize(strlen(newName.c_str())); ImGui::Separator(); // Buttons for renaming or canceling the action - if (Button("Rename", ImNexo::VALIDATION) && handleAssetRenaming(newName)) { + if (Button("Rename", ImNexo::ButtonTypes::VALIDATION) && handleAssetRenaming(newName)) { m_assetActionState.reset(); newName = ""; PopupManager::closePopup(); isFocus = true; } ImGui::SameLine(); - if (Button("Cancel", ImNexo::CANCEL)) { + if (Button("Cancel", ImNexo::ButtonTypes::CANCEL)) { m_assetActionState.reset(); newName = ""; PopupManager::closePopup(); @@ -179,7 +179,7 @@ namespace nexo::editor { ImGui::Text("Status: %s", m_assetActionState.assetData->isLoaded() ? "Loaded" : "Not Loaded"); ImGui::Separator(); - if (Button("Close", ImNexo::CANCEL)) { + if (Button("Close", ImNexo::ButtonTypes::CANCEL)) { m_assetActionState.reset(); PopupManager::closePopup(); } diff --git a/editor/src/DocumentWindows/AssetManager/DragDrop.cpp b/editor/src/DocumentWindows/AssetManager/DragDrop.cpp index 1dc0e15ad..7942bc2d0 100644 --- a/editor/src/DocumentWindows/AssetManager/DragDrop.cpp +++ b/editor/src/DocumentWindows/AssetManager/DragDrop.cpp @@ -34,11 +34,11 @@ namespace nexo::editor { } } - void AssetManagerWindow::handleFolderDrop(const std::string& folderPath, const std::string &folderName) + void AssetManagerWindow::handleFolderDrop(const std::string& folderPath) { if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("FOLDER_DRAG")) { - const auto data = static_cast(payload->Data); + const auto data = static_cast(payload->Data); m_folderManager.moveFolder(data->path, folderPath); m_selectedFolders.clear(); } @@ -69,7 +69,7 @@ namespace nexo::editor { } } - void AssetManagerWindow::handleFolderDrag(const std::string& folderPath, const std::string& folderName) + void AssetManagerWindow::handleFolderDrag(const std::string& folderPath, const std::string& folderName) const { if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { FolderDragDropPayload payload; @@ -79,11 +79,9 @@ namespace nexo::editor { payload.name[sizeof(payload.name) - 1] = '\0'; ImGui::SetDragDropPayload("FOLDER_DRAG", &payload, sizeof(payload)); - // ImTextureID textureID = ThumbnailCache::getInstance().getThumbnail(asset); - // if (textureID) ImGui::Image(textureID, {64, 64}, ImVec2(0, 1), ImVec2(1, 0)); const ImTextureID folderIconTexture = getIconTexture(m_folderIcon); if (folderIconTexture) { - ImGui::Image(folderIconTexture, {64, 64}, ImVec2(0, 1), ImVec2(0, 1)); // White tint for default color + ImGui::Image(folderIconTexture, {64, 64}, ImVec2(0, 1), ImVec2(0, 1)); // White tint for default color } ImGui::EndDragDropSource(); } @@ -134,7 +132,7 @@ namespace nexo::editor { if (const auto assetRef = importer.importAssetAuto(location, fileInput); !assetRef) LOG(NEXO_ERROR, "Failed to import asset: {}", location.getPath().data()); } catch (const std::exception& e) { - LOG(NEXO_ERROR, "Exception while importing {}: {}", location.getPath().data(), e.what()); + THROW_EXCEPTION(AssetImportException, location.getPath(), e.what()); } } } // namespace nexo::editor diff --git a/editor/src/DocumentWindows/AssetManager/FolderManager.cpp b/editor/src/DocumentWindows/AssetManager/FolderManager.cpp index de20d3523..72b4b1f37 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderManager.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderManager.cpp @@ -40,7 +40,6 @@ namespace nexo::editor { { clear(); std::unordered_set allPaths; - for (const auto& ref : assets::AssetCatalog::getInstance().getAssets()) { if (const auto assetData = ref.lock()) { const std::string& folderPath = assetData->getMetadata().location.getPath(); @@ -276,7 +275,7 @@ namespace nexo::editor { /** * @brief Moves a folder to a new parent folder and updates all its children paths accordingly. - * + * * @param currentFolderPath The current path of the folder to move. * @param path The new parent path where the folder will be moved. * @return `true` if the folder was successfully moved, `false` otherwise. @@ -294,6 +293,7 @@ namespace nexo::editor { if (exists(newFolderPath)) return false; + // get all children paths that need to be updated std::vector toUpdate = {}; for (const auto& key : m_children | std::views::keys) { if (key.starts_with(currentFolderPath)) { @@ -303,12 +303,13 @@ namespace nexo::editor { for (auto& child : m_children[newKey]) { if (child.starts_with(key)) { const std::string newChildPath = newKey + child.substr(key.size()); - child = newChildPath; + child = newChildPath; } } } } + // remove from parent's children list std::erase(m_children[parentPath], currentFolderPath); m_children[path].push_back(newFolderPath); @@ -327,6 +328,7 @@ namespace nexo::editor { } } + // update path in children for (const auto& oldPath : toUpdate) { m_children.erase(oldPath); } @@ -339,19 +341,20 @@ namespace nexo::editor { if (pathToRemove.empty()) { m_pathToName[newFolderPath] = folderName; if (!key.empty() && key.find(currentFolderPath) != std::string::npos) { - const std::string newKey = path.empty() ? key : path + "/" + key; + const std::string newKey = path.empty() ? key : std::format("%s/%s", path, key); toUpdate.push_back(key); m_pathToName[newKey] = m_pathToName[key]; } } if (!key.empty() && !pathToRemove.empty() && key.find(currentFolderPath) != std::string::npos) { - const std::string newKey = path.empty() ? key.substr(pathToRemove.size()) : - path + "/" + key.substr(pathToRemove.size()); + const std::string newKey = + path.empty() ? key.substr(pathToRemove.size()) : path + "/" + key.substr(pathToRemove.size()); toUpdate.push_back(key); m_pathToName[newKey] = m_pathToName[key]; } } + // clean up old paths for (const auto& oldPath : toUpdate) { m_pathToName.erase(oldPath); } @@ -406,25 +409,15 @@ namespace nexo::editor { */ float FolderManager::getFolderSize(const std::string& folderPath) { - namespace fs = std::filesystem; - float totalSize = 0.0f; - try { - for (const auto& entry : fs::recursive_directory_iterator(folderPath)) { - if (is_regular_file(entry.path())) { - totalSize += static_cast(fs::file_size(entry.path())); - } - } - } catch (const std::exception&) { - return 0.0f; - } - return totalSize / (1024.0f * 1024.0f); // Return size in Mo + // TODO: implement this function properly + return 0.0f; } /** * @brief Clears all folder data and resets the folder manager to its initial state. * * This method removes all entries from the internal maps `m_pathToName` and `m_children`, - * effectively clearing the folder hierarchy. After clearing, it reinitializes the root + * effectively clearing the folder hierarchy. After clearing, it reinitialized the root * folder with the name "Assets" and an empty list of children. */ void FolderManager::clear() diff --git a/editor/src/DocumentWindows/AssetManager/FolderManager.hpp b/editor/src/DocumentWindows/AssetManager/FolderManager.hpp index e72358b32..71c592b49 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderManager.hpp +++ b/editor/src/DocumentWindows/AssetManager/FolderManager.hpp @@ -45,7 +45,7 @@ namespace nexo::editor { bool renameFolder(const std::string& folderPath, const std::string& newName); - bool moveFolder(const std::string& currentFolderPath, const std::string &path); + bool moveFolder(const std::string& currentFolderPath, const std::string& path); [[nodiscard]] std::vector getAllPaths() const; diff --git a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp index e622307f9..031186a6b 100644 --- a/editor/src/DocumentWindows/AssetManager/FolderTree.cpp +++ b/editor/src/DocumentWindows/AssetManager/FolderTree.cpp @@ -48,9 +48,11 @@ namespace nexo::editor { ImGuiTreeNodeFlags rootFlags = ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnDoubleClick; if (!ImGui::TreeNodeEx(ICON_FA_STAR " Favorites", rootFlags)) return; - static constexpr FavoriteItem favorites[]{{ICON_FA_ADJUST, "Materials", assets::AssetType::MATERIAL}, - {ICON_FA_CUBE, "Models", assets::AssetType::MODEL}, - {ICON_FA_SQUARE, "Textures", assets::AssetType::TEXTURE}}; + static constexpr std::array favorites{{ + {ICON_FA_ADJUST, "Materials", assets::AssetType::MATERIAL}, + {ICON_FA_CUBE, "Models", assets::AssetType::MODEL}, + {ICON_FA_SQUARE, "Textures", assets::AssetType::TEXTURE} + }}; for (const auto& fav : favorites) { const bool isSelected = (fav.type == selectedType); @@ -82,6 +84,16 @@ namespace nexo::editor { PopupManager::endPopup(); } + /** + * @brief Recursively draws a folder tree item and its children. + * + * This method handles the rendering of a single folder item in the tree, + * including its icon, name, selection state, and context menu on right-click. + * It also recursively draws any child folders. + * + * @param name The display name of the folder. + * @param path The full path of the folder. + */ void AssetManagerWindow::drawFolderTreeItem(const std::string& name, const std::string& path) { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; @@ -95,7 +107,7 @@ namespace nexo::editor { ImGui::PopStyleColor(); ImGui::SameLine(); - bool opened = ImGui::TreeNodeEx(name.c_str(), flags); + const bool opened = ImGui::TreeNodeEx(name.c_str(), flags); if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && !ImGui::IsItemToggledOpen()) m_currentFolder = path; if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { diff --git a/editor/src/DocumentWindows/AssetManager/FoldersPopupUtils.cpp b/editor/src/DocumentWindows/AssetManager/FoldersPopupUtils.cpp index c17003efc..21c820a1e 100644 --- a/editor/src/DocumentWindows/AssetManager/FoldersPopupUtils.cpp +++ b/editor/src/DocumentWindows/AssetManager/FoldersPopupUtils.cpp @@ -102,13 +102,13 @@ namespace nexo::editor { m_folderActionState.folderName.resize(strlen(m_folderActionState.folderName.c_str())); ImGui::Separator(); - if (Button("Create", ImNexo::VALIDATION) && handleFolderCreation()) { + if (Button("Create", ImNexo::ButtonTypes::VALIDATION) && handleFolderCreation()) { m_folderActionState.reset(); PopupManager::closePopup(); isFocus = true; } ImGui::SameLine(); - if (Button("Cancel", ImNexo::CANCEL)) { + if (Button("Cancel", ImNexo::ButtonTypes::CANCEL)) { m_folderActionState.reset(); PopupManager::closePopup(); isFocus = true; @@ -137,7 +137,7 @@ namespace nexo::editor { // Draw the popup for deleting a folder ImGui::Text("Are you sure you want to delete %s?", m_folderActionState.folderName.c_str()); ImGui::Separator(); - if (Button("Delete", ImNexo::VALIDATION)) { + if (Button("Delete", ImNexo::ButtonTypes::VALIDATION)) { const std::vector assets = m_folderManager.getFolderAssets(folderPath); if (!assets.empty()) { m_popupManager.openPopup("Delete Not Empty Folder Popup"); @@ -148,11 +148,11 @@ namespace nexo::editor { PopupManager::closePopup(); } else { m_folderActionState.showError = true; - m_folderActionState.errorMessage = "Failed to delete the folder (may not be empty)"; + m_folderActionState.errorMessage = "The folder may not be empty"; } } ImGui::SameLine(); - if (Button("Cancel", ImNexo::CANCEL)) { + if (Button("Cancel", ImNexo::ButtonTypes::CANCEL)) { m_folderActionState.reset(); PopupManager::closePopup(); } @@ -173,15 +173,15 @@ namespace nexo::editor { // Draw the popup for deleting a folder that contains assets ImGui::Text("Are you sure you want to delete %s? It contains assets.", m_folderActionState.folderName.c_str()); ImGui::Separator(); - if (Button("Delete", ImNexo::VALIDATION) && m_folderManager.deleteFolder(folderPath)) { + if (Button("Delete", ImNexo::ButtonTypes::VALIDATION) && m_folderManager.deleteFolder(folderPath)) { m_folderActionState.reset(); PopupManager::closePopup(); } else { m_folderActionState.showError = true; - m_folderActionState.errorMessage = "Failed to delete the folder (may not be empty)"; + m_folderActionState.errorMessage = "Failed to delete the folder"; } ImGui::SameLine(); - if (Button("Cancel", ImNexo::CANCEL)) { + if (Button("Cancel", ImNexo::ButtonTypes::CANCEL)) { m_folderActionState.reset(); PopupManager::closePopup(); } @@ -281,14 +281,14 @@ namespace nexo::editor { ImGui::Separator(); // Buttons for renaming or canceling the action - if (Button("Rename", ImNexo::VALIDATION) && handleFolderRenaming(newName)) { + if (Button("Rename", ImNexo::ButtonTypes::VALIDATION) && handleFolderRenaming(newName)) { m_folderActionState.reset(); newName = ""; PopupManager::closePopup(); isFocus = true; } ImGui::SameLine(); - if (Button("Cancel", ImNexo::CANCEL)) { + if (Button("Cancel", ImNexo::ButtonTypes::CANCEL)) { m_folderActionState.reset(); newName = ""; PopupManager::closePopup(); @@ -323,7 +323,7 @@ namespace nexo::editor { m_folderManager.getChildCount(folderPath) + m_folderManager.getFolderAssets(folderPath).size()); ImGui::Text("Size: %.2f Ko", FolderManager::getFolderSize(folderPath) / 1024.0); ImGui::Separator(); - if (Button("Close", ImNexo::CANCEL)) { + if (Button("Close", ImNexo::ButtonTypes::CANCEL)) { m_folderActionState.reset(); PopupManager::closePopup(); } diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp index 397579032..9911ecf4b 100644 --- a/editor/src/DocumentWindows/AssetManager/Init.cpp +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -13,49 +13,144 @@ /////////////////////////////////////////////////////////////////////////////// #include "AssetManagerWindow.hpp" +#include "Path.hpp" #include "assets/AssetCatalog.hpp" #include "assets/AssetImporter.hpp" #include "assets/Assets/Model/ModelImporter.hpp" #include "assets/Assets/Texture/TextureImporter.hpp" -#include "Path.hpp" namespace nexo::editor { void AssetManagerWindow::setup() { + // Internal resources { assets::AssetImporter importer; - std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf"); + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/icon_folder.png"); assets::ImporterFileInput fileInput{path}; - auto assetRef9mn = importer.importAsset(assets::AssetLocation("my_package::9mn@DefaultScene"), fileInput); + m_folderIcon = + importer.importAsset(assets::AssetLocation("icon_folder@_internal"), fileInput); } { assets::AssetImporter importer; - std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/logo_nexo.png"); + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/logoNexo.png"); assets::ImporterFileInput fileInput{path}; - auto textureRef = importer.importAsset(assets::AssetLocation("nexo_logo@Random"), fileInput); + importer.importAsset(assets::AssetLocation("nexo_logo@Random"), fileInput); } + // Models { assets::AssetImporter importer; - std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/icon_folder.png"); + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/Avocado/Avocado.gltf"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Avocado@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = + Path::resolvePathRelativeToExe("../resources/models/SmilingFace/SmilingFace.gltf"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::SmilingFace@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/Sword/scene.gltf"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Sword@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/frog.glb"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Frog@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/plane.glb"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Plane@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/bench.glb"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Bench@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/earth.glb"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Earth@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/tree.glb"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Tree@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/log.glb"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Log@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/rubixCube.glb"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::RubixCube@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/cup.glb"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Cup@Models"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/plant.glb"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::Plant@Models"), fileInput); + } + // Textures + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/grass.jpg"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::grass@Textures"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/rock.jpg"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::rock@Textures"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/wood.jpg"); + assets::ImporterFileInput fileInput{path}; + importer.importAsset(assets::AssetLocation("my_package::wood@Textures"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/dirt.jpg"); assets::ImporterFileInput fileInput{path}; - m_folderIcon = importer.importAsset(assets::AssetLocation("icon_folder@_internal"), fileInput); + importer.importAsset(assets::AssetLocation("my_package::dirt@Textures"), fileInput); } // Register for file drop events Application::getInstance().getEventManager()->registerListener(this); - m_layout.color.thumbnailBg = ImGui::GetColorU32(ImGuiCol_Button); - m_layout.color.thumbnailBgHovered = ImGui::GetColorU32(ImGuiCol_ButtonHovered); - m_layout.color.thumbnailBgSelected = ImGui::GetColorU32(ImGuiCol_Header); + m_layout.color.thumbnailBg = ImGui::GetColorU32(ImGuiCol_Button); + m_layout.color.thumbnailBgHovered = ImGui::GetColorU32(ImGuiCol_ButtonHovered); + m_layout.color.thumbnailBgSelected = ImGui::GetColorU32(ImGuiCol_Header); m_layout.color.thumbnailBgSelectedHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); m_layout.color.selectedBoxColor = ImGui::GetColorU32(ImGuiCol_TabSelectedOverline); - m_layout.color.titleBg = ImGui::GetColorU32(ImGuiCol_Header); - m_layout.color.titleBgHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); - m_layout.color.titleBgSelected = ImGui::GetColorU32(ImGuiCol_Header); + m_layout.color.titleBg = ImGui::GetColorU32(ImGuiCol_Header); + m_layout.color.titleBgHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); + m_layout.color.titleBgSelected = ImGui::GetColorU32(ImGuiCol_Header); m_layout.color.titleBgSelectedHovered = ImGui::GetColorU32(ImGuiCol_HeaderHovered); m_layout.color.titleText = ImGui::GetColorU32(ImGuiCol_Text); buildFolderStructure(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/AssetManager/RightClickUtils.cpp b/editor/src/DocumentWindows/AssetManager/RightClickUtils.cpp index ea2e45c66..a9785cd86 100644 --- a/editor/src/DocumentWindows/AssetManager/RightClickUtils.cpp +++ b/editor/src/DocumentWindows/AssetManager/RightClickUtils.cpp @@ -61,5 +61,4 @@ namespace nexo::editor { m_popupManager.openPopup("Right click on Asset"); } } - } // namespace nexo::editor diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 63fe5f00f..cc9dbd9cd 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -12,18 +12,15 @@ // /////////////////////////////////////////////////////////////////////////////// +#include #include #include "AssetManagerWindow.hpp" #include "IconsFontAwesome.h" #include "ImNexo/Elements.hpp" #include "Path.hpp" #include "assets/AssetCatalog.hpp" -#include "context/ThumbnailCache.hpp" #include "context/ActionManager.hpp" -#include "context/actions/AssetActions.hpp" -#include "ImNexo/Elements.hpp" -#include -#include +#include "context/ThumbnailCache.hpp" namespace nexo::editor { @@ -66,7 +63,7 @@ namespace nexo::editor { if (ImGui::Button("Assets")) m_currentFolder.clear(); handleAssetDrop(""); - handleFolderDrop("", m_currentFolder); + handleFolderDrop(""); ImGui::PopID(); std::string path = m_currentFolder; @@ -82,11 +79,11 @@ namespace nexo::editor { ImGui::PushID(("breadcrumb_" + crumb).c_str()); if (i == lastIndex) ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", crumb.c_str()); - else if (ImNexo::Button(crumb + "##" + std::to_string(i))) + else if (ImNexo::Button(std::format("%d##%d", crumb, i))) m_currentFolder = fullPath; handleAssetDrop(fullPath); - handleFolderDrop(fullPath, crumb.empty() ? crumb : "Assets"); + handleFolderDrop(fullPath); ImGui::PopID(); } } @@ -95,7 +92,7 @@ namespace nexo::editor { { ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, - ImGuiWindowFlags_MenuBar); + ImGuiWindowFlags_NoTitleBar); beginRender(NEXO_WND_USTRID_ASSET_MANAGER); @@ -141,7 +138,7 @@ namespace nexo::editor { // Asset popups if (m_popupManager.showPopupModal("Rename Asset Popup")) renameAssetPopup(); if (m_popupManager.showPopupModal("Delete Asset Popup")) deleteAssetPopup(); - if (m_popupManager.showPopupModal("Delete Not Empty Asset Popup")) deleteUsedAssetPopup(); + if (m_popupManager.showPopupModal("Delete Used Asset Popup")) deleteUsedAssetPopup(); if (m_popupManager.showPopupModal("Details Asset Popup")) assetDetailsPopup(); } diff --git a/editor/src/DocumentWindows/AssetManager/Update.cpp b/editor/src/DocumentWindows/AssetManager/Update.cpp index e519ee9d1..20f745219 100644 --- a/editor/src/DocumentWindows/AssetManager/Update.cpp +++ b/editor/src/DocumentWindows/AssetManager/Update.cpp @@ -27,4 +27,4 @@ namespace nexo::editor { // Nothing to do for now } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/AssetManager/Utils.cpp b/editor/src/DocumentWindows/AssetManager/Utils.cpp index 942703419..a2648b820 100644 --- a/editor/src/DocumentWindows/AssetManager/Utils.cpp +++ b/editor/src/DocumentWindows/AssetManager/Utils.cpp @@ -16,7 +16,7 @@ namespace nexo::editor { - template + template void AssetManagerWindow::drawErrorMessageInPopup(T& actionState) { if (!actionState.showError) return; @@ -47,10 +47,10 @@ namespace nexo::editor { * @param texture The asset reference to the texture. * @return ImTextureID The texture ID or 0 if the texture is invalid. */ - ImTextureID AssetManagerWindow::getIconTexture(const assets::AssetRef &texture) + ImTextureID AssetManagerWindow::getIconTexture(const assets::AssetRef& texture) { if (const auto texRef = texture.lock()) { - const auto &texData = texRef->getData(); + const auto& texData = texRef->getData(); if (texData && texData->texture) { return texData->texture->getId(); } diff --git a/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp index 4df72cace..4aa05f8b0 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp +++ b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp @@ -14,32 +14,68 @@ #pragma once +#include #include "ADocumentWindow.hpp" #include "Editor.hpp" -#include namespace nexo::editor { + /** + * @brief Converts a loguru::Verbosity level to its string representation. + * + * This function takes a loguru::Verbosity level and returns a human-readable string + * that represents the verbosity level for display purposes. + * + * @param level The loguru::Verbosity level to convert. + * @return A string representing the verbosity level. + */ std::string verbosityToString(loguru::Verbosity level); + + /** + * @brief Converts a custom LogLevel to a loguru::Verbosity level. + * + * Maps the provided LogLevel to the corresponding loguru::Verbosity level for consistent logging. + * + * @param level The custom LogLevel to convert. + * @return The equivalent loguru::Verbosity level. + */ loguru::Verbosity nexoLevelToLoguruLevel(LogLevel level); + + /** + * @brief Retrieves the color associated with a specific log verbosity level. + * + * This function returns an ImVec4 color that corresponds to the given loguru::Verbosity level, + * allowing for visual differentiation of log messages based on their severity. + * + * @param level The loguru::Verbosity level for which to get the color. + * @return An ImVec4 representing the color associated with the verbosity level. + */ ImVec4 getVerbosityColor(loguru::Verbosity level); + + /** + * @brief Generates a log file path based on the current date and time. + * + * This function constructs a file path for storing log files, incorporating the current + * date and time to ensure uniqueness and organization. + * + * @return A string representing the generated log file path. + */ std::string generateLogFilePath(); - constexpr auto LOGURU_CALLBACK_NAME = "GEE"; + constexpr auto LOGURU_CALLBACK_NAME = "GEE"; - /** + /** * @brief Structure representing a formatted log message. * * Contains all necessary information for displaying a log message in the console, * including its verbosity level, the message text, and an optional prefix. */ struct LogMessage { - loguru::Verbosity verbosity; ///< The verbosity level of the log message - std::string message; ///< The content of the log message - std::string prefix; ///< Optional prefix for the log message + loguru::Verbosity verbosity{}; ///< The verbosity level of the log message + std::string message; ///< The content of the log message + std::string prefix; ///< Optional prefix for the log message }; - /** * @brief Console window for displaying and managing application logs. * @@ -48,204 +84,197 @@ namespace nexo::editor { * with the loguru logging system to display real-time log messages. */ class ConsoleWindow final : public ADocumentWindow { - public: - /** - * @brief Constructs and initializes a ConsoleWindow. - * - * This constructor sets up the console's logging functionality by registering a loguru callback via - * loguru::add_callback to route log messages to the console (using the static loguruCallback) and by - * establishing an engine log callback that maps custom LogLevel messages to loguru verbosity levels - * using nexoLevelToLoguruLevel before logging them with VLOG_F. - * - * @param windowName The name of the window - * @param registry The window registry used to register this console window. - */ - explicit ConsoleWindow(const std::string &windowName, WindowRegistry ®istry); - - ConsoleWindow(const ConsoleWindow&) = delete; - ConsoleWindow& operator=(const ConsoleWindow&) = delete; - - /** - * @brief Destructor that cleans up the ConsoleWindow. - * - * Removes the registered loguru callback identified by LOGURU_CALLBACK_NAME to prevent further logging after the window is destroyed. - */ - ~ConsoleWindow() override - { - loguru::remove_callback(LOGURU_CALLBACK_NAME); - }; - - // No-op method in this class - void setup() override; - - /** - * @brief Clears all stored log entries during shutdown. - * - * This method resets the console's log by invoking clearLog(), ensuring that all previous - * log entries are removed as part of the shutdown process. - */ - void shutdown() override; - - /** - * @brief Renders the console window interface. - * - * This method initializes and displays the console window using ImGui. It sets a predefined window size and - * creates a scrolling region to display log messages filtered by selected verbosity levels. When the console is - * opened for the first time, it performs an initial docking setup. The function also adjusts log padding for proper - * alignment and automatically scrolls to the bottom if new messages have been added. - * - * An input field is provided for entering commands, which are executed upon pressing Enter, with the input buffer - * cleared afterward. Additionally, a popup for adjusting verbosity settings is available, accessible via a button. - */ - void show() override; - - // No-op method in this class - void update() override; - - /** - * @brief Executes a command entered in the console. - * - * Processes the given command line, adds it to the command history, - * and displays it in the log. - * - * @param commandLine The command text to execute. - * @note not implemented yet - */ - void executeCommand(const char* commandLine); - - private: - char m_inputBuf[512] = {}; - std::vector m_commands; // History of executed commands. - - std::string m_logFilePath; - bool m_exportLog = true; - - bool m_scrollToBottom = true; - - - std::set m_selectedVerbosityLevels = { - loguru::Verbosity_FATAL, - loguru::Verbosity_ERROR, - loguru::Verbosity_WARNING, - loguru::Verbosity_INFO, - loguru::Verbosity_1, - }; - - float m_logPadding = 0.0f; - std::vector m_logs; - size_t m_maxLogCapacity = 200; - std::vector m_bufferLogsToExport; - size_t m_maxBufferLogToExportCapacity = 20; - - - /** - * @brief Clears all log entries and display items. - * - * Removes all log messages from the internal storage and resets the list of display items. - */ - void clearLog(); - - /** - * @brief Appends a log message to the console's log collection. - * - * This method adds the provided log message to the internal container, ensuring it is available - * for display in the console window. - * - * @param message The log message to append. - */ - void addLog(const LogMessage& message); - - /** - * @brief Adds a formatted log message to the console. - * - * Creates a log message using printf-style formatting and adds it to the log collection. - * - * @tparam Args Variadic template for format arguments - * @param fmt Format string similar to printf - * @param args Arguments for the format string - */ - template - void addLog(std::format_string fmt, Args&&... args) - { - try { - std::string formattedString = std::format(fmt, std::forward(args)...); - - LogMessage newMessage; - newMessage.verbosity = loguru::Verbosity_1; - newMessage.message = formattedString; - newMessage.prefix = ""; - m_logs.push_back(newMessage); - } catch (const std::exception &e) { - LogMessage newMessage; - newMessage.verbosity = loguru::Verbosity_ERROR; - - char errorBuffer[1024]; - - // format up to sizeof(errorBuffer)-1 characters - auto result = std::format_to_n( - std::begin(errorBuffer), - std::size(errorBuffer) - 1, - "Error formatting log message: {}", - e.what() - ); - - // null-terminate - *result.out = '\0'; - - newMessage.message = std::string(errorBuffer); - newMessage.prefix = ""; - m_logs.push_back(newMessage); - } - - m_scrollToBottom = true; - } - - /** - * @brief Displays a single log entry in the console UI. - * - * Renders a log message with appropriate styling based on its verbosity level. - * - * @param verbosity The verbosity level that determines the message's color - * @param msg The message text to display - */ - void displayLog(loguru::Verbosity verbosity, const std::string &msg) const; - - /** - * @brief Exports buffered logs to the log file. - * - * Writes any logs in the export buffer to the configured log file, - * helping to prevent memory buildup from excessive logging. - */ - void exportLogsBuffered() const; - - /** - * @brief Displays the popup for configuring verbosity settings. - * - * Shows a popup menu that allows the user to select which verbosity levels - * to display in the console and configure other log-related settings. - */ - void showVerbositySettingsPopup(); - - /** - * @brief Updates the horizontal padding for log entries. - * - * Iterates over the stored log messages to compute the maximum width of the verbosity tag text for - * messages that are currently visible (filtered by selected verbosity levels). The computed maximum - * width is then increased by the spacing defined in the ImGui style to ensure proper alignment in the UI. - */ - void calcLogPadding(); - - /** - * @brief Processes a loguru message and adds it to the console log. - * - * Converts a loguru message to the internal log format and appends it to the ConsoleWindow's log list. - * The userData pointer is cast to a ConsoleWindow instance, which is then used to record the message details, - * including verbosity, message content, and prefix. - * - * @param userData Pointer to the ConsoleWindow instance. - * @param message The loguru message carrying the log details. - */ - static void loguruCallback(void *userData, const loguru::Message& message); + public: + /** + * @brief Constructs and initializes a ConsoleWindow. + * + * This constructor sets up the console's logging functionality by registering a loguru callback via + * loguru::add_callback to route log messages to the console (using the static loguruCallback) and by + * establishing an engine log callback that maps custom LogLevel messages to loguru verbosity levels + * using nexoLevelToLoguruLevel before logging them with VLOG_F. + * + * @param windowName The name of the window + * @param registry The window registry used to register this console window. + */ + explicit ConsoleWindow(const std::string &windowName, WindowRegistry ®istry); + + ConsoleWindow(const ConsoleWindow &) = delete; + ConsoleWindow &operator=(const ConsoleWindow &) = delete; + + /** + * @brief Destructor that cleans up the ConsoleWindow. + * + * Removes the registered loguru callback identified by LOGURU_CALLBACK_NAME to prevent further logging after + * the window is destroyed. + */ + ~ConsoleWindow() override + { + loguru::remove_callback(LOGURU_CALLBACK_NAME); + }; + + // No-op method in this class + void setup() override; + + /** + * @brief Clears all stored log entries during shutdown. + * + * This method resets the console's log by invoking clearLog(), ensuring that all previous + * log entries are removed as part of the shutdown process. + */ + void shutdown() override; + + /** + * @brief Renders the console window interface. + * + * This method initializes and displays the console window using ImGui. It sets a predefined window size and + * creates a scrolling region to display log messages filtered by selected verbosity levels. When the console is + * opened for the first time, it performs an initial docking setup. The function also adjusts log padding for + * proper alignment and automatically scrolls to the bottom if new messages have been added. + * + * An input field is provided for entering commands, which are executed upon pressing Enter, with the input + * buffer cleared afterward. Additionally, a popup for adjusting verbosity settings is available, accessible via + * a button. + */ + void show() override; + + // No-op method in this class + void update() override; + + /** + * @brief Executes a command that is entered in the console. + * + * Processes the given command line, adds it to the command history, + * and displays it in the log. + * + * @param commandLine The command text to execute. + * @note not implemented yet + */ + void executeCommand(const char *commandLine); + + private: + char m_inputBuf[512] = {}; + std::vector m_commands; // History of executed commands. + + std::string m_logFilePath; + bool m_exportLog = true; + + bool m_scrollToBottom = true; + + std::set m_selectedVerbosityLevels = { + loguru::Verbosity_FATAL, loguru::Verbosity_ERROR, loguru::Verbosity_WARNING, + loguru::Verbosity_INFO, loguru::Verbosity_1, + }; + + float m_logPadding = 0.0f; + std::vector m_logs; + size_t m_maxLogCapacity = 200; + std::vector m_bufferLogsToExport; + size_t m_maxBufferLogToExportCapacity = 20; + + /** + * @brief Clears all log entries and display items. + * + * Removes all log messages from the internal storage and resets the list of display items. + */ + void clearLog(); + + /** + * @brief Appends a log message to the console's log collection. + * + * This method adds the provided log message to the internal container, ensuring it is available + * for display in the console window. + * + * @param message The log message to append. + */ + void addLog(const LogMessage &message); + + /** + * @brief Adds a formatted log message to the console. + * + * Creates a log message using printf-style formatting and adds it to the log collection. + * + * @tparam Args Variadic template for format arguments + * @param fmt Format string similar to printf + * @param args Arguments for the format string + */ + template + void addLog(std::format_string fmt, Args &&...args) + { + try { + const std::string formattedString = std::format(fmt, std::forward(args)...); + + LogMessage newMessage; + newMessage.verbosity = loguru::Verbosity_1; + newMessage.message = formattedString; + newMessage.prefix = ""; + m_logs.push_back(newMessage); + } catch (const std::exception &e) { + LogMessage newMessage; + newMessage.verbosity = loguru::Verbosity_ERROR; + + char errorBuffer[1024]; + + // format up to sizeof(errorBuffer)-1 characters + auto result = std::format_to_n(std::begin(errorBuffer), std::size(errorBuffer) - 1, + "Error formatting log message: {}", e.what()); + + // null-terminate + *result.out = '\0'; + + newMessage.message = std::string(errorBuffer); + newMessage.prefix = ""; + m_logs.push_back(newMessage); + } + + m_scrollToBottom = true; + } + + /** + * @brief Displays a single log entry in the console UI. + * + * Renders a log message with appropriate styling based on its verbosity level. + * + * @param verbosity The verbosity level that determines the message's color + * @param msg The message text to display + */ + void displayLog(loguru::Verbosity verbosity, const std::string &msg) const; + + /** + * @brief Exports buffered logs to the log file. + * + * Writes any logs in the export buffer to the configured log file, + * helping to prevent memory buildup from excessive logging. + */ + void exportLogsBuffered() const; + + /** + * @brief Displays the popup for configuring verbosity settings. + * + * Shows a popup menu that allows the user to select which verbosity levels + * to display in the console and configure other log-related settings. + */ + void showVerbositySettingsPopup(); + + /** + * @brief Updates the horizontal padding for log entries. + * + * Iterates over the stored log messages to compute the maximum width of the verbosity tag text for + * messages that are currently visible (filtered by selected verbosity levels). The computed maximum + * width is then increased by the spacing defined in the ImGui style to ensure proper alignment in the UI. + */ + void calcLogPadding(); + + /** + * @brief Processes a loguru message and adds it to the console log. + * + * Converts a loguru message to the internal log format and appends it to the ConsoleWindow's log list. + * The userData pointer is cast to a ConsoleWindow instance, which is then used to record the message details, + * including verbosity, message content, and prefix. + * + * @param userData Pointer to the ConsoleWindow instance. + * @param message The loguru message carrying the log details. + */ + static void loguruCallback(void *userData, const loguru::Message &message); }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/ConsoleWindow/Init.cpp b/editor/src/DocumentWindows/ConsoleWindow/Init.cpp index 2f8f88af2..67831f3a4 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Init.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Init.cpp @@ -16,52 +16,50 @@ #include "Path.hpp" namespace nexo::editor { - void ConsoleWindow::loguruCallback([[maybe_unused]] void *userData, - const loguru::Message &message) + void ConsoleWindow::loguruCallback([[maybe_unused]] void *userData, const loguru::Message &message) { const auto console = static_cast(userData); LogMessage newMessage; newMessage.verbosity = message.verbosity; - newMessage.message = message.message; - newMessage.prefix = message.prefix; + newMessage.message = message.message; + newMessage.prefix = message.prefix; console->addLog(newMessage); } - - ConsoleWindow::ConsoleWindow(const std::string &windowName, WindowRegistry ®istry) : ADocumentWindow(windowName, registry) + ConsoleWindow::ConsoleWindow(const std::string &windowName, WindowRegistry ®istry) + : ADocumentWindow(windowName, registry) { - loguru::add_callback(LOGURU_CALLBACK_NAME, &ConsoleWindow::loguruCallback, - this, loguru::Verbosity_MAX); + loguru::add_callback(LOGURU_CALLBACK_NAME, &ConsoleWindow::loguruCallback, this, loguru::Verbosity_MAX); - auto engineLogCallback = [](const LogLevel level, const std::source_location& loc, const std::string &message) { - const auto loguruLevel = nexoLevelToLoguruLevel(level); - if (loguruLevel > loguru::current_verbosity_cutoff()) - return; - loguru::log(loguruLevel, loc.file_name(), loc.line(), "%s", message.c_str()); - }; - Logger::setCallback(engineLogCallback); - try { - const auto logFilePath = generateLogFilePath(); - if (logFilePath.empty()) { - throw std::runtime_error("Generated log file path is empty."); - } + auto engineLogCallback = [](const LogLevel level, const std::source_location &loc, const std::string &message) { + const auto loguruLevel = nexoLevelToLoguruLevel(level); + if (loguruLevel > loguru::current_verbosity_cutoff()) return; + loguru::log(loguruLevel, loc.file_name(), loc.line(), "%s", message.c_str()); + }; + Logger::setCallback(engineLogCallback); + try { + const auto logFilePath = generateLogFilePath(); + if (logFilePath.empty()) { + THROW_EXCEPTION(LogFilePathEmptyException); + } - const auto resolvedPath = Path::resolvePathRelativeToExe(logFilePath); - if (resolvedPath.empty()) { - throw std::runtime_error("Resolved log file path is empty."); - } + const auto resolvedPath = Path::resolvePathRelativeToExe(logFilePath); + if (resolvedPath.empty()) { + THROW_EXCEPTION(LogResolvedPathEmptyException); + } - m_logFilePath = resolvedPath.string(); - } catch (const std::exception &e) { - LOG(NEXO_ERROR, "Error setting up log file: {}", e.what()); - m_logFilePath.clear(); - } - m_logs.reserve(m_maxLogCapacity); - m_bufferLogsToExport.reserve(m_maxBufferLogToExportCapacity); - }; + m_logFilePath = resolvedPath.string(); + } catch (const std::system_error &e) { + LOG(NEXO_ERROR, "Error setting up log file: {}", e.what()); + m_logFilePath.clear(); + } + m_logs.reserve(m_maxLogCapacity); + m_bufferLogsToExport.reserve(m_maxBufferLogToExportCapacity); + } void ConsoleWindow::setup() { - //All the setup is made in the constructor because the rest of the editor needs the log setup before setting up the windows + // All the setup is made in the constructor because the rest of the editor needs the log setup before setting up + // the windows } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/ConsoleWindow/Log.cpp b/editor/src/DocumentWindows/ConsoleWindow/Log.cpp index e2af09fce..a515bcd4f 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Log.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Log.cpp @@ -12,12 +12,12 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "ConsoleWindow.hpp" #include +#include "ConsoleWindow.hpp" namespace nexo::editor { - void ConsoleWindow::addLog(const LogMessage &message) + void ConsoleWindow::addLog(const LogMessage& message) { if (m_logs.size() >= m_maxLogCapacity) { m_bufferLogsToExport.push_back(message); @@ -38,22 +38,19 @@ namespace nexo::editor { if (m_exportLog) { std::ofstream logFile(m_logFilePath, std::ios::app); for (const auto& log : m_logs) { - logFile << verbosityToString(log.verbosity) << " " - << log.message << std::endl; + logFile << verbosityToString(log.verbosity) << " " << log.message << std::endl; } } - m_logs.clear(); + m_logs.clear(); } void ConsoleWindow::exportLogsBuffered() const { - if (!m_exportLog) - return; + if (!m_exportLog) return; std::ofstream logFile(m_logFilePath, std::ios::app); for (const auto& log : m_bufferLogsToExport) { - logFile << verbosityToString(log.verbosity) << " " - << log.message << std::endl; + logFile << verbosityToString(log.verbosity) << " " << log.message << std::endl; } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/ConsoleWindow/Show.cpp b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp index 3d31e6d3a..c90628871 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Show.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp @@ -15,8 +15,8 @@ #include "ConsoleWindow.hpp" #include "IconsFontAwesome.h" #include "ImNexo/Elements.hpp" -#include "utils/FileSystem.hpp" #include "Path.hpp" +#include "utils/FileSystem.hpp" namespace nexo::editor { @@ -28,27 +28,21 @@ namespace nexo::editor { const struct { loguru::Verbosity level; const char *name; - } levels[] = { - {loguru::Verbosity_FATAL, "FATAL"}, - {loguru::Verbosity_ERROR, "ERROR"}, - {loguru::Verbosity_WARNING, "WARNING"}, - {loguru::Verbosity_INFO, "INFO"}, - {loguru::Verbosity_1, "USER"}, - {loguru::Verbosity_2, "DEBUG"}, - {loguru::Verbosity_3, "DEV"} - }; - - for (const auto &[level, name]: levels) - { + } levels[] = {{loguru::Verbosity_FATAL, "FATAL"}, + {loguru::Verbosity_ERROR, "ERROR"}, + {loguru::Verbosity_WARNING, "WARNING"}, + {loguru::Verbosity_INFO, "INFO"}, + {loguru::Verbosity_1, "USER"}, + {loguru::Verbosity_2, "DEBUG"}, + {loguru::Verbosity_3, "DEV"}}; + + for (const auto &[level, name] : levels) { bool selected = (m_selectedVerbosityLevels.contains(level)); - if (ImGui::Checkbox(name, &selected)) - { - if (selected) - { + if (ImGui::Checkbox(name, &selected)) { + if (selected) { m_selectedVerbosityLevels.insert(level); calcLogPadding(); - } else - { + } else { m_selectedVerbosityLevels.erase(level); calcLogPadding(); } @@ -57,8 +51,7 @@ namespace nexo::editor { ImGui::Separator(); ImGui::Checkbox("File logging", &m_exportLog); - if (ImNexo::Button("Open log folder")) - utils::openFolder(Path::resolvePathRelativeToExe("../logs").string()); + if (ImNexo::Button("Open log folder")) utils::openFolder(Path::resolvePathRelativeToExe("../logs").string()); ImGui::EndPopup(); } @@ -76,8 +69,7 @@ namespace nexo::editor { for (const auto &selectedLevel : m_selectedVerbosityLevels) { const std::string tag = verbosityToString(selectedLevel); const ImVec2 textSize = ImGui::CalcTextSize(tag.c_str()); - if (textSize.x > m_logPadding) - m_logPadding = textSize.x; + if (textSize.x > m_logPadding) m_logPadding = textSize.x; } m_logPadding += ImGui::GetStyle().ItemSpacing.x; } @@ -108,40 +100,33 @@ namespace nexo::editor { const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerHeight), false, ImGuiWindowFlags_HorizontalScrollbar); - if (m_logPadding == 0.0f) - calcLogPadding(); + if (m_logPadding == 0.0f) calcLogPadding(); auto id = 0; - for (const auto &[verbosity, message, prefix]: m_logs) - { - if (!m_selectedVerbosityLevels.contains(verbosity)) - continue; + for (const auto &[verbosity, message, prefix] : m_logs) { + if (!m_selectedVerbosityLevels.contains(verbosity)) continue; ImGui::PushID(id++); displayLog(verbosity, message); ImGui::PopID(); } - if (m_scrollToBottom) - ImGui::SetScrollHereY(1.0f); + if (m_scrollToBottom) ImGui::SetScrollHereY(1.0f); m_scrollToBottom = false; ImGui::EndChild(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60.0f); - if (ImGui::InputText("Input", m_inputBuf, IM_ARRAYSIZE(m_inputBuf), ImGuiInputTextFlags_EnterReturnsTrue)) - { + if (ImGui::InputText("Input", m_inputBuf, IM_ARRAYSIZE(m_inputBuf), ImGuiInputTextFlags_EnterReturnsTrue)) { executeCommand(m_inputBuf); std::memset(m_inputBuf, '\0', sizeof(m_inputBuf)); } ImGui::SameLine(); - if (ImNexo::Button("...")) - ImGui::OpenPopup("VerbositySettings"); + if (ImNexo::Button("...")) ImGui::OpenPopup("VerbositySettings"); - if (ImGui::BeginPopup("VerbositySettings")) - showVerbositySettingsPopup(); + if (ImGui::BeginPopup("VerbositySettings")) showVerbositySettingsPopup(); ImGui::End(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp b/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp index a2bb7ec7f..abc5a84bc 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp @@ -18,7 +18,7 @@ namespace nexo::editor { void ConsoleWindow::shutdown() { - clearLog(); + clearLog(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/ConsoleWindow/Update.cpp b/editor/src/DocumentWindows/ConsoleWindow/Update.cpp index 25c0711db..2535d8557 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Update.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Update.cpp @@ -17,6 +17,6 @@ namespace nexo::editor { void ConsoleWindow::update() { - //No need to update anything + // No need to update anything } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp index 2b9294cfc..b39d9cabe 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp @@ -27,17 +27,25 @@ namespace nexo::editor { */ std::string verbosityToString(const loguru::Verbosity level) { - switch (level) - { - case loguru::Verbosity_FATAL: return "[FATAL]"; - case loguru::Verbosity_ERROR: return "[ERROR]"; - case loguru::Verbosity_WARNING: return "[WARNING]"; - case loguru::Verbosity_INFO: return "[INFO]"; - case loguru::Verbosity_INVALID: return "[INVALID]"; - case loguru::Verbosity_1: return "[USER]"; - case loguru::Verbosity_2: return "[DEBUG]"; - case loguru::Verbosity_3: return "[DEV]"; - default: return "[UNKNOWN]"; + switch (level) { + case loguru::Verbosity_FATAL: + return "[FATAL]"; + case loguru::Verbosity_ERROR: + return "[ERROR]"; + case loguru::Verbosity_WARNING: + return "[WARNING]"; + case loguru::Verbosity_INFO: + return "[INFO]"; + case loguru::Verbosity_INVALID: + return "[INVALID]"; + case loguru::Verbosity_1: + return "[USER]"; + case loguru::Verbosity_2: + return "[DEBUG]"; + case loguru::Verbosity_3: + return "[DEV]"; + default: + return "[UNKNOWN]"; } } @@ -52,16 +60,23 @@ namespace nexo::editor { */ loguru::Verbosity nexoLevelToLoguruLevel(const LogLevel level) { - switch (level) - { - case LogLevel::FATAL: return loguru::Verbosity_FATAL; - case LogLevel::ERR: return loguru::Verbosity_ERROR; - case LogLevel::WARN: return loguru::Verbosity_WARNING; - case LogLevel::INFO: return loguru::Verbosity_INFO; - case LogLevel::USER: return loguru::Verbosity_1; - case LogLevel::DEBUG: return loguru::Verbosity_2; - case LogLevel::DEV: return loguru::Verbosity_3; - default: return loguru::Verbosity_INVALID; + switch (level) { + case LogLevel::FATAL: + return loguru::Verbosity_FATAL; + case LogLevel::ERR: + return loguru::Verbosity_ERROR; + case LogLevel::WARN: + return loguru::Verbosity_WARNING; + case LogLevel::INFO: + return loguru::Verbosity_INFO; + case LogLevel::USER: + return loguru::Verbosity_1; + case LogLevel::DEBUG: + return loguru::Verbosity_2; + case LogLevel::DEV: + return loguru::Verbosity_3; + default: + return loguru::Verbosity_INVALID; } } @@ -82,36 +97,51 @@ namespace nexo::editor { { ImVec4 color; - switch (level) - { + switch (level) { case loguru::Verbosity_FATAL: // Red - case loguru::Verbosity_ERROR: color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + case loguru::Verbosity_ERROR: + color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); break; // Red - case loguru::Verbosity_WARNING: color = ImVec4(1.0f, 1.0f, 0.0f, 1.0f); + case loguru::Verbosity_WARNING: + color = ImVec4(1.0f, 1.0f, 0.0f, 1.0f); break; // Yellow - case loguru::Verbosity_INFO: color = ImVec4(0.0f, 0.5f, 1.0f, 1.0f); + case loguru::Verbosity_INFO: + color = ImVec4(0.0f, 0.5f, 1.0f, 1.0f); break; // Blue - case loguru::Verbosity_1: color = ImVec4(0.09f, 0.67f, 0.14f, 1.0f); // User - break; // Green - case loguru::Verbosity_2: color = ImVec4(0.898f, 0.0f, 1.0f, 1.0f); // Debug - break; // Pink - case loguru::Verbosity_3: color = ImVec4(0.388f, 0.055f, 0.851f, 1.0f); // Dev - break; // Purple - default: color = ImVec4(1, 1, 1, 1); // White + case loguru::Verbosity_1: + color = ImVec4(0.09f, 0.67f, 0.14f, 1.0f); // User + break; // Green + case loguru::Verbosity_2: + color = ImVec4(0.898f, 0.0f, 1.0f, 1.0f); // Debug + break; // Pink + case loguru::Verbosity_3: + color = ImVec4(0.388f, 0.055f, 0.851f, 1.0f); // Dev + break; // Purple + default: + color = ImVec4(1, 1, 1, 1); // White } return color; } + /** + * @brief Generates a log file path based on the current date and time. + * + * This function constructs a file path for storing log files, incorporating the current + * date and time to ensure uniqueness and organization. The generated path follows the format: + * "../logs/NEXO-YYYYmmdd_HHMMSS.log", where "YYYYmmdd_HHMMSS" represents the timestamp. + * + * @return A string representing the generated log file path. + */ std::string generateLogFilePath() { using namespace std::chrono; // Truncate to seconds precision const auto now = floor(system_clock::now()); - const zoned_time local_zoned{ current_zone(), now }; + const zoned_time local_zoned{current_zone(), now}; auto local_time = local_zoned.get_local_time(); - std::string ts = std::format("{:%Y%m%d_%H%M%S}", local_time); + std::string ts = std::format("{:%Y%m%d_%H%M%S}", local_time); return std::format("../logs/NEXO-{}.log", ts); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp index 3a9183dab..2d165a21a 100644 --- a/editor/src/DocumentWindows/EditorScene/DragDrop.cpp +++ b/editor/src/DocumentWindows/EditorScene/DragDrop.cpp @@ -23,20 +23,16 @@ namespace nexo::editor { - void EditorScene::handleDropModel(const AssetDragDropPayload &payload) const + void EditorScene::handleDropModel(const AssetDragDropPayload& payload) const { const auto modelRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (!modelRef) - return; - if (const auto model = modelRef.as(); model) - { + if (!modelRef) return; + if (const auto model = modelRef.as(); model) { auto& sceneManager = Application::getInstance().getSceneManager(); // Create entity with the model - ecs::Entity newEntity = EntityFactory3D::createModel( - model, - {0.0f, 0.0f, 0.0f}, // position - {1.0f, 1.0f, 1.0f}, // scale - {0.0f, 0.0f, 0.0f} // rotation + ecs::Entity newEntity = EntityFactory3D::createModel(model, {0.0f, 0.0f, 0.0f}, // position + {1.0f, 1.0f, 1.0f}, // scale + {0.0f, 0.0f, 0.0f} // rotation ); // Add to the scene @@ -48,13 +44,11 @@ namespace nexo::editor { } } - void EditorScene::handleDropTexture(const AssetDragDropPayload &payload) const + void EditorScene::handleDropTexture(const AssetDragDropPayload& payload) const { const auto textureRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (!textureRef) - return; - if (const auto texture = textureRef.as(); texture) - { + if (!textureRef) return; + if (const auto texture = textureRef.as(); texture) { auto [mx, my] = ImGui::GetMousePos(); mx -= m_viewportBounds[0].x; my -= m_viewportBounds[0].y; @@ -63,21 +57,18 @@ namespace nexo::editor { my = m_contentSize.y - my; // Check if mouse is inside viewport - if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) - return; + if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) return; const int entityId = sampleEntityTexture(mx, my); if (entityId == -1) { auto& sceneManager = Application::getInstance().getSceneManager(); components::Material material; material.albedoTexture = texture; - material.albedoColor = glm::vec4(1.0f); // White to show texture colors + material.albedoColor = glm::vec4(1.0f); // White to show texture colors // Create billboard entity - ecs::Entity newEntity = EntityFactory3D::createBillboard( - {0.0f, 0.0f, 0.0f}, // position - {1.0f, 1.0f, 1.0f}, // size - material - ); + ecs::Entity newEntity = EntityFactory3D::createBillboard({0.0f, 0.0f, 0.0f}, // position + {1.0f, 1.0f, 1.0f}, // size + material); // Add to the scene auto& scene = sceneManager.getScene(m_sceneId); @@ -88,23 +79,20 @@ namespace nexo::editor { ActionManager::get().recordAction(std::move(action)); return; } - const auto matComponent = Application::m_coordinator->tryGetComponent(entityId); - if (!matComponent) - return; + const auto matComponent = + Application::m_coordinator->tryGetComponent(entityId); + if (!matComponent) return; const auto material = matComponent->get().material.lock(); - if (!material) - return; + if (!material) return; material->getData()->albedoTexture = texture; } } - void EditorScene::handleDropMaterial(const AssetDragDropPayload &payload) const + void EditorScene::handleDropMaterial(const AssetDragDropPayload& payload) const { const auto materialRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (!materialRef) - return; - if (const auto material = materialRef.as(); material) - { + if (!materialRef) return; + if (const auto material = materialRef.as(); material) { auto [mx, my] = ImGui::GetMousePos(); mx -= m_viewportBounds[0].x; my -= m_viewportBounds[0].y; @@ -113,25 +101,22 @@ namespace nexo::editor { my = m_contentSize.y - my; // Check if mouse is inside viewport - if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) - return; + if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) return; const int entityId = sampleEntityTexture(mx, my); - if (entityId == -1) - return; - const auto matComponent = Application::m_coordinator->tryGetComponent(entityId); - if (!matComponent) - return; + if (entityId == -1) return; + const auto matComponent = + Application::m_coordinator->tryGetComponent(entityId); + if (!matComponent) return; matComponent->get().material = material; } } void EditorScene::handleDropTarget() { - if (ImGui::BeginDragDropTarget()) - { + if (ImGui::BeginDragDropTarget()) { // Handle drops from asset manager - if (const ImGuiPayload* assetPayload = ImGui::AcceptDragDropPayload("ASSET_DRAG", ImGuiDragDropFlags_AcceptBeforeDelivery)) - { + if (const ImGuiPayload* assetPayload = + ImGui::AcceptDragDropPayload("ASSET_DRAG", ImGuiDragDropFlags_AcceptBeforeDelivery)) { IM_ASSERT(assetPayload->DataSize == sizeof(AssetDragDropPayload)); auto [mx, my] = ImGui::GetMousePos(); mx -= m_viewportBounds[0].x; @@ -141,39 +126,35 @@ namespace nexo::editor { my = m_contentSize.y - my; // Check if mouse is inside viewport - if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) - return; + if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) return; const int entityId = sampleEntityTexture(mx, my); - if (entityId != -1 && static_cast(entityId) != m_entityHovered) - { + if (entityId != -1 && static_cast(entityId) != m_entityHovered) { m_entityHovered = static_cast(entityId); Application::m_coordinator->addComponent(m_entityHovered, components::SelectedTag{}); } - if (entityId == -1 && m_entityHovered != ecs::INVALID_ENTITY) - { + if (entityId == -1 && m_entityHovered != ecs::INVALID_ENTITY) { Application::m_coordinator->removeComponent(m_entityHovered); m_entityHovered = ecs::INVALID_ENTITY; } - if (!assetPayload->IsDelivery()) - { + if (!assetPayload->IsDelivery()) { return; } if (m_entityHovered != ecs::INVALID_ENTITY) Application::m_coordinator->removeComponent(m_entityHovered); - m_entityHovered = ecs::INVALID_ENTITY; + m_entityHovered = ecs::INVALID_ENTITY; const auto& payload = *static_cast(assetPayload->Data); - switch(payload.type) - { - case assets::AssetType::MODEL: + using enum assets::AssetType; + switch (payload.type) { + case MODEL: handleDropModel(payload); - break; - case assets::AssetType::TEXTURE: + break; + case TEXTURE: handleDropTexture(payload); - break; - case assets::AssetType::MATERIAL: + break; + case MATERIAL: handleDropMaterial(payload); - break; + break; default: break; } @@ -181,4 +162,4 @@ namespace nexo::editor { ImGui::EndDragDropTarget(); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp index 9af23fc1e..7dae785a5 100644 --- a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -14,21 +14,19 @@ #pragma once #include -#include +#include +#include "../PopupManager.hpp" #include "ADocumentWindow.hpp" #include "Definitions.hpp" -#include "inputs/WindowState.hpp" -#include "core/scene/SceneManager.hpp" -#include "../PopupManager.hpp" -#include "ImNexo/Widgets.hpp" #include "DocumentWindows/AssetManager/AssetManagerWindow.hpp" +#include "ImNexo/Widgets.hpp" +#include "core/scene/SceneManager.hpp" +#include "inputs/WindowState.hpp" -namespace nexo::editor -{ - class EditorScene final : public ADocumentWindow - { - public: +namespace nexo::editor { + class EditorScene final : public ADocumentWindow { + public: using ADocumentWindow::ADocumentWindow; /** @@ -41,7 +39,12 @@ namespace nexo::editor */ void setup() override; - // No-op method in this class + /** + * @brief Shuts down the main scene. + * + * This method is a no-op in the EditorScene class, as there are no specific shutdown + * procedures required for this scene type. + */ void shutdown() override; /** @@ -54,34 +57,41 @@ namespace nexo::editor */ void show() override; + bool showToolbar = false; + bool isPhysicsRunning = false; + /** * @brief Updates the scene by processing input events and rendering the current frame. * * This function handles key events and updates the scene by executing the rendering engine in framebuffer mode. - * It processes left mouse clicks when the scene view is focused and ImGuizmo is not active. The mouse position is - * adjusted relative to the viewport and its y-coordinate is flipped to match OpenGL's texture format. If the click - * falls within valid bounds and corresponds to a valid entity (pixel value not equal to -1), the entity is selected - * and the SceneViewManager is notified of the active scene; otherwise, any existing selection is cleared. + * It processes left mouse clicks when the scene view is focused and ImGuizmo is not active. The mouse position + * is adjusted relative to the viewport and its y-coordinate is flipped to match OpenGL's texture format. If the + * click falls within valid bounds and corresponds to a valid entity (pixel value not equal to -1), the entity + * is selected and the SceneViewManager is notified of the active scene; otherwise, any existing selection is + * cleared. * * The update is skipped entirely if the scene window is not open. */ void update() override; /** - * @brief Retrieves the unique identifier of the scene. - * - * @return scene::SceneId The identifier of this scene. - */ - [[nodiscard]] scene::SceneId getSceneId() const { return m_sceneId; }; + * @brief Retrieves the unique identifier of the scene. + * + * @return scene::SceneId The identifier of this scene. + */ + [[nodiscard]] scene::SceneId getSceneId() const + { + return m_sceneId; + }; /** - * @brief Sets the active camera for this scene. - * - * Deactivates the current camera and switches to the specified camera entity. - * The previously active camera will have its render and active flags set to false. - * - * @param cameraId Entity ID of the camera to set as active. - */ + * @brief Sets the active camera for this scene. + * + * Deactivates the current camera and switches to the specified camera entity. + * The previously active camera will have its render and active flags set to false. + * + * @param cameraId Entity ID of the camera to set as active. + */ void setCamera(ecs::Entity cameraId); /** @@ -90,19 +100,22 @@ namespace nexo::editor * When a scene is set as the default, it will be populated with * default entities (lights, basic geometry) during setup. */ - void setDefault() { m_defaultScene = true; }; + void setDefault() + { + m_defaultScene = true; + }; - private: + private: bool m_defaultScene = false; ImVec2 m_viewportBounds[2]; ImGuizmo::OPERATION m_currentGizmoOperation = ImGuizmo::UNIVERSAL; - ImGuizmo::MODE m_currentGizmoMode = ImGuizmo::WORLD; - bool m_snapTranslateOn = false; - glm::vec3 m_snapTranslate = {10.0f, 10.0f, 10.0f}; - bool m_snapRotateOn = false; - float m_angleSnap = 90.0f; - bool m_snapToGrid = false; - bool m_wireframeEnabled = false; + ImGuizmo::MODE m_currentGizmoMode = ImGuizmo::WORLD; + bool m_snapTranslateOn = false; + glm::vec3 m_snapTranslate = {10.0f, 10.0f, 10.0f}; + bool m_snapRotateOn = false; + float m_angleSnap = 90.0f; + bool m_snapToGrid = false; + bool m_wireframeEnabled = false; ecs::Entity m_entityHovered = ecs::INVALID_ENTITY; @@ -113,10 +126,8 @@ namespace nexo::editor PopupManager m_popupManager; - const std::vector m_buttonGradient = { - {0.0f, IM_COL32(50, 50, 70, 230)}, - {1.0f, IM_COL32(30, 30, 45, 230)} - }; + const std::vector m_buttonGradient = {{0.0f, IM_COL32(50, 50, 70, 230)}, + {1.0f, IM_COL32(30, 30, 45, 230)}}; // Game window focus scheduling bool m_shouldFocusGameWindow = false; @@ -127,10 +138,13 @@ namespace nexo::editor std::string m_gameWindowNameToSplit; // Selected button gradient - lighter blue gradient - const std::vector m_selectedGradient = { - {0.0f, IM_COL32(70, 70, 120, 230)}, - {1.0f, IM_COL32(50, 50, 100, 230)} - }; + const std::vector m_selectedGradient = {{0.0f, IM_COL32(70, 70, 120, 230)}, + {1.0f, IM_COL32(50, 50, 100, 230)}}; + + std::vector m_timecodeSeconds{5.0f, 8.0f, 5.0f, 10.0f}; + int m_currentTimecodeIndex = 0; + bool m_isTimecodeActive = false; + float m_timecodeElapsed = 0.0f; /** * @brief Sets the main scene window's view size. @@ -147,32 +161,150 @@ namespace nexo::editor */ void setupScene(); + /** + * @brief Handles drag-and-drop events for adding models and textures to the scene. + * + * This method checks if a drag-and-drop operation is occurring over the scene viewport. + * If a model or texture asset is dropped, it creates the corresponding entity in the scene + * at a default position. The method also records the creation action for undo/redo functionality. + */ void hideAllButSelectionCallback() const; + + /** + * @brief Selects all entities in the scene. + * + * Iterates through all entities in the current scene and adds them to the selection + * context, excluding the editor camera. Updates the window state to the gizmo state + * after selection. + */ void selectAllCallback(); + + /** + * @brief Unhides all entities in the scene. + * + * Iterates through all entities in the current scene and sets their RenderComponent's + * isRendered property to true, effectively unhiding them. Records the changes as actions + * for undo/redo functionality. + */ void unhideAllCallback() const; + + /** + * @brief Deletes the currently selected entities from the scene. + * + * Removes all selected entities from the scene and records the deletions as actions + * for undo/redo functionality. Clears the selection context after deletion and resets + * the window state to the global state. + */ void deleteCallback(); + /** @ + * brief Sets up command bindings for various editor states. + * + * Initializes command mappings for the global state, gizmo state, and specific + * gizmo modes (translate, rotate, scale). Also sets up keyboard shortcuts for + * common actions within the editor. + */ void setupGlobalState(); + + /** + * @brief Sets up command bindings for the gizmo state. + * + * Initializes command mappings for the gizmo state, allowing users to switch + * between different transformation modes (translate, rotate, scale) and modify + * gizmo behavior using keyboard shortcuts. Includes commands for toggling snapping, + * locking axes, and changing the gizmo operation. + */ void setupGizmoState(); + + /** + * @brief Sets up command bindings for the gizmo translate state. + * + * Initializes command mappings for the translate state of the gizmo, + * allowing users to switch between different transformation modes (translate, rotate, scale) + * and modify gizmo behavior using keyboard shortcuts. It includes commands for toggling + * snapping, locking axes, and changing the gizmo operation. + */ void setupGizmoTranslateState(); + + /** + * @brief Sets up command bindings for the gizmo scale state. + * + * Initializes command mappings for the scale state of the gizmo, + * allowing users to switch between different transformation modes (translate, rotate, scale) + * and modify gizmo behavior using keyboard shortcuts. It includes commands for toggling + * snapping, locking axes, and changing the gizmo operation. + */ void setupGizmoRotateState(); + + /** + * @brief Sets up command bindings for the gizmo scale state. + * + * Initializes command mappings for the scale state of the gizmo, + * allowing users to switch between different transformation modes (translate, rotate, scale) + * and modify gizmo behavior using keyboard shortcuts. It includes commands for toggling + * snapping, locking axes, and changing the gizmo operation. + */ void setupGizmoScaleState(); + + /** + * @brief Sets up all keyboard shortcuts for the editor scene. + * + * This function initializes the global state and various gizmo states (translate, rotate, scale) + * by registering commands with their associated key bindings and actions. It configures how + * different key combinations affect the editor's behavior, such as selecting all entities, + * deleting entities, switching between gizmo modes, and toggling snapping. + */ void setupShortcuts(); /** - * @brief Populates the scene with default entities. - * - * Creates standard light sources (ambient, directional, point, spot) - * and a simple ground plane in the scene. - */ - void loadDefaultEntities() const; + * @brief Populates the scene with default entities. + * + * Creates standard light sources (ambient, directional, point, spot) + * and a simple ground plane in the scene. + */ + static void loadDefaultEntities(); + + /** + * @brief Creates a physics-enabled entity in the scene. + * + * This method creates an entity with a mesh, material, transform, and physics components + * based on the provided parameters. The entity is added to the current scene and configured + * with the specified position, size, rotation, color, shape type, and motion type. + * + * @param pos The position of the entity in world space. + * @param size The size (scale) of the entity. + * @param rotation The rotation of the entity in Euler angles (degrees). + * @param color The color of the entity as a glm::vec4 (RGBA). + * @param shapeType The shape type for the physics collider (e.g., box, sphere). + * @param motionType The motion type for the physics body (e.g., static, dynamic). + */ + void createEntityWithPhysic(const glm::vec3& pos, const glm::vec3& size, const glm::vec3& rotation, + const glm::vec4& color, system::ShapeType shapeType, + JPH::EMotionType motionType) const; + + /** + * @brief Adds a 3D model to the scene at the specified position, scale, and rotation. + * + * This method creates an entity with a model component using the provided model path, + * and sets its transform based on the given position, scale, and rotation. The entity + * is then added to the current scene. + * + * @param modelPath The file path to the 3D model asset. + * @param position The position of the model in world space. + * @param scale The scale of the model. + * @param rotation The rotation of the model in Euler angles (degrees). + */ + void addModelToScene(const std::string& modelPath, const glm::vec3& position, + const glm::vec3& scale = {1.0f, 1.0f, 1.0f}, + const glm::vec3& rotation = {0.0f, 0.0f, 0.0f}) const; /** * @brief Renders the toolbar overlay within the main scene view. * - * This method uses ImGui to display a toolbar that includes buttons for switching between orthographic and perspective camera modes, - * a popup placeholder for adding primitive entities, and a draggable input for adjusting the target frames per second (FPS). - * The toolbar is positioned relative to the current view to align with the scene layout. + * This method uses ImGui to display a toolbar that includes buttons for switching between orthographic and + * perspective camera modes, a popup placeholder for adding primitive entities, and a draggable input for + * adjusting the target frames per second (FPS). The toolbar is positioned relative to the current view to align + * with the scene layout. */ void renderToolbar(); @@ -205,11 +337,8 @@ namespace nexo::editor * @param inactiveGizmoMode Reference to store the inactive mode button properties * @return true if the button was clicked */ - bool renderGizmoModeToolbarButton( - bool showGizmoModeMenu, - ImNexo::ButtonProps& activeGizmoMode, - ImNexo::ButtonProps& inactiveGizmoMode - ); + bool renderGizmoModeToolbarButton(bool showGizmoModeMenu, ImNexo::ButtonProps& activeGizmoMode, + ImNexo::ButtonProps& inactiveGizmoMode); /** * @brief Renders the primitive creation dropdown menu. @@ -244,6 +373,11 @@ namespace nexo::editor */ void snapSettingsPopup(); + /** + * @brief Handles the grid settings popup dialog. + * + * Creates a modal popup allowing users to configure grid visibility and size. + */ void gridSettingsPopup(); /** @@ -258,14 +392,16 @@ namespace nexo::editor * @param rightClicked Optional pointer to track right-click events * @return true if the button was clicked */ - static bool renderToolbarButton( - const std::string& uniqueId, - const std::string& icon, - const std::string& tooltip, - const std::vector& gradientStop, - bool* rightClicked = nullptr - ); + static bool renderToolbarButton(const std::string& uniqueId, const std::string& icon, + const std::string& tooltip, + const std::vector& gradientStop, + bool* rightClicked = nullptr); + /** + * @brief Retrieves the last used gizmo operation mode. + * + * @return ImGuizmo::OPERATION The last gizmo operation mode (translate, rotate, scale). + */ ImGuizmo::OPERATION getLastGuizmoOperation(); /** @@ -280,15 +416,65 @@ namespace nexo::editor * If the gizmo is actively manipulated, the entity's transform component is updated with the new values. */ void renderGizmo(); + + /** + * @brief Configures ImGuizmo with the camera's parameters. + * + * Sets up ImGuizmo to use the provided camera's view and projection matrices, + * and configures it for orthographic or perspective mode based on the camera type. + * + * @param camera The camera component whose parameters will be used to set up ImGuizmo. + */ void setupGizmoContext(const components::CameraComponent& camera) const; + + /** * @brief Retrieves the snap settings for a given gizmo operation. + * + * This function checks if snapping is enabled for the specified operation + * (translation or rotation) and returns a pointer to the corresponding snap + * value array. If snapping is not enabled for the operation, it returns nullptr. + * + * @param operation The ImGuizmo operation type (TRANSLATE or ROTATE). + * @return A pointer to the snap settings array if snapping is enabled; otherwise, nullptr. + */ float* getSnapSettingsForOperation(ImGuizmo::OPERATION operation); + + /** * @brief Captures the initial transform states of multiple entities. + * + * This method stores the current state of the TransformComponent for each entity + * in the provided list. The states are saved in a static map, allowing for later + * comparison to detect changes after transformations are applied. + * + * @param entities A vector of entity IDs whose transform states will be captured. + */ static void captureInitialTransformStates(const std::vector& entities); - static void applyTransformToEntities( - ecs::Entity sourceEntity, - const glm::mat4& oldWorldMatrix, - const glm::mat4& newWorldMatrix, - const std::vector& targetEntities) ; + + /** * @brief Applies a world transformation delta to multiple entities. + * + * This function takes a source entity's old and new world transformation matrices, + * computes the delta transformation, and applies this delta to a list of target entities. + * Each target entity's TransformComponent is updated to reflect the new world position, + * rotation, and scale based on the computed delta. + * + * @param sourceEntity The entity whose transformation change is the basis for the delta. + * @param oldWorldMatrix The original world transformation matrix of the source entity. + * @param newWorldMatrix The new world transformation matrix of the source entity. + * @param targetEntities A vector of entity IDs to which the delta transformation will be applied. + */ + static void applyTransformToEntities(ecs::Entity sourceEntity, const glm::mat4& oldWorldMatrix, + const glm::mat4& newWorldMatrix, const std::vector& targetEntities); + + /** * @brief Creates undo actions for transform changes on multiple entities. + * + * This method generates and records undo actions for a set of entities that have undergone + * transformation changes. It compares the initial and current states of each entity's + * TransformComponent and, if a change is detected, creates a TransformChangeAction to + * encapsulate the before-and-after states. All generated actions are then recorded with + * the ActionManager to enable undo/redo functionality. + * + * @param entities A vector of entity IDs that have been transformed. + */ static void createTransformUndoActions(const std::vector& entities); + static bool s_wasUsingGizmo; static ImGuizmo::OPERATION s_lastOperation; static std::unordered_map s_initialTransformStates; @@ -300,41 +486,233 @@ namespace nexo::editor * rendered scene, and updates viewport bounds for input handling. */ void renderView(); + + /** + * @brief Renders a message when no active camera is set in the scene. + * + * Displays a centered message within the viewport area indicating that no active + * camera is available for rendering the scene. + */ void renderNoActiveCamera() const; + + /** + * @brief Renders the popup for creating primitive shapes. + * + * Displays a modal popup allowing users to select and create various primitive + * shapes (cube, sphere, plane, etc.) in the scene. + * + * @param primitive The type of primitive to create. + */ void renderPrimitiveCreationPopup(const Primitives& primitive) const; + + /** + * @brief Renders the popup for creating new entities. + * + * Displays a modal popup with options to create different types of entities, + * including primitives, models, lights, and cameras. Handles user interactions + * to instantiate the selected entity type in the scene. + */ void renderNewEntityPopup(); + /** + * @brief Renders the popup for creating a sphere primitive. + * + * Displays a modal popup allowing users to specify parameters for creating + * a sphere primitive in the scene, such as radius, segments, and color. + */ void handleSelection(); + + /** + * @brief Handles drag-and-drop events for adding models and textures to the scene. + * + * This method checks if a drag-and-drop operation is occurring over the scene viewport. + * If a model or texture asset is dropped, it creates the corresponding entity in the scene + * at a default position. The method also records the creation action for undo/redo functionality. + */ void handleDropTarget(); - void handleDropModel(const AssetDragDropPayload &payload) const; - void handleDropTexture(const AssetDragDropPayload &payload) const; - void handleDropMaterial(const AssetDragDropPayload &payload) const; - int sampleEntityTexture(float mx, float my) const; + + /** * @brief Handles the drop of a model asset into the scene. + * + * Creates a new model entity in the scene at a default position when a model + * asset is dropped onto the viewport. Records the creation action for undo/redo. + * + * @param payload The drag-and-drop payload containing model asset information. + */ + void handleDropModel(const AssetDragDropPayload& payload) const; + + /** * @brief Handles the drop of a texture asset into the scene. + * + * Applies the dropped texture to the currently selected entity if it has + * a material component. If no entity is selected, it samples the entity + * under the mouse cursor and applies the texture to it. Records the change + * as an action for undo/redo functionality. + * + * @param payload The drag-and-drop payload containing texture asset information. + */ + void handleDropTexture(const AssetDragDropPayload& payload) const; + + /** + * @brief Handles the drop of a material asset into the scene. + * + * Applies the dropped material to the currently selected entity if it has + * a material component. If no entity is selected, it samples the entity + * under the mouse cursor and applies the material to it. Records the change + * as an action for undo/redo functionality. + * + * @param payload The drag-and-drop payload containing material asset information. + */ + void handleDropMaterial(const AssetDragDropPayload& payload) const; + + /** * @brief Samples the entity ID at the given mouse coordinates in the viewport. + * + * Reads the pixel value from the framebuffer at the specified mouse coordinates + * to determine which entity is under the cursor. The pixel value corresponds to + * the entity ID, with -1 indicating no entity is present at that location. + * + * @param mx The x-coordinate of the mouse within the viewport. + * @param my The y-coordinate of the mouse within the viewport. + * @return The entity ID under the mouse cursor, or -1 if no entity is present. + */ + [[nodiscard]] int sampleEntityTexture(float mx, float my) const; + + /** + * @brief Finds the root parent entity of the given entity. + * + * Traverses the entity hierarchy upwards to locate the top-most parent entity. + * Returns the root parent entity ID, or the input entity ID if it has no parent. + * + * @param entityId The entity whose root parent is to be found. + * @return The root parent entity ID. + */ static ecs::Entity findRootParent(ecs::Entity entityId); + + /** + * @brief Selects the entire hierarchy of the given entity. + * + * Selects the entity and all its children in the hierarchy. If Ctrl is pressed, + * the selection is added to the current selection context. + * + * @param entityId The entity whose hierarchy will be selected. + * @param isCtrlPressed True if Ctrl is pressed for multi-selection. + */ void selectEntityHierarchy(ecs::Entity entityId, bool isCtrlPressed); + + /** + * @brief Selects all child entities of a model. + * + * Adds all children of the given model entities to the selection context. + * If Ctrl is pressed, the selection is added to the current selection. + * + * @param children Vector of child entity IDs to select. + * @param isCtrlPressed True if Ctrl is pressed for multi-selection. + */ void selectModelChildren(const std::vector& children, bool isCtrlPressed); + + /** + * @brief Updates the selection context based on user input. + * + * Handles selection logic for entities, supporting Shift and Ctrl modifiers + * for multi-selection and range selection. + * + * @param entityId The entity to update selection for. + * @param isShiftPressed True if Shift is pressed for range selection. + * @param isCtrlPressed True if Ctrl is pressed for multi-selection. + */ void updateSelection(int entityId, bool isShiftPressed, bool isCtrlPressed); + + /** + * @brief Updates the window state after selection changes. + * + * Synchronizes the window state with the current selection context, + * ensuring the UI reflects the latest selection. + */ void updateWindowState(); /** - * @brief Creates a new game window or focuses an existing one. - * - * Checks if a game window for the current scene already exists. If it does, - * makes it visible and focuses it. Otherwise, creates a new game window, - * configures it with the scene ID and UUID, and schedules it for docking - * and focusing on the next frame. - */ + * @brief Updates the timecode for video textures in the scene. + * + * Advances the timecode and updates playback for all video textures + * according to the current timecode state. + */ + void handleTimecodeUpdate(); + + /** + * @brief Skips all video textures to the previous keyframe. + * + * Iterates through all video textures in the scene and rewinds their + * playback to the previous keyframe. + */ + void skipVideosToPreviousKeyframe() const; + + /** + * @brief Skips all video textures in the scene to the next keyframe. + * + * Iterates through all entities in the current scene, identifies those with + * VideoTextureComponent, and advances their playback to the next keyframe. + */ + void skipVideosToNextKeyframe() const; + + /** + * @brief Creates a new game window or focuses an existing one. + * + * Checks if a game window for the current scene already exists. If it does, + * makes it visible and focuses it. Otherwise, creates a new game window, + * configures it with the scene ID and UUID, and schedules it for docking + * and focusing on the next frame. + */ void createOrFocusGameWindow(); - enum class EditorState - { - GLOBAL, - GIZMO, - GIZMO_TRANSLATE, - GIZMO_ROTATE, - GIZMO_SCALE, - NB_STATE - }; + /** @brief Spawns a scene with multiple balls for physics testing. + * + * Creates a grid of sphere entities with physics properties, arranged in + * a 5x5x5 formation. Each sphere is given a random color and positioned + * based on the provided offset. + * + * @param offset A glm::vec3 representing the positional offset to apply to all spheres. + */ + void spawnBallsScene(const glm::vec3& offset = {0.0f, 0.0f, 0.0f}) const; + + /** + * @brief Creates a scene with multiple light sources. + * + * Adds various types of lights (directional, point, spot, etc.) to the scene, + * positioned according to the given offset. Useful for testing lighting and visual effects. + * + * @param offset Position offset to apply to all lights. + */ + void lightsScene(const glm::vec3& offset = {0.0f, 0.0f, 0.0f}) const; + + /** + * @brief Creates a scene with physics entities. + * + * Adds objects with physical properties (rigid, dynamic) to the scene, + * positioned according to the given offset. Useful for testing physics interactions. + * + * @param offset Position offset to apply to all physics entities. + */ + void physicScene(const glm::vec3& offset = {0.0f, 0.0f, 0.0f}) const; + + /** + * @brief Creates a scene containing video textures. + * + * Adds entities with animated (video) textures to the scene, positioned according to the offset. + * Useful for testing video playback and synchronization in the engine. + * + * @param offset Position offset to apply to all video textures. + */ + void videoScene(const glm::vec3& offset = {0.0f, 0.0f, 0.0f}) const; + + /** + * @brief Creates a forest-type scene. + * + * Adds many vegetation objects (trees, bushes) to the scene, positioned according to the offset. + * Useful for testing rendering and management of large numbers of entities. + * + * @param offset Position offset to apply to all forest elements. + */ + void forestScene(const glm::vec3& offset = {0.0f, 0.0f, 0.0f}) const; + + enum class EditorState { GLOBAL, GIZMO, GIZMO_TRANSLATE, GIZMO_ROTATE, GIZMO_SCALE, NB_STATE }; WindowState m_globalState; WindowState m_gizmoState; @@ -342,4 +720,4 @@ namespace nexo::editor WindowState m_gizmoRotateState; WindowState m_gizmoScaleState; }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp index 8c0d451bc..f610b0333 100644 --- a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp +++ b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp @@ -14,31 +14,47 @@ #include "EditorScene.hpp" #include "components/Parent.hpp" -#include "context/Selector.hpp" #include "context/ActionManager.hpp" +#include "context/Selector.hpp" #include #define GLM_ENABLE_EXPERIMENTAL -#include #include +#include namespace nexo::editor { // Class-level variables for tracking gizmo state between frames - bool EditorScene::s_wasUsingGizmo = false; + bool EditorScene::s_wasUsingGizmo = false; ImGuizmo::OPERATION EditorScene::s_lastOperation = ImGuizmo::OPERATION::UNIVERSAL; std::unordered_map EditorScene::s_initialTransformStates; + /** + * @brief Retrieves the snap settings for a given gizmo operation. + * + * This function checks if snapping is enabled for the specified operation + * (translation or rotation) and returns a pointer to the corresponding snap + * value array. If snapping is not enabled for the operation, it returns nullptr. + * + * @return A pointer to the snap settings array if snapping is enabled; otherwise, nullptr. + */ static ImGuizmo::OPERATION getActiveGuizmoOperation() { - for (int bitPos = 0; bitPos <= 13; bitPos++) - { + for (int bitPos = 0; bitPos <= 13; bitPos++) { auto op = static_cast(1u << bitPos); - if (ImGuizmo::IsOver(op)) - return op; + if (ImGuizmo::IsOver(op)) return op; } return ImGuizmo::OPERATION::UNIVERSAL; } + /** + * @brief Captures the initial transform states of multiple entities. + * + * This method stores the current state of the TransformComponent for each entity + * in the provided list. The states are saved in a static map, allowing for later + * comparison to detect changes after transformations are applied. + * + * @param entities A vector of entity IDs whose transform states will be captured. + */ static std::optional findEntityWithTransform(const std::vector& entities) { const auto& coord = nexo::Application::m_coordinator; @@ -52,6 +68,14 @@ namespace nexo::editor { return std::nullopt; } + /** + * @brief Configures ImGuizmo settings based on the provided camera component. + * + * This method sets up ImGuizmo to match the camera's projection type (orthographic or perspective), + * defines the drawing area to align with the viewport bounds, and enables gizmo rendering. + * + * @param camera The camera component whose parameters will be used to set up ImGuizmo. + */ void EditorScene::setupGizmoContext(const components::CameraComponent& camera) const { ImGuizmo::SetOrthographic(camera.type == components::CameraType::ORTHOGRAPHIC); @@ -60,40 +84,65 @@ namespace nexo::editor { ImGuizmo::Enable(true); } + /** + * @brief Retrieves the world transformation matrix of an entity's parent. + * + * This function checks if the specified entity has a ParentComponent. If it does, + * it retrieves the parent's TransformComponent and returns its world matrix. If the + * entity has no parent or the parent lacks a TransformComponent, the function returns + * an identity matrix. + * + * @param entity The entity whose parent's world matrix is to be retrieved. + * @return The parent's world transformation matrix, or an identity matrix if no parent exists. + */ static glm::mat4 getEntityParentWorldMatrix(const ecs::Entity entity) { const auto& coord = nexo::Application::m_coordinator; const auto parentComponent = coord->tryGetComponent(entity); - if (!parentComponent) - return {1.0f}; // No parent, return identity + if (!parentComponent) return {1.0f}; // No parent, return identity const ecs::Entity parentEntity = parentComponent->get().parent; - if (parentEntity == ecs::INVALID_ENTITY) - return {1.0f}; // No parent, return identity + if (parentEntity == ecs::INVALID_ENTITY) return {1.0f}; // No parent, return identity const auto parentTransform = coord->tryGetComponent(parentEntity); - if (!parentTransform) - return {1.0f}; // Parent has no transform, return identity + if (!parentTransform) return {1.0f}; // Parent has no transform, return identity return parentTransform->get().worldMatrix; } + /** + * @brief Calculates the world transformation matrix from a TransformComponent. + * + * This function constructs a world transformation matrix by combining translation, + * rotation (from quaternion), and scaling based on the provided TransformComponent. + * + * @param transform The TransformComponent containing position, rotation, and scale. + * @return The resulting world transformation matrix as a glm::mat4. + */ static glm::mat4 calculateWorldMatrix(const components::TransformComponent& transform) { - return glm::translate(glm::mat4(1.0f), transform.pos) * - glm::toMat4(transform.quat) * + return glm::translate(glm::mat4(1.0f), transform.pos) * glm::toMat4(transform.quat) * glm::scale(glm::mat4(1.0f), transform.size); } + /** + * @brief Updates the world matrix of a single entity based on its local transform and parent's world matrix. + * + * This function retrieves the TransformComponent of the specified entity, calculates its local + * transformation matrix from its position, rotation, and scale, and then combines it with the + * world matrix of its parent (if any) to update the entity's world matrix. If the entity does + * not have a TransformComponent, the function exits early. + * + * @param entity The entity whose world matrix needs to be updated. + */ static void updateEntityWorldMatrix(const ecs::Entity entity) { - const auto& coord = nexo::Application::m_coordinator; + const auto& coord = nexo::Application::m_coordinator; const auto transform = coord->tryGetComponent(entity); - if (!transform) - return; + if (!transform) return; // Get parent's world matrix const glm::mat4 parentWorldMatrix = getEntityParentWorldMatrix(entity); @@ -105,9 +154,20 @@ namespace nexo::editor { transform->get().worldMatrix = parentWorldMatrix * localMatrix; } + /** + * @brief Recursively updates the world matrix of an entity and its ancestors. + * + * This function ensures that the world matrix of the specified entity is updated, + * along with all of its parent entities up the hierarchy. It first checks if the entity + * has a parent, and if so, it recursively calls itself on the parent before updating + * the world matrix of the current entity. This guarantees that all transformations + * are correctly propagated through the hierarchy. + * + * @param entity The entity whose world matrix needs to be updated. + */ static void updateEntityWorldMatrixRecursive(const ecs::Entity entity) { - const auto& coord = nexo::Application::m_coordinator; + const auto& coord = nexo::Application::m_coordinator; const auto parentComponent = coord->tryGetComponent(entity); if (parentComponent) { const ecs::Entity parentEntity = parentComponent->get().parent; @@ -118,7 +178,19 @@ namespace nexo::editor { updateEntityWorldMatrix(entity); } - static void updateLocalTransformFromWorld(components::TransformComponent& transform, const glm::mat4& worldMatrix, const ecs::Entity entity) + /** + * @brief Updates a TransformComponent's local transform based on a new world matrix. + * + * This function recalculates the local position, rotation, and scale of a TransformComponent + * given its new world transformation matrix. It takes into account the entity's parent + * transformation to ensure the local values are correctly derived from the world space. + * + * @param transform Reference to the TransformComponent to update. + * @param worldMatrix The new world transformation matrix to apply. + * @param entity The entity associated with the TransformComponent. + */ + static void updateLocalTransformFromWorld(components::TransformComponent& transform, const glm::mat4& worldMatrix, + const ecs::Entity entity) { const glm::mat4 parentWorldMatrix = getEntityParentWorldMatrix(entity); @@ -127,23 +199,15 @@ namespace nexo::editor { glm::vec3 skew; glm::vec4 perspective; - glm::decompose( - localMatrix, - transform.size, - transform.quat, - transform.pos, - skew, - perspective - ); - - transform.quat = glm::normalize(transform.quat); + glm::decompose(localMatrix, transform.size, transform.quat, transform.pos, skew, perspective); + + transform.quat = glm::normalize(transform.quat); transform.worldMatrix = worldMatrix; } float* EditorScene::getSnapSettingsForOperation(const ImGuizmo::OPERATION operation) { - if (m_snapTranslateOn && operation & ImGuizmo::OPERATION::TRANSLATE) - return &m_snapTranslate.x; + if (m_snapTranslateOn && operation & ImGuizmo::OPERATION::TRANSLATE) return &m_snapTranslate.x; if (m_snapRotateOn && operation & ImGuizmo::OPERATION::ROTATE) { return &m_angleSnap; } @@ -163,11 +227,8 @@ namespace nexo::editor { } } - void EditorScene::applyTransformToEntities( - const ecs::Entity sourceEntity, - const glm::mat4& oldWorldMatrix, - const glm::mat4& newWorldMatrix, - const std::vector& targetEntities) + void EditorScene::applyTransformToEntities(const ecs::Entity sourceEntity, const glm::mat4& oldWorldMatrix, + const glm::mat4& newWorldMatrix, const std::vector& targetEntities) { const auto& coord = Application::m_coordinator; @@ -186,22 +247,31 @@ namespace nexo::editor { } } + /** + * @brief Compares two TransformComponent states to determine if any properties have changed. + * + * This static method checks the position, rotation, and scale of two TransformComponent + * memento states. It returns true if any of these properties differ between the two states, + * indicating that a change has occurred. + * + * @param before The initial state of the TransformComponent. + * @param after The modified state of the TransformComponent. + * @return True if any property has changed; otherwise, false. + */ static bool hasTransformChanged(const components::TransformComponent::Memento& before, - const components::TransformComponent::Memento& after) + const components::TransformComponent::Memento& after) { - return before.position != after.position || - before.rotation != after.rotation || - before.scale != after.scale; + return before.position != after.position || before.rotation != after.rotation || before.scale != after.scale; } void EditorScene::createTransformUndoActions(const std::vector& entities) { - const auto& coord = Application::m_coordinator; + const auto& coord = Application::m_coordinator; auto& actionManager = ActionManager::get(); if (entities.size() > 1) { auto groupAction = ActionManager::createActionGroup(); - bool anyChanges = false; + bool anyChanges = false; for (const auto& entity : entities) { auto transform = coord->tryGetComponent(entity); @@ -211,7 +281,7 @@ namespace nexo::editor { if (it == s_initialTransformStates.end()) continue; auto beforeState = it->second; - auto afterState = transform->get().save(); + auto afterState = transform->get().save(); // Check if anything actually changed if (hasTransformChanged(beforeState, afterState)) { @@ -225,18 +295,17 @@ namespace nexo::editor { if (anyChanges) { actionManager.recordAction(std::move(groupAction)); } - } - else if (entities.size() == 1) { - auto entity = entities[0]; + } else if (entities.size() == 1) { + auto entity = entities[0]; auto transform = coord->tryGetComponent(entity); if (s_initialTransformStates.contains(entity)) { auto beforeState = s_initialTransformStates[entity]; - auto afterState = transform->get().save(); + auto afterState = transform->get().save(); if (hasTransformChanged(beforeState, afterState)) { - actionManager.recordComponentChange( - entity, beforeState, afterState); + actionManager.recordComponentChange(entity, beforeState, + afterState); } } } @@ -247,19 +316,18 @@ namespace nexo::editor { void EditorScene::renderGizmo() { - const auto& coord = Application::m_coordinator; + const auto& coord = Application::m_coordinator; auto const& selector = Selector::get(); // Skip if no valid selection - if (selector.getPrimarySelectionType() == SelectionType::SCENE || - selector.getSelectedScene() != m_sceneId || + if (selector.getPrimarySelectionType() == SelectionType::SCENE || selector.getSelectedScene() != m_sceneId || !selector.hasSelection()) { return; } // Find entity with transform component const auto& selectedEntities = selector.getSelectedEntities(); - ecs::Entity primaryEntity = selector.getPrimaryEntity(); + ecs::Entity primaryEntity = selector.getPrimaryEntity(); auto primaryTransform = coord->tryGetComponent(primaryEntity); if (!primaryTransform) { @@ -268,7 +336,7 @@ namespace nexo::editor { return; // No entity with transform found } - primaryEntity = *entityWithTransform; + primaryEntity = *entityWithTransform; primaryTransform = coord->tryGetComponent(primaryEntity); } @@ -277,12 +345,12 @@ namespace nexo::editor { // Camera setup const auto& cameraTransform = coord->getComponent(m_activeCamera); - auto& camera = coord->getComponent(m_activeCamera); + auto& camera = coord->getComponent(m_activeCamera); setupGizmoContext(camera); ImGuizmo::SetID(static_cast(primaryEntity)); - glm::mat4 viewMatrix = camera.getViewMatrix(cameraTransform); + glm::mat4 viewMatrix = camera.getViewMatrix(cameraTransform); glm::mat4 projectionMatrix = camera.getProjectionMatrix(); // 1) M₀ = parentWorld * T(pos) * R(quat) * S(size) @@ -293,7 +361,7 @@ namespace nexo::editor { const glm::mat4 M0 = parentWorld * Tpos * Rrot * Sscale; // 2) “centroid offset” = T(centroidLocal) - const glm::mat4 C_offset = glm::translate(glm::mat4(1.0f), primaryTransform->get().localCenter); + const glm::mat4 C_offset = glm::translate(glm::mat4(1.0f), primaryTransform->get().localCenter); // 3) M1 = M0 * C_offset glm::mat4 worldTransformMatrix = M0 * C_offset; @@ -301,23 +369,15 @@ namespace nexo::editor { // (We’ll need “M₀” again after manipulation for decomposing back.) const glm::mat4 originalWorldMatrix_ModelOrigin = M0; - if (!ImGuizmo::IsUsing()) - s_lastOperation = getActiveGuizmoOperation(); + if (!ImGuizmo::IsUsing()) s_lastOperation = getActiveGuizmoOperation(); const float* snap = getSnapSettingsForOperation(s_lastOperation); if (!s_wasUsingGizmo && ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGuizmo::IsOver()) captureInitialTransformStates(selectedEntities); - ImGuizmo::Manipulate( - glm::value_ptr(viewMatrix), - glm::value_ptr(projectionMatrix), - m_currentGizmoOperation, - m_currentGizmoMode, - glm::value_ptr(worldTransformMatrix), - nullptr, - snap - ); + ImGuizmo::Manipulate(glm::value_ptr(viewMatrix), glm::value_ptr(projectionMatrix), m_currentGizmoOperation, + m_currentGizmoMode, glm::value_ptr(worldTransformMatrix), nullptr, snap); const bool isUsingGizmo = ImGuizmo::IsUsing(); @@ -325,8 +385,8 @@ namespace nexo::editor { // Disable camera movement during manipulation camera.active = false; - const glm::mat4 newWorldMatrix_Centroid = worldTransformMatrix; - const glm::mat4 invCentroidOffset = glm::inverse(C_offset); + const glm::mat4 newWorldMatrix_Centroid = worldTransformMatrix; + const glm::mat4 invCentroidOffset = glm::inverse(C_offset); const glm::mat4 newWorldMatrix_ModelOrigin = newWorldMatrix_Centroid * invCentroidOffset; // Update the primary entity's local transform based on the new world transform @@ -355,4 +415,4 @@ namespace nexo::editor { s_wasUsingGizmo = isUsingGizmo; } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EditorScene/Init.cpp b/editor/src/DocumentWindows/EditorScene/Init.cpp index d8af86ccf..bed11be41 100644 --- a/editor/src/DocumentWindows/EditorScene/Init.cpp +++ b/editor/src/DocumentWindows/EditorScene/Init.cpp @@ -12,20 +12,20 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "EditorScene.hpp" +#include #include "CameraFactory.hpp" -#include "LightFactory.hpp" +#include "EditorScene.hpp" #include "EntityFactory3D.hpp" +#include "LightFactory.hpp" +#include "Path.hpp" #include "RenderPass.hpp" -#include "utils/EditorProps.hpp" +#include "assets/AssetImporter.hpp" #include "renderPasses/GridPass.hpp" #include "renderPasses/MaskPass.hpp" #include "renderPasses/OutlinePass.hpp" -#include "assets/AssetImporter.hpp" -#include "Path.hpp" +#include "utils/EditorProps.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void EditorScene::setup() { setupWindow(); @@ -39,29 +39,29 @@ namespace nexo::editor m_sceneId = static_cast(app.getSceneManager().createScene(m_windowName)); renderer::NxFramebufferSpecs framebufferSpecs; - framebufferSpecs.attachments = { - renderer::NxFrameBufferTextureFormats::RGBA8, renderer::NxFrameBufferTextureFormats::RED_INTEGER, - renderer::NxFrameBufferTextureFormats::Depth - }; - framebufferSpecs.width = static_cast(m_contentSize.x); - framebufferSpecs.height = static_cast(m_contentSize.y); - const auto renderTarget = renderer::NxFramebuffer::create(framebufferSpecs); - m_editorCamera = static_cast(CameraFactory::createPerspectiveCamera({0.0f, 36.0f, 25.0f}, - static_cast(m_contentSize.x), - static_cast(m_contentSize.y), - renderTarget)); + framebufferSpecs.attachments = {renderer::NxFrameBufferTextureFormats::RGBA8, + renderer::NxFrameBufferTextureFormats::RED_INTEGER, + renderer::NxFrameBufferTextureFormats::Depth}; + framebufferSpecs.width = static_cast(m_contentSize.x); + framebufferSpecs.height = static_cast(m_contentSize.y); + const auto renderTarget = renderer::NxFramebuffer::create(framebufferSpecs); + m_editorCamera = static_cast( + CameraFactory::createPerspectiveCamera({-14.51f, 7.41f, 2.46f}, static_cast(m_contentSize.x), + static_cast(m_contentSize.y), renderTarget)); auto& cameraComponent = Application::m_coordinator->getComponent(m_editorCamera); - cameraComponent.render = true; - auto maskPass = std::make_shared( - static_cast(m_contentSize.x), - static_cast(m_contentSize.y)); - auto outlinePass = std::make_shared(); - auto gridPass = std::make_shared(); + auto& transformComponent = + Application::m_coordinator->getComponent(m_editorCamera); + transformComponent.quat = glm::quat(glm::radians(glm::vec3{-56.90f, 18.90f, 0.0f})); + cameraComponent.render = true; + auto maskPass = std::make_shared(static_cast(m_contentSize.x), + static_cast(m_contentSize.y)); + auto outlinePass = std::make_shared(); + auto gridPass = std::make_shared(); const renderer::PassId forwardId = cameraComponent.pipeline.getFinalOutputPass(); - const renderer::PassId maskId = cameraComponent.pipeline.addRenderPass(std::move(maskPass)); + const renderer::PassId maskId = cameraComponent.pipeline.addRenderPass(std::move(maskPass)); const renderer::PassId outlineId = cameraComponent.pipeline.addRenderPass(std::move(outlinePass)); - const renderer::PassId gridId = cameraComponent.pipeline.addRenderPass(std::move(gridPass)); + const renderer::PassId gridId = cameraComponent.pipeline.addRenderPass(std::move(gridPass)); // Set up prerequisites cameraComponent.pipeline.addPrerequisite(outlineId, maskId); cameraComponent.pipeline.addPrerequisite(outlineId, forwardId); @@ -74,6 +74,8 @@ namespace nexo::editor // Set the final output pass explicitly cameraComponent.pipeline.setFinalOutputPass(gridId); + + // Add editor camera to the scene app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_editorCamera)); const components::PerspectiveCameraController controller; Application::m_coordinator->addComponent( @@ -82,206 +84,310 @@ namespace nexo::editor Application::m_coordinator->addComponent(m_editorCamera, editorCameraTag); m_activeCamera = m_editorCamera; + scene::Scene& scene = app.getSceneManager().getScene(m_sceneId); + scene.addEntity(LightFactory::createAmbientLight({1.0f, 1.0f, 1.0f})); + scene.addEntity(LightFactory::createDirectionalLight({0.0f, -0.8f, 0.0f})); + m_sceneUuid = app.getSceneManager().getScene(m_sceneId).getUuid(); - if (m_defaultScene) - loadDefaultEntities(); + if (m_defaultScene) { + // loadDefaultEntities(); + physicScene(glm::vec3{-60.0f, 0.0f, 0.0f}); + videoScene(glm::vec3{-15.0f, 0.0f, 0.0f}); + lightsScene(glm::vec3{50.0f, 0.0f, 0.0f}); + forestScene({100.0f, 1.0f, 0.0f}); + } } - void lightsScene(const unsigned int sceneId) + void EditorScene::createEntityWithPhysic(const glm::vec3& pos, const glm::vec3& size, const glm::vec3& rotation, + const glm::vec4& color, system::ShapeType shapeType, + const JPH::EMotionType motionType) const { - auto& app = getApp(); - scene::Scene& scene = app.getSceneManager().getScene(sceneId); + auto& app = getApp(); + scene::Scene& scene = app.getSceneManager().getScene(m_sceneId); + + ecs::Entity entity; + LOG(NEXO_DEV, "Creating entity of type: {}", static_cast(shapeType)); + + using enum nexo::system::ShapeType; + switch (shapeType) { + case Box: + entity = EntityFactory3D::createCube(pos, size, rotation, color); + break; + case Sphere: + entity = EntityFactory3D::createSphere(pos, size, rotation, color, 1); + break; + case Cylinder: + entity = EntityFactory3D::createCylinder(pos, size, rotation, color, 8); + break; + case Tetrahedron: + entity = EntityFactory3D::createTetrahedron(pos, size, rotation, color); + break; + case Pyramid: + entity = EntityFactory3D::createPyramid(pos, size, rotation, color); + break; + default: + THROW_EXCEPTION(UnsupportedEntityShapeType); + } + + const JPH::BodyID bodyId = app.getPhysicsSystem()->createBodyFromShape( + entity, Application::m_coordinator->getComponent(entity), shapeType, + motionType); + if (bodyId.IsInvalid()) { + THROW_EXCEPTION(InvalidBodyId, entity); + } + scene.addEntity(entity); + } + + void EditorScene::addModelToScene(const std::string& modelPath, const glm::vec3& position, const glm::vec3& scale, + const glm::vec3& rotation) const + { + auto& app = getApp(); + scene::Scene& scene = app.getSceneManager().getScene(m_sceneId); + const auto& catalog = nexo::assets::AssetCatalog::getInstance(); + + const assets::AssetLocation model(modelPath); + const auto modelAssetRef = catalog.getAsset(model).as(); + const ecs::Entity entity = EntityFactory3D::createModel(modelAssetRef, position, scale, rotation); + + scene.addEntity(entity); + } + + void EditorScene::setupWindow() + { + m_contentSize = ImVec2(1280, 720); + } + + void EditorScene::setCamera(const ecs::Entity cameraId) + { + auto& oldCameraComponent = + Application::m_coordinator->getComponent(m_activeCamera); + oldCameraComponent.active = false; + oldCameraComponent.render = false; + m_activeCamera = static_cast(cameraId); + auto& newCameraComponent = Application::m_coordinator->getComponent(cameraId); + newCameraComponent.resize(static_cast(m_contentSize.x), + static_cast(m_contentSize.y)); + } + + void EditorScene::loadDefaultEntities() + {} + + void EditorScene::lightsScene(const glm::vec3& offset) const + { + auto& app = getApp(); + scene::Scene& scene = app.getSceneManager().getScene(m_sceneId); // Lights - constexpr int numLights = 5; - constexpr glm::vec3 center = {0.0f, 5.0f, 0.0f}; + constexpr int numLights = 5; + constexpr glm::vec3 center = {0.0f, 5.0f, 0.0f}; const std::vector colors = { {0.0f, 0.0f, 1.0f, 1.0f}, // Blue {1.0f, 0.0f, 1.0f, 1.0f}, // Magenta {1.0f, 0.5f, 0.0f, 1.0f}, // Orange {0.0f, 1.0f, 0.0f, 1.0f}, // Green - {1.0f, 1.0f, 0.0f, 1.0f} // Yellow + {1.0f, 1.0f, 0.0f, 1.0f} // Yellow }; - for (int i = 0; i < numLights; ++i) - { - constexpr float radius = 30.0f; - const float angle = glm::radians(360.0f / numLights * static_cast(i)); - const glm::vec3 position = center + glm::vec3(radius * cos(angle), 0.0f, radius * sin(angle)); - const auto light = LightFactory::createPointLight(position, colors[i % colors.size()], 0.01f, 0.0010f); + for (int i = 0; i < numLights; ++i) { + constexpr float radius = 10.0f; + const float angle = glm::radians(360.0f / numLights * static_cast(i)); + const glm::vec3 position = center + glm::vec3(radius * cos(angle), 5.0f, radius * sin(angle)); + const auto light = + LightFactory::createPointLight(position + offset, colors[i % colors.size()], 0.01f, 0.0010f); + utils::addPropsTo(light, utils::PropsType::POINT_LIGHT); scene.addEntity(light); } - } - void EditorScene::loadDefaultEntities() const - { - auto& app = getApp(); - scene::Scene& scene = app.getSceneManager().getScene(m_sceneId); + const auto base = + EntityFactory3D::createCube({0.0f + offset.x, 0.0f + offset.y, 0.0f + offset.z}, {25.0f, 0.7f, 25.0f}, + {0.0f, 0.0f, 0.0f}, {0.15f, 0.15f, 0.15f, 1.0f}); + scene.addEntity(base); - static std::random_device rd; - static std::mt19937 gen(rd()); - static std::uniform_real_distribution dis(0.0f, 1.0f); + const auto& catalog = nexo::assets::AssetCatalog::getInstance(); - scene.addEntity(LightFactory::createAmbientLight({1.0f, 1.0f, 1.0f})); - // const ecs::Entity pointLight = LightFactory::createPointLight({2.0f, 5.0f, 0.0f}); - // addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); - // scene.addEntity(pointLight); - // scene.addEntity(LightFactory::createDirectionalLight({0.2f, -1.0f, -0.3f})); + const assets::AssetLocation rubixCubeModel("my_package::RubixCube@Models"); + const auto rubixCubeAssetRef = catalog.getAsset(rubixCubeModel).as(); + auto rubixCube = + EntityFactory3D::createModel(rubixCubeAssetRef, {4.1f + offset.x, 2.8f + offset.y, -4.7f + offset.z}, + {10.0f, 10.0f, 10.0f}, {180.0f, 0.0f, 0.0f}); + scene.addEntity(rubixCube); - // Lights - // constexpr int nb_lights = 5; - // for (int i = 0; i < nb_lights; ++i) - // { - // constexpr float startY_light = 60.0f; - // constexpr float light_spacing = 70.0f / nb_lights; - // scene.addEntity(LightFactory::createPointLight({-21.0f, startY_light + static_cast(i) * light_spacing, 0.0f}, - // {1.0f, 1.0f, 1.0f})); - // scene.addEntity(LightFactory::createPointLight({21.0f, startY_light + static_cast(i) * light_spacing, 0.0f}, - // {1.0f, 1.0f, 1.0f})); - // - // } - - // Helper function to create and add an entity - auto createAndAddEntity = [&](const glm::vec3& pos, const glm::vec3& size, const glm::vec3& rotation, - const glm::vec4& color, system::ShapeType shapeType, JPH::EMotionType motionType) - { - ecs::Entity entity; - LOG(NEXO_DEV, "Creating entity of type: {}", static_cast(shapeType)); - switch (shapeType) - { - case system::ShapeType::Box: - entity = EntityFactory3D::createCube(pos, size, rotation, color); - break; - case system::ShapeType::Sphere: - entity = EntityFactory3D::createSphere(pos, size, rotation, color, 1); - break; - case system::ShapeType::Cylinder: - entity = EntityFactory3D::createCylinder(pos, size, rotation, color, 8); - break; - case system::ShapeType::Tetrahedron: - entity = EntityFactory3D::createTetrahedron(pos, size, rotation, color); - break; - case system::ShapeType::Pyramid: - entity = EntityFactory3D::createPyramid(pos, size, rotation, color); - break; - default: - throw std::runtime_error("Unsupported shape type for entity creation."); - } - JPH::BodyID bodyId = app.getPhysicsSystem()->createBodyFromShape(entity, Application::m_coordinator->getComponent(entity), shapeType, motionType); - if (bodyId.IsInvalid()) { - LOG(NEXO_ERROR, "Failed to create physics body for entity {}", entity); - } - scene.addEntity(entity); - return entity; - }; + const assets::AssetLocation planeModel("my_package::Plane@Models"); + const auto planeAssetRef = catalog.getAsset(planeModel).as(); + auto plane = EntityFactory3D::createModel(planeAssetRef, {-5.0f + offset.x, 0.5f + offset.y, -5.0f + offset.z}, + {1.0f, 1.0f, 1.0f}, {0.0f, 45.0f, 0.0f}); + scene.addEntity(plane); - // Balls - - for (int i = 0; i < 50; ++i) - { - float x = -3.0f + static_cast(i % 5) * 1.5f; - float z = static_cast((i % 2 == 0) ? 1 : -1) * 0.5f; - glm::vec3 pos = {x, 62.0f + static_cast(i), z}; - glm::vec4 color = { - 1.0f, dis(gen), dis(gen), 1.0f - }; - createAndAddEntity(pos, {0.4f, 0.4f, 0.4f}, {0, 0, 0}, color, system::ShapeType::Sphere, - JPH::EMotionType::Dynamic); - } - lightsScene(m_sceneId); + const assets::AssetLocation cupModel("my_package::Cup@Models"); + const auto cupAssetRef = catalog.getAsset(cupModel).as(); + auto cup = EntityFactory3D::createModel(cupAssetRef, {7.0f + offset.x, 0.3f + offset.y, 1.6f + offset.z}, + {14.0f, 14.0f, 14.0f}, {0.0f, -105.0f, 0.0f}); + scene.addEntity(cup); - // Background - createAndAddEntity({0.0f, 40.0f, -2.5f}, {44.0f, 80.0f, 0.5f}, {0, 0, 0}, {0.91f, 0.91f, 0.91f, 1.0f}, - system::ShapeType::Box, JPH::EMotionType::Static); + const assets::AssetLocation earthModel("my_package::Earth@Models"); + const auto earthAssetRef = catalog.getAsset(earthModel).as(); + auto earth = EntityFactory3D::createModel(earthAssetRef, {-0.4f + offset.x, 2.1f + offset.y, 7.0f + offset.z}, + {0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 0.0f}); + scene.addEntity(earth); - // Funnel - createAndAddEntity({-6.0f, 70.0f, 0.0f}, {10.0f, 0.5f, 4.0f}, {0, 0, -45.0f}, {0.0f, 0.28f, 0.47f, 1.0f}, - system::ShapeType::Box, JPH::EMotionType::Static); - createAndAddEntity({6.0f, 70.0f, 0.0f}, {10.0f, 0.5f, 4.0f}, {0, 0, 45.0f}, {0.0f, 0.28f, 0.47f, 1.0f}, - system::ShapeType::Box, JPH::EMotionType::Static); + const assets::AssetLocation plantModel("my_package::Plant@Models"); + const auto plantAssetRef = catalog.getAsset(plantModel).as(); + auto plant = EntityFactory3D::createModel(plantAssetRef, {-4.9f + offset.x, 0.38f + offset.y, 3.8f + offset.z}, + {5.0f, 5.0f, 5.0f}, {0.0f, 0.0f, 0.0f}); + scene.addEntity(plant); - // Spinner - createAndAddEntity({0.0f, 65.0f, 0.0f}, {0.5f, 3.0f, 4.0f}, {0, 0, 5.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, - system::ShapeType::Box, JPH::EMotionType::Static); + const assets::AssetLocation swordModel("my_package::Sword@Models"); + const auto swordAssetRef = catalog.getAsset(swordModel).as(); + auto sword = EntityFactory3D::createModel(swordAssetRef, {-0.0f + offset.x, 0.48f + offset.y, 0.0f + offset.z}, + {2.77f, 2.77f, 2.77f}, {0.0f, 0.0f, 0.0f}); + scene.addEntity(sword); + } - // Stairs + void EditorScene::physicScene(const glm::vec3& offset) const + { + // Background + createEntityWithPhysic({0.0f + offset.x, 40.0f + offset.y, -2.5f + offset.z}, {44.0f, 80.0f, 0.5f}, {0, 0, 0}, + {0.91f, 0.91f, 0.91f, 1.0f}, system::ShapeType::Box, JPH::EMotionType::Static); + + // Funnel left and right + createEntityWithPhysic({-6.0f + offset.x, 70.0f + offset.y, 0.0f + offset.z}, {10.0f, 0.5f, 4.0f}, + {0, 0, -45.0f}, {0.0f, 0.28f, 0.47f, 1.0f}, system::ShapeType::Box, + JPH::EMotionType::Static); + createEntityWithPhysic({6.0f + offset.x, 70.0f + offset.y, 0.0f + offset.z}, {10.0f, 0.5f, 4.0f}, {0, 0, 45.0f}, + {0.0f, 0.28f, 0.47f, 1.0f}, system::ShapeType::Box, JPH::EMotionType::Static); + + // Stairs right std::vector> stairs = { {{3.0f, 61.5f, 0.0f}, {5.0f, 0.5f, 4.0f}, {0, 0, -15.0f}, {0.0f, 0.28f, 0.47f, 1.0f}}, - {{11.0f, 58.5f, 0.0f}, {8.0f, 0.5f, 4.0f}, {0.0f, 0.0f, 20.0f}, {0.0f, 0.28f, 0.47f, 1.0f}}, - {{3.0f, 55.5f, 0.0f}, {5.0f, 0.5f, 4.0f}, {0.0f, 0.0f, -15.0f}, {0.0f, 0.28f, 0.47f, 1.0f}}, - {{10.0f, 52.5f, 0.0f}, {12.0f, 0.5f, 4.0f}, {0.0f, 0.0f, 20.0f}, {0.0f, 0.28f, 0.47f, 1.0f}} - }; - for (const auto& [pos, size, rotation, color] : stairs) - { - createAndAddEntity(pos, size, rotation, color, system::ShapeType::Box, JPH::EMotionType::Static); + {{11.0f, 58.5f, 0.0f}, {8.0f, 0.5f, 4.0f}, {0.0f, 0.0f, 20.0f}, {0.0f, 0.28f, 0.47f, 1.0f}}, + {{3.0f, 55.5f, 0.0f}, {5.0f, 0.5f, 4.0f}, {0.0f, 0.0f, -15.0f}, {0.0f, 0.28f, 0.47f, 1.0f}}, + {{10.0f, 52.5f, 0.0f}, {12.0f, 0.5f, 4.0f}, {0.0f, 0.0f, 20.0f}, {0.0f, 0.28f, 0.47f, 1.0f}}}; + for (const auto& [pos, size, rotation, color] : stairs) { + createEntityWithPhysic(pos + offset, size, rotation, color, system::ShapeType::Box, + JPH::EMotionType::Static); } - // Tunnel + // Tunnel left std::vector> tunnels = { {{-6.0f, 59.0f, 0.0f}, {3.0f, 11.0f, 4.0f}, {0, 0, 0}}, {{-1.0f, 58.5f, 0.0f}, {3.0f, 8.0f, 4.0f}, {0, 0, 0}}, - {{-5.0f, 51.0f, 0.0f}, {9.0f, 0.5f, 4.0f}, {0, 0, -25.0f}} - }; - for (const auto& [pos, size, rotation] : tunnels) - { - createAndAddEntity(pos, size, rotation, {0.0f, 0.28f, 0.47f, 1.0f}, system::ShapeType::Box, JPH::EMotionType::Static); + {{-5.0f, 51.0f, 0.0f}, {9.0f, 0.5f, 4.0f}, {0, 0, -25.0f}}}; + for (const auto& [pos, size, rotation] : tunnels) { + createEntityWithPhysic(pos + offset, size, rotation, {0.0f, 0.28f, 0.47f, 1.0f}, system::ShapeType::Box, + JPH::EMotionType::Static); } + // Dominos left and right + createEntityWithPhysic({-9.0f + offset.x, 44.0f + offset.y, 0.0f + offset.z}, {20.9f, 0.5f, 4.0f}, {0, 0, 0.0f}, + {0.0f, 0.28f, 0.47f, 1.0f}, system::ShapeType::Box, JPH::EMotionType::Static); + createEntityWithPhysic({11.15f + offset.x, 44.0f + offset.y, 0.0f + offset.z}, {15.5f, 0.5f, 4.0f}, + {0, 0, 0.0f}, {0.0f, 0.28f, 0.47f, 1.0f}, system::ShapeType::Box, + JPH::EMotionType::Static); - // Dominos - createAndAddEntity({-9.0f, 44.0f, 0.0f}, {20.9f, 0.5f, 4.0f}, {0, 0, 0.0f}, {0.0f, 0.28f, 0.47f, 1.0f}, - system::ShapeType::Box, JPH::EMotionType::Static); - createAndAddEntity({11.15f, 44.0f, 0.0f}, {15.5f, 0.5f, 4.0f}, {0, 0, 0.0f}, {0.0f, 0.28f, 0.47f, 1.0f}, - system::ShapeType::Box, JPH::EMotionType::Static); - - for (int i = 0; i < 24; ++i) - { + for (int i = 0; i < 24; ++i) { if (i == 13) continue; - float x = -18.4f + static_cast(i) * 1.6f; - glm::vec3 pos = {x, 45.5f, 0.0f}; - float gradientFactor = static_cast(i) / 24.0f; - glm::vec4 color = mix(glm::vec4(0.0f, 0.77f, 0.95f, 1.0f), glm::vec4(0.83f, 0.14f, 0.67f, 1.0f), gradientFactor); - createAndAddEntity(pos, {0.25f, 3.0f, 3.0f}, {0, 0, 0}, color, system::ShapeType::Box, JPH::EMotionType::Dynamic); + const float x = -18.4f + static_cast(i) * 1.6f; + glm::vec3 pos = {x, 45.5f, 0.0f}; + const float gradientFactor = static_cast(i) / 24.0f; + glm::vec4 color = + mix(glm::vec4(0.0f, 0.77f, 0.95f, 1.0f), glm::vec4(0.83f, 0.14f, 0.67f, 1.0f), gradientFactor); + createEntityWithPhysic(pos + offset, {0.25f, 3.0f, 3.0f}, {0, 0, 0}, color, system::ShapeType::Box, + JPH::EMotionType::Dynamic); } - // Spinner - createAndAddEntity({2.5f, 41.0f, 0.0f}, {0.5f, 3.0f, 4.0f}, {0, 0, 0}, {0.0f, 1.0f, 0.0f, 1.0f}, - system::ShapeType::Box, JPH::EMotionType::Static); - // Fakir constexpr int totalRows = 20; - constexpr float startX = -14.0f; + constexpr float startX = -14.0f; - for (int row = 0; row < totalRows; ++row) - { + for (int row = 0; row < totalRows; ++row) { constexpr int cols = 10; - for (int col = 0; col < cols; ++col) - { - constexpr float startY = 14.0f; - constexpr float spacing = 3.0f; - float offsetX = (row % 2 == 0) ? 0.0f : spacing / 2.0f; - glm::vec3 pos = {static_cast(col) * spacing + startX + offsetX, startY + static_cast(row) * 1.2f, 0.0f}; - auto maxFactor = static_cast(totalRows * cols); - float gradientFactor = static_cast((row + 1) * (col + 1)) / maxFactor; - glm::vec4 color = mix(glm::vec4(0.0f, 0.77f, 0.95f, 1.0f), glm::vec4(0.83f, 0.14f, 0.67f, 1.0f), gradientFactor); - createAndAddEntity(pos, {0.4f, 6.0f, 0.4f}, {90.0f, 0, 0}, color, system::ShapeType::Cylinder, JPH::EMotionType::Static); + for (int col = 0; col < cols; ++col) { + constexpr float startY = 14.0f; + constexpr float spacing = 3.0f; + const float offsetX = (row % 2 == 0) ? 0.0f : spacing / 2.0f; + glm::vec3 pos = {static_cast(col) * spacing + startX + offsetX, + startY + static_cast(row) * 1.2f, 0.0f}; + constexpr auto maxFactor = static_cast(totalRows * cols); + const float gradientFactor = static_cast((row + 1) * (col + 1)) / maxFactor; + glm::vec4 color = + mix(glm::vec4(0.0f, 0.77f, 0.95f, 1.0f), glm::vec4(0.83f, 0.14f, 0.67f, 1.0f), gradientFactor); + createEntityWithPhysic(pos + offset, {0.4f, 6.0f, 0.4f}, {90.0f, 0, 0}, color, + system::ShapeType::Cylinder, JPH::EMotionType::Static); } } } - void EditorScene::setupWindow() + void EditorScene::videoScene(const glm::vec3& offset) const { - m_contentSize = ImVec2(1280, 720); + auto& app = getApp(); + scene::Scene& scene = app.getSceneManager().getScene(m_sceneId); + + const auto videoBillboard = EntityFactory3D::createBillboard( + {0.0f + offset.x, 5.0f + offset.y, 1.0f + offset.z}, {5.3f, 3.0f, 1.0f}, {1.0f, 1.0f, 1.0f, 1.0f}); + components::VideoComponent videoComponent; + videoComponent.path = nexo::Path::resolvePathRelativeToExe("../resources/videos/_Warmup.mp4").string(); + videoComponent.keyframes = { + {0.0f, 0.1f, components::KeyframeType::NORMAL}, + {0.01f, 1.25f, components::KeyframeType::TRANSITION}, + {1.25f, 16.15f, components::KeyframeType::LOOP}, + {16.15f, 17.00f, components::KeyframeType::TRANSITION}, + {17.00f, 37.23f, components::KeyframeType::LOOP}, + {37.23f, 38.07f, components::KeyframeType::TRANSITION}, + {38.07f, 82.27f, components::KeyframeType::LOOP}, + {82.27f, 84.25f, components::KeyframeType::TRANSITION}, + {84.25f, 85.00f, components::KeyframeType::NORMAL}, + {85.00f, 87.03f, components::KeyframeType::TRANSITION}, + {87.03f, 95.15f, components::KeyframeType::LOOP}, + {95.15f, 97.15f, components::KeyframeType::TRANSITION}, + {97.15f, 109.13f, components::KeyframeType::LOOP}, + {109.13f, 111.07f, components::KeyframeType::TRANSITION}, + {111.07f, 140.04f, components::KeyframeType::LOOP}, + {138.07f, 140.04f, components::KeyframeType::TRANSITION}, + {142.01f, 149.15f, components::KeyframeType::LOOP}, + {149.15f, 151.09f, components::KeyframeType::TRANSITION}, + {151.09f, 161.53f, components::KeyframeType::LOOP}, + {161.53f, 164.06f, components::KeyframeType::TRANSITION}, + {164.06f, 195.14f, components::KeyframeType::LOOP}, + {195.14f, 197.19f, components::KeyframeType::TRANSITION}, + {197.19f, 214.12f, components::KeyframeType::LOOP}, + {214.12f, 216.18f, components::KeyframeType::TRANSITION}, + {216.18f, 243.81f, components::KeyframeType::LOOP}, + {243.81f, 245.81f, components::KeyframeType::TRANSITION}, + {245.81f, 262.00f, components::KeyframeType::NORMAL}, + + }; + + Application::m_coordinator->addComponent(videoBillboard, videoComponent); + + scene.addEntity(videoBillboard); } - void EditorScene::setCamera(const ecs::Entity cameraId) + void EditorScene::forestScene(const glm::vec3& offset) const { - auto& oldCameraComponent = Application::m_coordinator->getComponent< - components::CameraComponent>(m_activeCamera); - oldCameraComponent.active = false; - oldCameraComponent.render = false; - m_activeCamera = static_cast(cameraId); - auto& newCameraComponent = Application::m_coordinator->getComponent(cameraId); - newCameraComponent.resize(static_cast(m_contentSize.x), - static_cast(m_contentSize.y)); + auto& app = getApp(); + scene::Scene& scene = app.getSceneManager().getScene(m_sceneId); + + const auto floor = EntityFactory3D::createCube({0.0f + offset.x, 0.0f + offset.y, 0.0f + offset.z}, + {20.0f, 1.0f, 20.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f, 1.0f}); + scene.addEntity(floor); + + const auto& catalog = nexo::assets::AssetCatalog::getInstance(); + const assets::AssetLocation grassTexture("my_package::grass@Textures"); + const auto grassAssetRef = catalog.getAsset(grassTexture).as(); + auto& materialComp = nexo::Application::m_coordinator->getComponent(floor); + const auto materialAssetRef = materialComp.material; + const auto materialAsset = materialAssetRef.lock(); + materialAsset->getData()->albedoTexture = grassAssetRef; + + addModelToScene("my_package::Frog@Models", {0.0f + offset.x, 1.0f + offset.y, 0.0f + offset.z}, + {0.5f, 0.5f, 0.5f}); + + addModelToScene("my_package::Tree@Models", {5.0f + offset.x, 0.5f + offset.y, -3.0f + offset.z}, + {2.44f, 2.44f, 2.44f}); + + addModelToScene("my_package::Bench@Models", {-6.0f + offset.x, 2.0f + offset.y, -6.5f + offset.z}); + + addModelToScene("my_package::Log@Models", {-5.0f + offset.x, 0.5f + offset.y, 5.0f + offset.z}, + {2.3f, 2.3f, 2.3f}); } -} + +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp index d7d21c021..dfc8e4f92 100644 --- a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp +++ b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp @@ -13,25 +13,25 @@ /////////////////////////////////////////////////////////////////////////////// #include "EditorScene.hpp" -#include "context/Selector.hpp" -#include "context/ActionManager.hpp" #include "components/Uuid.hpp" +#include "context/ActionManager.hpp" +#include "context/Selector.hpp" namespace nexo::editor { static void hideCallback() { - auto &selector = Selector::get(); + auto &selector = Selector::get(); const auto &selectedEntities = selector.getSelectedEntities(); - auto& actionManager = ActionManager::get(); - auto actionGroup = ActionManager::createActionGroup(); + auto &actionManager = ActionManager::get(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : selectedEntities) { - auto &renderComponent = Application::m_coordinator->getComponent(entity); - auto beforeState = renderComponent.save(); + auto &renderComponent = Application::m_coordinator->getComponent(entity); + auto beforeState = renderComponent.save(); renderComponent.isRendered = !renderComponent.isRendered; - auto afterState = renderComponent.save(); - actionGroup->addAction(std::make_unique>( - entity, beforeState, afterState)); + auto afterState = renderComponent.save(); + actionGroup->addAction( + std::make_unique>(entity, beforeState, afterState)); } actionManager.recordAction(std::move(actionGroup)); selector.clearSelection(); @@ -39,8 +39,8 @@ namespace nexo::editor { void EditorScene::selectAllCallback() { - auto &selector = Selector::get(); - auto &app = getApp(); + auto &selector = Selector::get(); + auto &app = getApp(); const auto &scene = app.getSceneManager().getScene(m_sceneId); selector.clearSelection(); @@ -49,26 +49,26 @@ namespace nexo::editor { if (static_cast(entity) == m_editorCamera) continue; // Skip editor camera const auto uuidComponent = Application::m_coordinator->tryGetComponent(entity); - if (uuidComponent) - selector.addToSelection(uuidComponent->get().uuid, static_cast(entity)); + if (uuidComponent) selector.addToSelection(uuidComponent->get().uuid, static_cast(entity)); } m_windowState = m_gizmoState; } void EditorScene::hideAllButSelectionCallback() const { - auto &app = getApp(); + auto &app = getApp(); const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); const auto &selector = Selector::get(); - auto &actionManager = ActionManager::get(); - auto actionGroup = ActionManager::createActionGroup(); + auto &actionManager = ActionManager::get(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : entities) { - if (Application::m_coordinator->entityHasComponent(entity) && !selector.isEntitySelected(static_cast(entity))) { + if (Application::m_coordinator->entityHasComponent(entity) && + !selector.isEntitySelected(static_cast(entity))) { auto &renderComponent = Application::m_coordinator->getComponent(entity); if (renderComponent.isRendered) { - auto beforeState = renderComponent.save(); + auto beforeState = renderComponent.save(); renderComponent.isRendered = false; - auto afterState = renderComponent.save(); + auto afterState = renderComponent.save(); actionGroup->addAction(std::make_unique>( entity, beforeState, afterState)); } @@ -79,13 +79,12 @@ namespace nexo::editor { void EditorScene::deleteCallback() { - auto &selector = Selector::get(); + auto &selector = Selector::get(); const auto &selectedEntities = selector.getSelectedEntities(); - auto &app = getApp(); - auto& actionManager = ActionManager::get(); + auto &app = getApp(); + auto &actionManager = ActionManager::get(); - if (selectedEntities.empty()) - return; + if (selectedEntities.empty()) return; if (selectedEntities.size() > 1) { auto actionGroup = ActionManager::createActionGroup(); @@ -104,20 +103,19 @@ namespace nexo::editor { this->m_windowState = m_globalState; } - void EditorScene::unhideAllCallback() const { - auto &app = getApp(); + auto &app = getApp(); const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); - auto &actionManager = ActionManager::get(); - auto actionGroup = ActionManager::createActionGroup(); + auto &actionManager = ActionManager::get(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : entities) { if (Application::m_coordinator->entityHasComponent(entity)) { auto &renderComponent = Application::m_coordinator->getComponent(entity); if (!renderComponent.isRendered) { - auto beforeState = renderComponent.save(); + auto beforeState = renderComponent.save(); renderComponent.isRendered = true; - auto afterState = renderComponent.save(); + auto afterState = renderComponent.save(); actionGroup->addAction(std::make_unique>( entity, beforeState, afterState)); } @@ -137,40 +135,49 @@ namespace nexo::editor { .description("Shift context") .key("Shift") .modifier(true) - .addChild( - Command::create() - .description("Add entity") - .key("A") - .onPressed([this]{ this->m_popupManager.openPopup("Add new entity popup"); }) - .build() - ) - .build() - ); + .addChild(Command::create() + .description("Add entity") + .key("A") + .onPressed([this] { this->m_popupManager.openPopup("Add new entity popup"); }) + .build()) + .build()); // Control context - m_globalState.registerCommand( - Command::create() - .description("Control context") - .key("Ctrl") - .modifier(true) - .addChild( - Command::create() - .description("Unhide all") - .key("H") - .onPressed([this]() { this->unhideAllCallback(); }) - .build() - ) - .build() - ); + m_globalState.registerCommand(Command::create() + .description("Control context") + .key("Ctrl") + .modifier(true) + .addChild(Command::create() + .description("Unhide all") + .key("H") + .onPressed([this]() { this->unhideAllCallback(); }) + .build()) + .build()); // Select all - m_globalState.registerCommand( - Command::create() - .description("Select all") - .key("A") - .onPressed([this]{ this->selectAllCallback(); }) - .build() - ); + m_globalState.registerCommand(Command::create() + .description("Select all") + .key("A") + .onPressed([this] { this->selectAllCallback(); }) + .build()); + + m_globalState.registerCommand(Command::create() + .description("Start previous timecode") + .key("Left") + .onPressed([this] { this->skipVideosToPreviousKeyframe(); }) + .build()); + m_globalState.registerCommand(Command::create() + .description("Start next timecode") + .key("Right") + .onPressed([this] { this->skipVideosToNextKeyframe(); }) + .build()); + m_globalState.registerCommand(Command::create() + .description("Spawn balls") + .key("L") + .onPressed([this] { + this->spawnBallsScene(glm::vec3{-60.0f, 0.0f, 0.0f}); + }) + .build()); } void EditorScene::setupGizmoState() @@ -179,88 +186,68 @@ namespace nexo::editor { m_gizmoState = {static_cast(EditorState::GIZMO)}; // Delete - m_gizmoState.registerCommand( - Command::create() - .description("Delete") - .key("Delete") - .onPressed([this]() { this->deleteCallback(); }) - .build() - ); + m_gizmoState.registerCommand(Command::create() + .description("Delete") + .key("Delete") + .onPressed([this]() { this->deleteCallback(); }) + .build()); // Hide - m_gizmoState.registerCommand( - Command::create() - .description("Hide") - .key("H") - .onPressed(&hideCallback) - .build() - ); + m_gizmoState.registerCommand(Command::create().description("Hide").key("H").onPressed(&hideCallback).build()); // Translate - m_gizmoState.registerCommand( - Command::create() - .description("Translate") - .key("G") - .onPressed([this]{ - this->m_windowState = m_gizmoTranslateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }) - .build() - ); + m_gizmoState.registerCommand(Command::create() + .description("Translate") + .key("G") + .onPressed([this] { + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }) + .build()); // Rotate - m_gizmoState.registerCommand( - Command::create() - .description("Rotate") - .key("R") - .onPressed([this]{ - this->m_windowState = m_gizmoRotateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }) - .build() - ); + m_gizmoState.registerCommand(Command::create() + .description("Rotate") + .key("R") + .onPressed([this] { + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }) + .build()); // Scale - m_gizmoState.registerCommand( - Command::create() - .description("Scale") - .key("S") - .onPressed([this]{ - this->m_windowState = m_gizmoScaleState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }) - .build() - ); + m_gizmoState.registerCommand(Command::create() + .description("Scale") + .key("S") + .onPressed([this] { + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }) + .build()); // Shift context - m_gizmoState.registerCommand( - Command::create() - .description("Shift context") - .key("Shift") - .modifier(true) - .addChild( - Command::create() - .description("Toggle snapping") - .key("S") - .onPressed([this]{ - m_snapTranslateOn = true; - m_snapRotateOn = true; - }) - .onReleased([this]{ - m_snapTranslateOn = false; - m_snapRotateOn = false; - }) - .build() - ) - .addChild( - Command::create() - .description("Hide all but selection") - .key("H") - .onPressed([this]{ this->hideAllButSelectionCallback(); }) - .build() - ) - .build() - ); + m_gizmoState.registerCommand(Command::create() + .description("Shift context") + .key("Shift") + .modifier(true) + .addChild(Command::create() + .description("Toggle snapping") + .key("S") + .onPressed([this] { + m_snapTranslateOn = true; + m_snapRotateOn = true; + }) + .onReleased([this] { + m_snapTranslateOn = false; + m_snapRotateOn = false; + }) + .build()) + .addChild(Command::create() + .description("Hide all but selection") + .key("H") + .onPressed([this] { this->hideAllButSelectionCallback(); }) + .build()) + .build()); } void EditorScene::setupGizmoTranslateState() @@ -269,58 +256,50 @@ namespace nexo::editor { m_gizmoTranslateState = {static_cast(EditorState::GIZMO_TRANSLATE)}; // Universal - m_gizmoTranslateState.registerCommand( - Command::create() - .description("Universal") - .key("U") - .onPressed([this]{ - this->m_windowState = m_gizmoState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; - }) - .build() - ); + m_gizmoTranslateState.registerCommand(Command::create() + .description("Universal") + .key("U") + .onPressed([this] { + this->m_windowState = m_gizmoState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; + }) + .build()); // Translate - m_gizmoTranslateState.registerCommand( - Command::create() - .description("Translate") - .key("G") - .onPressed([this]{ - this->m_windowState = m_gizmoTranslateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }) - .onRepeat([this]{ - if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) - this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; - else - this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; - }) - .build() - ); + m_gizmoTranslateState.registerCommand(Command::create() + .description("Translate") + .key("G") + .onPressed([this] { + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }) + .onRepeat([this] { + if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) + this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; + else + this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; + }) + .build()); // Rotate - m_gizmoTranslateState.registerCommand( - Command::create() - .description("Rotate") - .key("R") - .onPressed([this]{ - this->m_windowState = m_gizmoRotateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }) - .build() - ); + m_gizmoTranslateState.registerCommand(Command::create() + .description("Rotate") + .key("R") + .onPressed([this] { + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }) + .build()); // Scale - m_gizmoTranslateState.registerCommand( - Command::create() - .description("Scale") - .key("S") - .onPressed([this]{ - this->m_windowState = m_gizmoScaleState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }) - .build() - ); + m_gizmoTranslateState.registerCommand(Command::create() + .description("Scale") + .key("S") + .onPressed([this] { + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }) + .build()); // Shift context m_gizmoTranslateState.registerCommand( @@ -328,104 +307,76 @@ namespace nexo::editor { .description("Shift context") .key("Shift") .modifier(true) - .addChild( - Command::create() - .description("Exclude X") - .key("X") - .onPressed([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_X); - }) - .onReleased([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_X); - }) - .build() - ) - .addChild( - Command::create() - .description("Exclude Y") - .key("Y") - .onPressed([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Y); - }) - .onReleased([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Y); - }) - .build() - ) - .addChild( - Command::create() - .description("Exclude Z") - .key("Z") - .onPressed([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Z); - }) - .onReleased([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Z); - }) - .build() - ) - .addChild( - Command::create() - .description("Toggle snapping") - .key("S") - .onPressed([this]{ - m_snapTranslateOn = true; - }) - .onReleased([this]{ - m_snapTranslateOn = false; - }) - .build() - ) - .build() - ); + .addChild(Command::create() + .description("Exclude X") + .key("X") + .onPressed([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_X); + }) + .onReleased([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_X); + }) + .build()) + .addChild(Command::create() + .description("Exclude Y") + .key("Y") + .onPressed([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Y); + }) + .onReleased([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Y); + }) + .build()) + .addChild(Command::create() + .description("Exclude Z") + .key("Z") + .onPressed([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Z); + }) + .onReleased([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Z); + }) + .build()) + .addChild(Command::create() + .description("Toggle snapping") + .key("S") + .onPressed([this] { m_snapTranslateOn = true; }) + .onReleased([this] { m_snapTranslateOn = false; }) + .build()) + .build()); // Lock X m_gizmoTranslateState.registerCommand( Command::create() .description("Lock X") .key("X") - .onPressed([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_X; - }) - .onReleased([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }) - .build() - ); + .onPressed([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_X; }) + .onReleased([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }) + .build()); // Lock Y m_gizmoTranslateState.registerCommand( Command::create() .description("Lock Y") .key("Y") - .onPressed([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Y; - }) - .onReleased([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }) - .build() - ); + .onPressed([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Y; }) + .onReleased([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }) + .build()); // Lock Z m_gizmoTranslateState.registerCommand( Command::create() .description("Lock Z") .key("Z") - .onPressed([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Z; - }) - .onReleased([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }) - .build() - ); + .onPressed([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Z; }) + .onReleased([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }) + .build()); } void EditorScene::setupGizmoRotateState() @@ -434,58 +385,50 @@ namespace nexo::editor { m_gizmoRotateState = {static_cast(EditorState::GIZMO_ROTATE)}; // Universal - m_gizmoRotateState.registerCommand( - Command::create() - .description("Universal") - .key("U") - .onPressed([this]{ - this->m_windowState = m_gizmoState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; - }) - .build() - ); + m_gizmoRotateState.registerCommand(Command::create() + .description("Universal") + .key("U") + .onPressed([this] { + this->m_windowState = m_gizmoState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; + }) + .build()); // Rotate - m_gizmoRotateState.registerCommand( - Command::create() - .description("Rotate") - .key("R") - .onPressed([this]{ - this->m_windowState = m_gizmoRotateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }) - .onRepeat([this]{ - if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) - this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; - else - this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; - }) - .build() - ); + m_gizmoRotateState.registerCommand(Command::create() + .description("Rotate") + .key("R") + .onPressed([this] { + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }) + .onRepeat([this] { + if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) + this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; + else + this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; + }) + .build()); // Translate - m_gizmoRotateState.registerCommand( - Command::create() - .description("Translate") - .key("G") - .onPressed([this]{ - this->m_windowState = m_gizmoTranslateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }) - .build() - ); + m_gizmoRotateState.registerCommand(Command::create() + .description("Translate") + .key("G") + .onPressed([this] { + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }) + .build()); // Scale - m_gizmoRotateState.registerCommand( - Command::create() - .description("Scale") - .key("S") - .onPressed([this]{ - this->m_windowState = m_gizmoScaleState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }) - .build() - ); + m_gizmoRotateState.registerCommand(Command::create() + .description("Scale") + .key("S") + .onPressed([this] { + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }) + .build()); // Shift context m_gizmoRotateState.registerCommand( @@ -493,104 +436,76 @@ namespace nexo::editor { .description("Shift context") .key("Shift") .modifier(true) - .addChild( - Command::create() - .description("Exclude X") - .key("X") - .onPressed([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_X); - }) - .onReleased([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_X); - }) - .build() - ) - .addChild( - Command::create() - .description("Exclude Y") - .key("Y") - .onPressed([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Y); - }) - .onReleased([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Y); - }) - .build() - ) - .addChild( - Command::create() - .description("Exclude Z") - .key("Z") - .onPressed([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Z); - }) - .onReleased([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation | ImGuizmo::OPERATION::ROTATE_Z); - }) - .build() - ) - .addChild( - Command::create() - .description("Toggle snapping") - .key("S") - .onPressed([this]{ - m_snapRotateOn = true; - }) - .onReleased([this]{ - m_snapRotateOn = false; - }) - .build() - ) - .build() - ); + .addChild(Command::create() + .description("Exclude X") + .key("X") + .onPressed([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_X); + }) + .onReleased([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_X); + }) + .build()) + .addChild(Command::create() + .description("Exclude Y") + .key("Y") + .onPressed([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Y); + }) + .onReleased([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Y); + }) + .build()) + .addChild(Command::create() + .description("Exclude Z") + .key("Z") + .onPressed([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Z); + }) + .onReleased([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation | ImGuizmo::OPERATION::ROTATE_Z); + }) + .build()) + .addChild(Command::create() + .description("Toggle snapping") + .key("S") + .onPressed([this] { m_snapRotateOn = true; }) + .onReleased([this] { m_snapRotateOn = false; }) + .build()) + .build()); // Lock X m_gizmoRotateState.registerCommand( Command::create() .description("Lock X") .key("X") - .onPressed([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_X; - }) - .onReleased([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }) - .build() - ); + .onPressed([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_X; }) + .onReleased([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }) + .build()); // Lock Y m_gizmoRotateState.registerCommand( Command::create() .description("Lock Y") .key("Y") - .onPressed([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Y; - }) - .onReleased([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }) - .build() - ); + .onPressed([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Y; }) + .onReleased([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }) + .build()); // Lock Z m_gizmoRotateState.registerCommand( Command::create() .description("Lock Z") .key("Z") - .onPressed([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Z; - }) - .onReleased([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }) - .build() - ); + .onPressed([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Z; }) + .onReleased([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }) + .build()); } void EditorScene::setupGizmoScaleState() @@ -599,58 +514,50 @@ namespace nexo::editor { m_gizmoScaleState = {static_cast(EditorState::GIZMO_SCALE)}; // Universal - m_gizmoScaleState.registerCommand( - Command::create() - .description("Universal") - .key("U") - .onPressed([this]{ - this->m_windowState = m_gizmoState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; - }) - .build() - ); + m_gizmoScaleState.registerCommand(Command::create() + .description("Universal") + .key("U") + .onPressed([this] { + this->m_windowState = m_gizmoState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; + }) + .build()); // Scale - m_gizmoScaleState.registerCommand( - Command::create() - .description("Scale") - .key("S") - .onPressed([this]{ - this->m_windowState = m_gizmoScaleState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }) - .onRepeat([this]{ - if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) - this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; - else - this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; - }) - .build() - ); + m_gizmoScaleState.registerCommand(Command::create() + .description("Scale") + .key("S") + .onPressed([this] { + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }) + .onRepeat([this] { + if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) + this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; + else + this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; + }) + .build()); // Translate - m_gizmoScaleState.registerCommand( - Command::create() - .description("Translate") - .key("G") - .onPressed([this]{ - this->m_windowState = m_gizmoTranslateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }) - .build() - ); + m_gizmoScaleState.registerCommand(Command::create() + .description("Translate") + .key("G") + .onPressed([this] { + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }) + .build()); // Rotate - m_gizmoScaleState.registerCommand( - Command::create() - .description("Rotate") - .key("R") - .onPressed([this]{ - this->m_windowState = m_gizmoRotateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }) - .build() - ); + m_gizmoScaleState.registerCommand(Command::create() + .description("Rotate") + .key("R") + .onPressed([this] { + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }) + .build()); // Shift context m_gizmoScaleState.registerCommand( @@ -658,92 +565,70 @@ namespace nexo::editor { .description("Shift context") .key("Shift") .modifier(true) - .addChild( - Command::create() - .description("Exclude X") - .key("X") - .onPressed([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_X); - }) - .onReleased([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_X); - }) - .build() - ) - .addChild( - Command::create() - .description("Exclude Y") - .key("Y") - .onPressed([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Y); - }) - .onReleased([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Y); - }) - .build() - ) - .addChild( - Command::create() - .description("Exclude Z") - .key("Z") - .onPressed([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Z); - }) - .onReleased([this]{ - m_currentGizmoOperation = - static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Z); - }) - .build() - ) - .build() - ); + .addChild(Command::create() + .description("Exclude X") + .key("X") + .onPressed([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_X); + }) + .onReleased([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_X); + }) + .build()) + .addChild(Command::create() + .description("Exclude Y") + .key("Y") + .onPressed([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Y); + }) + .onReleased([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Y); + }) + .build()) + .addChild(Command::create() + .description("Exclude Z") + .key("Z") + .onPressed([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Z); + }) + .onReleased([this] { + m_currentGizmoOperation = static_cast( + m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Z); + }) + .build()) + .build()); // Lock X m_gizmoScaleState.registerCommand( Command::create() .description("Lock X") .key("X") - .onPressed([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_X; - }) - .onReleased([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }) - .build() - ); + .onPressed([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_X; }) + .onReleased([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }) + .build()); // Lock Y m_gizmoScaleState.registerCommand( Command::create() .description("Lock Y") .key("Y") - .onPressed([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Y; - }) - .onReleased([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }) - .build() - ); + .onPressed([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Y; }) + .onReleased([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }) + .build()); // Lock Z m_gizmoScaleState.registerCommand( Command::create() .description("Lock Z") .key("Z") - .onPressed([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Z; - }) - .onReleased([this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }) - .build() - ); + .onPressed([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Z; }) + .onReleased([this] { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }) + .build()); } void EditorScene::setupShortcuts() @@ -756,4 +641,4 @@ namespace nexo::editor { setupGizmoRotateState(); setupGizmoScaleState(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index 00a44674a..a241418ba 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -12,24 +12,23 @@ // /////////////////////////////////////////////////////////////////////////////// +#include #include "EditorScene.hpp" -#include "context/Selector.hpp" #include "EntityFactory3D.hpp" -#include "LightFactory.hpp" #include "IconsFontAwesome.h" #include "ImNexo/Panels.hpp" -#include "utils/EditorProps.hpp" -#include "context/actions/EntityActions.hpp" +#include "LightFactory.hpp" #include "context/ActionManager.hpp" -#include +#include "context/Selector.hpp" +#include "context/actions/EntityActions.hpp" +#include "utils/EditorProps.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void EditorScene::renderNoActiveCamera() const { // No active camera, render the text at the center of the screen const ImVec2 textSize = ImGui::CalcTextSize("No active camera"); - const auto textPos = ImVec2((m_contentSize.x - textSize.x) / 2, (m_contentSize.y - textSize.y) / 2); + const auto textPos = ImVec2((m_contentSize.x - textSize.x) / 2, (m_contentSize.y - textSize.y) / 2); ImGui::SetCursorScreenPos(textPos); ImGui::Text("No active camera"); @@ -37,47 +36,37 @@ namespace nexo::editor void EditorScene::renderNewEntityPopup() { - auto& app = Application::getInstance(); + auto& app = Application::getInstance(); auto& sceneManager = app.getSceneManager(); // --- Primitives submenu --- - if (ImGui::BeginMenu("Primitives")) - { - if (ImGui::MenuItem("Cube")) - { - const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, { - 0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, - 1.0f - }); + if (ImGui::BeginMenu("Primitives")) { + if (ImGui::MenuItem("Cube")) { + const ecs::Entity newCube = + EntityFactory3D::createCube({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); sceneManager.getScene(m_sceneId).addEntity(newCube); auto createAction = std::make_unique(newCube); ActionManager::get().recordAction(std::move(createAction)); } - if (ImGui::MenuItem("Sphere")) - { + if (ImGui::MenuItem("Sphere")) { m_popupManager.openPopup("Sphere creation popup"); } - if (ImGui::MenuItem("Cylinder")) - { + if (ImGui::MenuItem("Cylinder")) { m_popupManager.openPopup("Cylinder creation popup"); } - if (ImGui::MenuItem("Pyramid")) - { - const ecs::Entity newPyramid = EntityFactory3D::createPyramid({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, { - 0.05f * 1.5, 0.09f * 1.15, - 0.13f * 1.25, 1.0f - }); + if (ImGui::MenuItem("Pyramid")) { + const ecs::Entity newPyramid = + EntityFactory3D::createPyramid({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); sceneManager.getScene(m_sceneId).addEntity(newPyramid); auto createAction = std::make_unique(newPyramid); ActionManager::get().recordAction(std::move(createAction)); } - if (ImGui::MenuItem("Tetrahedron")) - { - const ecs::Entity newTetrahedron = EntityFactory3D::createTetrahedron( - {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); + if (ImGui::MenuItem("Tetrahedron")) { + const ecs::Entity newTetrahedron = + EntityFactory3D::createTetrahedron({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); sceneManager.getScene(m_sceneId).addEntity(newTetrahedron); auto createAction = std::make_unique(newTetrahedron); ActionManager::get().recordAction(std::move(createAction)); @@ -86,31 +75,26 @@ namespace nexo::editor } // --- Model item (with file‑dialog) --- - if (ImGui::MenuItem("Model")) - { - //TODO: import model + if (ImGui::MenuItem("Model")) { + // TODO: import model } // --- Lights submenu --- - if (ImGui::BeginMenu("Lights")) - { - if (ImGui::MenuItem("Directional")) - { + if (ImGui::BeginMenu("Lights")) { + if (ImGui::MenuItem("Directional")) { const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.0f, -1.0f, 0.0f}); sceneManager.getScene(m_sceneId).addEntity(directionalLight); auto createAction = std::make_unique(directionalLight); ActionManager::get().recordAction(std::move(createAction)); } - if (ImGui::MenuItem("Point")) - { + if (ImGui::MenuItem("Point")) { const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); sceneManager.getScene(m_sceneId).addEntity(pointLight); auto createAction = std::make_unique(pointLight); ActionManager::get().recordAction(std::move(createAction)); } - if (ImGui::MenuItem("Spot")) - { + if (ImGui::MenuItem("Spot")) { const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); sceneManager.getScene(m_sceneId).addEntity(spotLight); @@ -121,27 +105,22 @@ namespace nexo::editor } // --- Camera item --- - if (ImGui::MenuItem("Camera")) - { - m_popupManager.openPopupWithCallback("Popup camera inspector", [this]() - { - ImNexo::CameraInspector(this->m_sceneId); - }, ImVec2(1440, 900)); + if (ImGui::MenuItem("Camera")) { + m_popupManager.openPopupWithCallback( + "Popup camera inspector", [this]() { ImNexo::CameraInspector(this->m_sceneId); }, ImVec2(1440, 900)); } PopupManager::endPopup(); } void EditorScene::renderView() { - auto &cameraComponent = Application::m_coordinator->getComponent(m_activeCamera); - if (!cameraComponent.m_renderTarget) - return; + auto& cameraComponent = Application::m_coordinator->getComponent(m_activeCamera); + if (!cameraComponent.m_renderTarget) return; // Resize handling - if (const glm::vec2 renderTargetSize = cameraComponent.m_renderTarget->getSize(); !cameraComponent. - viewportLocked && (m_contentSize.x > 0 && m_contentSize.y > 0) - && (m_contentSize.x != renderTargetSize.x || m_contentSize.y != renderTargetSize.y)) - { + if (const glm::vec2 renderTargetSize = cameraComponent.m_renderTarget->getSize(); + !cameraComponent.viewportLocked && (m_contentSize.x > 0 && m_contentSize.y > 0) && + (m_contentSize.x != renderTargetSize.x || m_contentSize.y != renderTargetSize.y)) { cameraComponent.resize(static_cast(m_contentSize.x), static_cast(m_contentSize.y)); } @@ -153,32 +132,34 @@ namespace nexo::editor handleDropTarget(); const ImVec2 viewportMin = ImGui::GetItemRectMin(); const ImVec2 viewportMax = ImGui::GetItemRectMax(); - m_viewportBounds[0] = viewportMin; - m_viewportBounds[1] = viewportMax; + m_viewportBounds[0] = viewportMin; + m_viewportBounds[1] = viewportMax; } void EditorScene::show() { // Handle deferred dock split before rendering - if (m_shouldSplitDock && !m_gameWindowNameToSplit.empty()) - { - const std::string currentWindowName = m_windowName; - const ImGuiWindow *currentImGuiWindow = ImGui::FindWindowByName((currentWindowName + NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(m_sceneId)).c_str()); + if (m_shouldSplitDock && !m_gameWindowNameToSplit.empty()) { + const std::string currentWindowName = m_windowName; + const ImGuiWindow* currentImGuiWindow = ImGui::FindWindowByName( + std::format("{}{}{}", currentWindowName, NEXO_WND_USTRID_DEFAULT_SCENE, m_sceneId).c_str()); - if (currentImGuiWindow && currentImGuiWindow->DockId) - { + if (currentImGuiWindow && currentImGuiWindow->DockId) { const ImGuiID editorDockId = currentImGuiWindow->DockId; - ImGuiID rightNode, leftNode; + ImGuiID rightNode = 0; + ImGuiID leftNode = 0; - if (ImGui::DockBuilderSplitNode(editorDockId, ImGuiDir_Right, 0.5f, &rightNode, &leftNode)) - { + if (ImGui::DockBuilderSplitNode(editorDockId, ImGuiDir_Right, 0.5f, &rightNode, &leftNode)) { // Dock the windows - ImGui::DockBuilderDockWindow((currentWindowName + NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(m_sceneId)).c_str(), leftNode); + ImGui::DockBuilderDockWindow( + std::format("{}{}{}", currentWindowName, NEXO_WND_USTRID_DEFAULT_SCENE, m_sceneId).c_str(), + leftNode); ImGui::DockBuilderDockWindow(m_gameWindowNameToSplit.c_str(), rightNode); ImGui::DockBuilderFinish(editorDockId); // Update registry - m_windowRegistry.setDockId(currentWindowName + NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(m_sceneId), leftNode); + m_windowRegistry.setDockId( + std::format("{}{}{}", currentWindowName, NEXO_WND_USTRID_DEFAULT_SCENE, m_sceneId), leftNode); m_windowRegistry.setDockId(m_gameWindowNameToSplit, rightNode); } } @@ -190,57 +171,45 @@ namespace nexo::editor ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::SetNextWindowSizeConstraints(ImVec2(480, 270), ImVec2(1920, 1080)); auto& selector = Selector::get(); - m_windowName = selector.getUiHandle(m_sceneUuid, std::string(ICON_FA_GLOBE) + " " + m_windowName); - const std::string &sceneWindowName = std::format("{}{}{}", - m_windowName, - NEXO_WND_USTRID_DEFAULT_SCENE, - m_sceneId); + m_windowName = selector.getUiHandle(m_sceneUuid, std::string(ICON_FA_GLOBE) + " " + m_windowName); + const std::string& sceneWindowName = + std::format("{}{}{}", m_windowName, NEXO_WND_USTRID_DEFAULT_SCENE, m_sceneId); m_wasVisibleLastFrame = m_isVisibleInDock; - m_isVisibleInDock = false; + m_isVisibleInDock = false; if (ImGui::Begin(sceneWindowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoScrollWithMouse)) - { - const std::string renderName = std::format("{}{}", - NEXO_WND_USTRID_DEFAULT_SCENE, - m_sceneId - ); + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoTitleBar)) { + if (ImGui::IsKeyPressed(ImGuiKey_P)) showToolbar = !showToolbar; + const std::string renderName = std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, m_sceneId); beginRender(renderName); auto& app = getApp(); app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused); - if (m_focused && selector.getSelectedScene() != m_sceneId) - { + if (m_focused && selector.getSelectedScene() != m_sceneId) { selector.setSelectedScene(m_sceneId); selector.clearSelection(); } - if (m_activeCamera == -1) - { + if (m_activeCamera == -1) { renderNoActiveCamera(); - } - else - { + } else { renderView(); renderGizmo(); - renderToolbar(); + if (showToolbar) renderToolbar(); } - if (m_popupManager.showPopup("Add new entity popup")) - { + if (m_popupManager.showPopup("Add new entity popup")) { renderNewEntityPopup(); } - if (m_popupManager.showPopup("Sphere creation popup")) - { + if (m_popupManager.showPopup("Sphere creation popup")) { ImNexo::PrimitiveCustomizationMenu(m_sceneId, SPHERE); } - if (m_popupManager.showPopup("Cylinder creation popup")) - { + if (m_popupManager.showPopup("Cylinder creation popup")) { ImNexo::PrimitiveCustomizationMenu(m_sceneId, CYLINDER); } } ImGui::End(); ImGui::PopStyleVar(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EditorScene/Timecode.cpp b/editor/src/DocumentWindows/EditorScene/Timecode.cpp new file mode 100644 index 000000000..132a185b1 --- /dev/null +++ b/editor/src/DocumentWindows/EditorScene/Timecode.cpp @@ -0,0 +1,74 @@ +//// Timecode.cpp ////////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz +// zzzzzzz zzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz +// zzz zzz zzz z zzzz zzzz zzzz zzzz +// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz +// +// Author: Mehdy MORVAN +// Date: 11/09/2025 +// Description: Timecode system for timed playback control +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "EditorScene.hpp" + +namespace nexo::editor { + + void EditorScene::handleTimecodeUpdate() + { + if (!m_isTimecodeActive || m_timecodeSeconds.empty()) { + return; + } + + m_timecodeElapsed += ImGui::GetIO().DeltaTime; + + if (m_currentTimecodeIndex < static_cast(m_timecodeSeconds.size())) { + float currentDuration = m_timecodeSeconds[m_currentTimecodeIndex]; + + if (m_timecodeElapsed >= currentDuration) { + m_isTimecodeActive = false; + m_timecodeElapsed = 0.0f; + + auto &app = getApp(); + app.setGameState(nexo::GameState::EDITOR_MODE); + + m_currentTimecodeIndex = (m_currentTimecodeIndex + 1) % static_cast(m_timecodeSeconds.size()); + } + } + } + + void EditorScene::skipVideosToPreviousKeyframe() const + { + // app.setGameState(nexo::GameState::PLAY_MODE); + for (auto &entity : nexo::Application::m_coordinator->getAllEntitiesWith()) { + if (!nexo::Application::m_coordinator->entityHasComponent(entity)) { + continue; + } + const auto &sceneTag = nexo::Application::m_coordinator->getComponent(entity); + if (sceneTag.id != static_cast(m_sceneId)) { + continue; + } + auto &videoComponent = nexo::Application::m_coordinator->getComponent(entity); + videoComponent.skipToPreviousKeyframe(); + } + } + + void EditorScene::skipVideosToNextKeyframe() const + { + // app.setGameState(nexo::GameState::PLAY_MODE); + for (auto &entity : nexo::Application::m_coordinator->getAllEntitiesWith()) { + if (!nexo::Application::m_coordinator->entityHasComponent(entity)) { + continue; + } + const auto &sceneTag = nexo::Application::m_coordinator->getComponent(entity); + if (sceneTag.id != static_cast(m_sceneId)) { + continue; + } + auto &videoComponent = nexo::Application::m_coordinator->getComponent(entity); + videoComponent.skipToNextKeyframe(); + } + } +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp index 9956c197f..e7dcd5e50 100644 --- a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp +++ b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp @@ -8,21 +8,21 @@ // // Author: Mehdy MORVAN // Date: 28/04/2025 -// Description: Source file for the toobal rendering of the editor scene +// Description: Source file for the toolbar rendering of the editor scene // /////////////////////////////////////////////////////////////////////////////// +#include +#include +#include "../GameWindow/GameWindow.hpp" +#include "Editor.hpp" #include "EditorScene.hpp" #include "EntityFactory3D.hpp" #include "IconsFontAwesome.h" -#include "context/Selector.hpp" #include "components/Uuid.hpp" -#include "context/actions/EntityActions.hpp" #include "context/ActionManager.hpp" -#include "../GameWindow/GameWindow.hpp" -#include "Editor.hpp" -#include -#include +#include "context/Selector.hpp" +#include "context/actions/EntityActions.hpp" namespace nexo::editor { @@ -30,7 +30,8 @@ namespace nexo::editor { { // Create or focus the game window auto& editor = Editor::getInstance(); - const std::string gameWindowName = std::format("Game View - {}{}{}", m_sceneUuid, NEXO_WND_USTRID_GAME_WINDOW, m_sceneId); + const std::string gameWindowName = + std::format("Game View - {}{}{}", m_sceneUuid, NEXO_WND_USTRID_GAME_WINDOW, m_sceneId); // Check if game window already exists if (const auto gameWindow = editor.getWindow(gameWindowName).lock()) { @@ -38,7 +39,7 @@ namespace nexo::editor { gameWindow->setOpened(true); } else { // Get current EditorScene window's dock ID for docking the game window - const std::string currentWindowName = m_windowName; + const std::string currentWindowName = m_windowName; const ImGuiWindow* currentImGuiWindow = ImGui::FindWindowByName(currentWindowName.c_str()); if (currentImGuiWindow && currentImGuiWindow->DockId) { @@ -56,20 +57,36 @@ namespace nexo::editor { newGameWindow->setOpened(true); // Schedule dock split for next frame - m_shouldSplitDock = true; + m_shouldSplitDock = true; m_gameWindowNameToSplit = gameWindowName; // Also schedule focus for after the split m_shouldFocusGameWindow = true; - m_gameWindowToFocus = gameWindowName; + m_gameWindowToFocus = gameWindowName; } } } } + void EditorScene::spawnBallsScene(const glm::vec3& offset) const + { + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_real_distribution dis(0.0f, 1.0f); + + // Balls + for (int i = 0; i < 50; ++i) { + float x = -3.0f + static_cast(i % 5) * 1.5f; + float z = static_cast((i % 2 == 0) ? 1 : -1) * 0.5f; + glm::vec3 pos = {x, 62.0f + static_cast(i), z}; + glm::vec4 color = {1.0f, dis(gen), dis(gen), 1.0f}; + createEntityWithPhysic(pos + offset, {0.4f, 0.4f, 0.4f}, {0, 0, 0}, color, system::ShapeType::Sphere, + JPH::EMotionType::Dynamic); + } + } void EditorScene::initialToolbarSetup(const float buttonWidth) const { - ImVec2 toolbarPos = m_windowPos; + ImVec2 toolbarPos = m_windowPos; const ImVec2 contentMin = ImGui::GetWindowContentRegionMin(); toolbarPos.x += contentMin.x + 10.0f; toolbarPos.y += contentMin.y + 20.0f; @@ -80,9 +97,8 @@ namespace nexo::editor { ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.1f, 0.1f, 0.1f, 0.0f)); ImGui::BeginChild("##ToolbarOverlay", toolbarSize, 0, - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoSavedSettings); + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings); ImGui::SetCursorPosY((ImGui::GetWindowHeight() - ImGui::GetFrameHeight()) * 0.5f); @@ -93,120 +109,83 @@ namespace nexo::editor { const std::string& tooltip, const std::vector& gradientStop, bool* rightClicked) { - constexpr float buttonWidth = 35.0f; + constexpr float buttonWidth = 35.0f; constexpr float buttonHeight = 35.0f; const bool clicked = ImNexo::IconGradientButton(uniqueId, icon, ImVec2(buttonWidth, buttonHeight), gradientStop); - if (!tooltip.empty() && ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", tooltip.c_str()); - if (rightClicked != nullptr) - *rightClicked = ImGui::IsItemClicked(ImGuiMouseButton_Right); + if (!tooltip.empty() && ImGui::IsItemHovered()) ImGui::SetTooltip("%s", tooltip.c_str()); + if (rightClicked != nullptr) *rightClicked = ImGui::IsItemClicked(ImGuiMouseButton_Right); return clicked; } void EditorScene::renderPrimitiveSubMenu(const ImVec2& primitiveButtonPos, const ImVec2& buttonSize, bool& showPrimitiveMenu) { - auto& app = getApp(); - static const std::vector buttonProps = - { - { - .uniqueId = "cube_primitive", - .icon = ICON_FA_CUBE, - .onClick = [this, &app]() - { - const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); - app.getSceneManager().getScene(this->m_sceneId).addEntity(newCube); - auto createAction = std::make_unique(newCube); - ActionManager::get().recordAction(std::move(createAction)); - }, - .tooltip = "Create Cube" - }, - { - .uniqueId = "sphere_primitive", - .icon = ICON_FA_CIRCLE, - .onClick = [this]() - { - // ImNexo::PrimitiveCustomizationMenu(this->m_sceneId, SPHERE); - this->m_popupManager.openPopup("Sphere creation popup"); - }, - .tooltip = "Create Sphere" - }, - { - .uniqueId = "cylinder_primitive", - .icon = ICON_FA_PLUS, - .onClick = [this]() - { - // ImNexo::PrimitiveCustomizationMenu(this->m_sceneId, CYLINDER); - this->m_popupManager.openPopup("Cylinder creation popup"); - }, - .tooltip = "Create Cylinder" - }, - { - .uniqueId = "pyramid_primitive", - .icon = ICON_FA_PLUS, - .onClick = [this, &app]() - { - const ecs::Entity newPyramid = EntityFactory3D::createPyramid( - {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); - app.getSceneManager().getScene(this->m_sceneId).addEntity(newPyramid); - auto createAction = std::make_unique(newPyramid); - ActionManager::get().recordAction(std::move(createAction)); - }, - .tooltip = "Create Pyramid" - }, - { - .uniqueId = "tetrahedron_primitive", - .icon = ICON_FA_PLUS, - .onClick = [this, &app]() - { - const ecs::Entity newTetrahedron = EntityFactory3D::createTetrahedron( - {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); - app.getSceneManager().getScene(this->m_sceneId).addEntity(newTetrahedron); - auto createAction = std::make_unique(newTetrahedron); - ActionManager::get().recordAction(std::move(createAction)); - }, - .tooltip = "Create Tetrahedron" - }, + auto& app = getApp(); + static const std::vector buttonProps = { + {.uniqueId = "cube_primitive", + .icon = ICON_FA_CUBE, + .onClick = + [this, &app]() { + const ecs::Entity newCube = + EntityFactory3D::createCube({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); + app.getSceneManager().getScene(this->m_sceneId).addEntity(newCube); + auto createAction = std::make_unique(newCube); + ActionManager::get().recordAction(std::move(createAction)); + }, + .tooltip = "Create Cube"}, + {.uniqueId = "sphere_primitive", + .icon = ICON_FA_CIRCLE, + .onClick = [this]() { this->m_popupManager.openPopup("Sphere creation popup"); }, + .tooltip = "Create Sphere"}, + {.uniqueId = "cylinder_primitive", + .icon = ICON_FA_PLUS, + .onClick = [this]() { this->m_popupManager.openPopup("Cylinder creation popup"); }, + .tooltip = "Create Cylinder"}, + {.uniqueId = "pyramid_primitive", + .icon = ICON_FA_PLUS, + .onClick = + [this, &app]() { + const ecs::Entity newPyramid = + EntityFactory3D::createPyramid({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); + app.getSceneManager().getScene(this->m_sceneId).addEntity(newPyramid); + auto createAction = std::make_unique(newPyramid); + ActionManager::get().recordAction(std::move(createAction)); + }, + .tooltip = "Create Pyramid"}, + {.uniqueId = "tetrahedron_primitive", + .icon = ICON_FA_PLUS, + .onClick = + [this, &app]() { + const ecs::Entity newTetrahedron = + EntityFactory3D::createTetrahedron({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); + app.getSceneManager().getScene(this->m_sceneId).addEntity(newTetrahedron); + auto createAction = std::make_unique(newTetrahedron); + ActionManager::get().recordAction(std::move(createAction)); + }, + .tooltip = "Create Tetrahedron"}, }; ImNexo::ButtonDropDown(primitiveButtonPos, buttonSize, buttonProps, showPrimitiveMenu); } void EditorScene::renderSnapSubMenu(const ImVec2& snapButtonPos, const ImVec2& buttonSize, bool& showSnapMenu) { - const std::vector buttonProps = - { - { - .uniqueId = "toggle_translate_snap", - .icon = ICON_FA_TH, - .onClick = [this]() - { - this->m_snapTranslateOn = !this->m_snapTranslateOn; - }, - .onRightClick = [this]() - { - this->m_popupManager.openPopup("Snap settings popup", ImVec2(400, 140)); - }, - .tooltip = "Toggle Translate Snap", - .buttonGradient = m_snapTranslateOn ? m_selectedGradient : m_buttonGradient - }, - { - .uniqueId = "toggle_rotate_snap", - .icon = ICON_FA_BULLSEYE, - .onClick = [this]() - { - this->m_snapRotateOn = !m_snapRotateOn; - }, - .onRightClick = [this]() - { - this->m_popupManager.openPopup("Snap settings popup", ImVec2(400, 140)); - }, - .tooltip = "Toggle Rotate Snap", - .buttonGradient = m_snapRotateOn ? m_selectedGradient : m_buttonGradient - } + const std::vector buttonProps = { + {.uniqueId = "toggle_translate_snap", + .icon = ICON_FA_TH, + .onClick = [this]() { this->m_snapTranslateOn = !this->m_snapTranslateOn; }, + .onRightClick = [this]() { this->m_popupManager.openPopup("Snap settings popup", ImVec2(400, 140)); }, + .tooltip = "Toggle Translate Snap", + .buttonGradient = m_snapTranslateOn ? m_selectedGradient : m_buttonGradient}, + {.uniqueId = "toggle_rotate_snap", + .icon = ICON_FA_BULLSEYE, + .onClick = [this]() { this->m_snapRotateOn = !m_snapRotateOn; }, + .onRightClick = [this]() { this->m_popupManager.openPopup("Snap settings popup", ImVec2(400, 140)); }, + .tooltip = "Toggle Rotate Snap", + .buttonGradient = m_snapRotateOn ? m_selectedGradient : m_buttonGradient} // Snap on scale is kinda strange, the IsOver is not able to detect it, so for now we disable it // { // .uniqueId = "toggle_scale_snap", @@ -228,38 +207,33 @@ namespace nexo::editor { void EditorScene::snapSettingsPopup() { - if (m_popupManager.showPopupModal("Snap settings popup")) - { + if (m_popupManager.showPopupModal("Snap settings popup")) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); ImGui::Indent(10.0f); - if (ImGui::BeginTable("TranslateSnap", 4, - ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn( - "##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn( - "##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn( - "##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn( - "##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + if (ImGui::BeginTable("TranslateSnap", 4, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##X", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Y", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Z", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImNexo::RowDragFloat3("Translate Snap", "X", "Y", "Z", &this->m_snapTranslate.x); ImGui::EndTable(); } - if (ImGui::BeginTable("ScaleAndRotateSnap", 4, - ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn( - "##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn( - "##Value", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + if (ImGui::BeginTable("ScaleAndRotateSnap", 4, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Value", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); // Empty columns to match the first table's structure - ImGui::TableSetupColumn( - "##Empty1", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn( - "##Empty2", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Empty1", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Empty2", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImNexo::RowDragFloat1("Rotate Snap", "", &this->m_angleSnap); ImGui::EndTable(); @@ -268,11 +242,10 @@ namespace nexo::editor { ImGui::Spacing(); constexpr float buttonWidth = 120.0f; - const float windowWidth = ImGui::GetWindowSize().x; + const float windowWidth = ImGui::GetWindowSize().x; ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); - if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) - { + if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) { PopupManager::closePopup(); } ImGui::Unindent(10.0f); @@ -283,31 +256,25 @@ namespace nexo::editor { void EditorScene::gridSettingsPopup() { - if (m_popupManager.showPopupModal("Grid settings")) - { + if (m_popupManager.showPopupModal("Grid settings")) { components::RenderContext::GridParams& gridSettings = Application::m_coordinator->getSingletonComponent().gridParams; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); ImGui::Indent(10.0f); - if (ImGui::BeginTable("GridSettings", 2, - ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn( - "##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn( - "##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + if (ImGui::BeginTable("GridSettings", 2, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##X", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImNexo::RowDragFloat1("Grid size", "", &gridSettings.gridSize, 50.0f, 150.0f); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("The total size of the grid"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("The total size of the grid"); ImNexo::RowDragFloat1("Pixel cell spacing", "", &gridSettings.minPixelsBetweenCells, 0.0f, 100.0f, 0.1f); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Level of detail of internal cells"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Level of detail of internal cells"); ImNexo::RowDragFloat1("Cell size", "", &gridSettings.cellSize, 0.1f, 20.0f, 0.02f); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("The size of the internal cells"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("The size of the internal cells"); ImGui::EndTable(); } @@ -315,11 +282,10 @@ namespace nexo::editor { ImGui::Spacing(); constexpr float buttonWidth = 120.0f; - const float windowWidth = ImGui::GetWindowSize().x; + const float windowWidth = ImGui::GetWindowSize().x; ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); - if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) - { + if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) { PopupManager::closePopup(); } ImGui::Unindent(10.0f); @@ -331,26 +297,22 @@ namespace nexo::editor { void EditorScene::renderEditorCameraToolbarButton() { auto& selector = Selector::get(); - if (m_activeCamera == m_editorCamera) - { - if (renderToolbarButton("editor_camera", ICON_FA_CAMERA, "Edit Editor Camera Setting", m_buttonGradient)) - { - const auto& uuidComponent = Application::m_coordinator->getComponent( - m_editorCamera); + if (m_activeCamera == m_editorCamera) { + if (renderToolbarButton("editor_camera", ICON_FA_CAMERA, "Edit Editor Camera Setting", m_buttonGradient)) { + const auto& uuidComponent = + Application::m_coordinator->getComponent(m_editorCamera); selector.addToSelection(uuidComponent.uuid, m_editorCamera); } - } - else - { - if (renderToolbarButton("switch_back", ICON_FA_EXCHANGE, "Switch back to editor camera", m_buttonGradient)) - { - auto& oldCameraComponent = Application::m_coordinator->getComponent( - m_activeCamera); + } else { + if (renderToolbarButton("switch_back", ICON_FA_EXCHANGE, "Switch back to editor camera", + m_buttonGradient)) { + auto& oldCameraComponent = + Application::m_coordinator->getComponent(m_activeCamera); oldCameraComponent.active = false; oldCameraComponent.render = false; - m_activeCamera = m_editorCamera; - auto& editorCameraComponent = Application::m_coordinator->getComponent( - m_activeCamera); + m_activeCamera = m_editorCamera; + auto& editorCameraComponent = + Application::m_coordinator->getComponent(m_activeCamera); editorCameraComponent.render = true; editorCameraComponent.active = true; } @@ -362,29 +324,24 @@ namespace nexo::editor { { static const ImNexo::ButtonProps gizmoLocalModeButtonProps = { "local_coords", ICON_FA_CROSSHAIRS, [this]() { this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; }, nullptr, - "Local coordinates" - }; + "Local coordinates"}; static const ImNexo::ButtonProps gizmoWorldModeButtonProps = { "world_coords", ICON_FA_GLOBE, [this]() { this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; }, nullptr, - "World coordinates" - }; - if (m_currentGizmoMode == ImGuizmo::MODE::LOCAL) - { - activeGizmoMode = gizmoLocalModeButtonProps; + "World coordinates"}; + if (m_currentGizmoMode == ImGuizmo::MODE::LOCAL) { + activeGizmoMode = gizmoLocalModeButtonProps; inactiveGizmoMode = gizmoWorldModeButtonProps; - } - else - { - activeGizmoMode = gizmoWorldModeButtonProps; + } else { + activeGizmoMode = gizmoWorldModeButtonProps; inactiveGizmoMode = gizmoLocalModeButtonProps; } - return renderToolbarButton(activeGizmoMode.uniqueId, activeGizmoMode.icon, - activeGizmoMode.tooltip, showGizmoModeMenu ? m_selectedGradient : m_buttonGradient); + return renderToolbarButton(activeGizmoMode.uniqueId, activeGizmoMode.icon, activeGizmoMode.tooltip, + showGizmoModeMenu ? m_selectedGradient : m_buttonGradient); } void EditorScene::renderToolbar() { - constexpr float buttonWidth = 35.0f; + constexpr float buttonWidth = 35.0f; constexpr float buttonHeight = 35.0f; constexpr ImVec2 buttonSize{buttonWidth, buttonHeight}; ImVec2 originalCursorPos = ImGui::GetCursorPos(); @@ -393,14 +350,11 @@ namespace nexo::editor { // -------------------------------- BUTTONS ------------------------------- // -------- Add primitve button -------- - // This can open a submenu, see at the end - ImVec2 addPrimButtonPos = ImGui::GetCursorScreenPos(); + ImVec2 addPrimButtonPos = ImGui::GetCursorScreenPos(); static bool showPrimitiveMenu = false; - bool addPrimitiveClicked = renderToolbarButton( - "add_primitive", ICON_FA_PLUS_SQUARE, - "Add primitive", showPrimitiveMenu ? m_selectedGradient : m_buttonGradient); - if (addPrimitiveClicked) - showPrimitiveMenu = !showPrimitiveMenu; + bool addPrimitiveClicked = renderToolbarButton("add_primitive", ICON_FA_PLUS_SQUARE, "Add primitive", + showPrimitiveMenu ? m_selectedGradient : m_buttonGradient); + if (addPrimitiveClicked) showPrimitiveMenu = !showPrimitiveMenu; ImGui::SameLine(); @@ -412,106 +366,89 @@ namespace nexo::editor { // -------- Gizmo operation button -------- static const auto gizmoTranslateButtonProps = ImNexo::ButtonProps{ "translate", ICON_FA_ARROWS, [this]() { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, - nullptr, "Translate" - }; + nullptr, "Translate"}; static const auto gizmoRotateButtonProps = ImNexo::ButtonProps{ "rotate", ICON_FA_REFRESH, [this]() { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }, - nullptr, "Rotate" - }; + nullptr, "Rotate"}; static const auto gizmoScaleButtonProps = ImNexo::ButtonProps{ "scale", ICON_FA_EXPAND, [this]() { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, nullptr, - "Scale" - }; + "Scale"}; static const auto gizmoUniversalButtonProps = ImNexo::ButtonProps{ "universal", ICON_FA_ARROWS_ALT, - [this]() { this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; }, nullptr, "Universal" - }; - std::vector gizmoButtons = { - gizmoTranslateButtonProps, - gizmoRotateButtonProps, - gizmoScaleButtonProps, - gizmoUniversalButtonProps - }; + [this]() { this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; }, nullptr, "Universal"}; + std::vector gizmoButtons = {gizmoTranslateButtonProps, gizmoRotateButtonProps, + gizmoScaleButtonProps, gizmoUniversalButtonProps}; ImNexo::ButtonProps activeOp; - switch (m_currentGizmoOperation) - { - case ImGuizmo::OPERATION::TRANSLATE: - activeOp = gizmoTranslateButtonProps; - std::erase_if(gizmoButtons, [](const auto& prop) { return prop.uniqueId == "translate"; }); - break; - case ImGuizmo::OPERATION::ROTATE: - activeOp = gizmoRotateButtonProps; - std::erase_if(gizmoButtons, [](const auto& prop) { return prop.uniqueId == "rotate"; }); - break; - case ImGuizmo::OPERATION::SCALE: - activeOp = gizmoScaleButtonProps; - std::erase_if(gizmoButtons, [](const auto& prop) { return prop.uniqueId == "scale"; }); - break; - case ImGuizmo::OPERATION::UNIVERSAL: - activeOp = gizmoUniversalButtonProps; - std::erase_if(gizmoButtons, [](const auto& prop) { return prop.uniqueId == "universal"; }); - break; - default: - break; + auto eraseGizmoButton = [&](const std::string& id, const ImNexo::ButtonProps& prop) { + activeOp = prop; + std::erase_if(gizmoButtons, [&](const auto& p) { return p.uniqueId == id; }); + }; + + using enum ImGuizmo::OPERATION; + switch (m_currentGizmoOperation) { + case TRANSLATE: + eraseGizmoButton("translate", gizmoTranslateButtonProps); + break; + case ROTATE: + eraseGizmoButton("rotate", gizmoRotateButtonProps); + break; + case SCALE: + eraseGizmoButton("scale", gizmoScaleButtonProps); + break; + case UNIVERSAL: + eraseGizmoButton("universal", gizmoUniversalButtonProps); + break; + default: + break; } - ImVec2 changeGizmoOpPos = ImGui::GetCursorScreenPos(); + ImVec2 changeGizmoOpPos = ImGui::GetCursorScreenPos(); static bool showGizmoOpMenu = false; - bool changeGizmoOpClicked = renderToolbarButton( - activeOp.uniqueId, activeOp.icon, - activeOp.tooltip, showGizmoOpMenu ? m_selectedGradient : m_buttonGradient); - if (changeGizmoOpClicked) - showGizmoOpMenu = !showGizmoOpMenu; + bool changeGizmoOpClicked = renderToolbarButton(activeOp.uniqueId, activeOp.icon, activeOp.tooltip, + showGizmoOpMenu ? m_selectedGradient : m_buttonGradient); + if (changeGizmoOpClicked) showGizmoOpMenu = !showGizmoOpMenu; ImGui::SameLine(); // -------- Gizmo operation button -------- ImNexo::ButtonProps activeGizmoMode; ImNexo::ButtonProps inactiveGizmoMode; - ImVec2 changeGizmoModePos = ImGui::GetCursorScreenPos(); + ImVec2 changeGizmoModePos = ImGui::GetCursorScreenPos(); static bool showGizmoModeMenu = false; - bool changeGizmoModeClicked = renderGizmoModeToolbarButton(showGizmoModeMenu, activeGizmoMode, - inactiveGizmoMode); - if (changeGizmoModeClicked) - showGizmoModeMenu = !showGizmoModeMenu; + bool changeGizmoModeClicked = + renderGizmoModeToolbarButton(showGizmoModeMenu, activeGizmoMode, inactiveGizmoMode); + if (changeGizmoModeClicked) showGizmoModeMenu = !showGizmoModeMenu; ImGui::SameLine(); // -------- Toggle snap button -------- - // This can open a submenu, see at the end - ImVec2 toggleSnapPos = ImGui::GetCursorScreenPos(); + ImVec2 toggleSnapPos = ImGui::GetCursorScreenPos(); static bool showSnapToggleMenu = false; - bool snapOn = m_snapRotateOn || m_snapTranslateOn; - bool toggleSnapClicked = renderToolbarButton("toggle_snap", ICON_FA_MAGNET, "Toggle gizmo snap", - (showSnapToggleMenu || snapOn) - ? m_selectedGradient - : m_buttonGradient); - if (toggleSnapClicked) - showSnapToggleMenu = !showSnapToggleMenu; + bool snapOn = m_snapRotateOn || m_snapTranslateOn; + bool toggleSnapClicked = + renderToolbarButton("toggle_snap", ICON_FA_MAGNET, "Toggle gizmo snap", + (showSnapToggleMenu || snapOn) ? m_selectedGradient : m_buttonGradient); + if (toggleSnapClicked) showSnapToggleMenu = !showSnapToggleMenu; ImGui::SameLine(); // -------- Grid enabled button -------- bool rightClicked = false; - components::RenderContext::GridParams& gridParams = Application::m_coordinator->getSingletonComponent< - components::RenderContext>().gridParams; + components::RenderContext::GridParams& gridParams = + Application::m_coordinator->getSingletonComponent().gridParams; if (renderToolbarButton("grid_enabled", ICON_FA_TH_LARGE, "Enable / Disable grid", - gridParams.enabled ? m_selectedGradient : m_buttonGradient, &rightClicked)) - { + gridParams.enabled ? m_selectedGradient : m_buttonGradient, &rightClicked)) { gridParams.enabled = !gridParams.enabled; } - if (rightClicked) - m_popupManager.openPopup("Grid settings", ImVec2(300, 180)); + if (rightClicked) m_popupManager.openPopup("Grid settings", ImVec2(300, 180)); ImGui::SameLine(); - // -------- Snap to gridbutton -------- - // NOTE: This seems complicated to implement using ImGuizmo, we leave it for now but i dont know if it will be implemented + // -------- Snap to grid button -------- if (renderToolbarButton("snap_to_grid", ICON_FA_TH, "Enable snapping to grid\n(only horizontal translation and scaling)", - m_snapToGrid ? m_selectedGradient : m_buttonGradient)) - { + m_snapToGrid ? m_selectedGradient : m_buttonGradient)) { m_snapToGrid = !m_snapToGrid; } @@ -519,27 +456,21 @@ namespace nexo::editor { // -------- Enable wireframe button -------- if (renderToolbarButton("wireframe", ICON_FA_CUBE, "Enable / Disable wireframe", - m_wireframeEnabled ? m_selectedGradient : m_buttonGradient)) - { + m_wireframeEnabled ? m_selectedGradient : m_buttonGradient)) { m_wireframeEnabled = !m_wireframeEnabled; } ImGui::SameLine(); - auto& app = getApp(); + auto& app = getApp(); const bool isPlaying = app.getGameState() == nexo::GameState::PLAY_MODE; - - const char* icon = isPlaying ? ICON_FA_STOP : ICON_FA_PLAY; - const char* tooltip = isPlaying ? "Stop scene" : "Play scene"; + + const char* icon = isPlaying ? ICON_FA_STOP : ICON_FA_PLAY; + const char* tooltip = isPlaying ? "Stop scene" : "Play scene"; const auto& gradient = isPlaying ? m_selectedGradient : m_buttonGradient; - - if (renderToolbarButton("play_stop", icon, tooltip, gradient)) - { - if (isPlaying) { - app.setGameState(nexo::GameState::EDITOR_MODE); - } else { - app.setGameState(nexo::GameState::PLAY_MODE); - } + + if (renderToolbarButton("play_stop", icon, tooltip, gradient)) { + app.setGameState(isPlaying ? nexo::GameState::EDITOR_MODE : nexo::GameState::PLAY_MODE); } ImGui::PopStyleVar(); @@ -547,37 +478,18 @@ namespace nexo::editor { ImGui::PopStyleColor(); // -------------------------------- SUB-MENUS ------------------------------- - // -------- Primitives sub-menus -------- - if (showPrimitiveMenu) - { - renderPrimitiveSubMenu(addPrimButtonPos, buttonSize, showPrimitiveMenu); - } + if (showPrimitiveMenu) renderPrimitiveSubMenu(addPrimButtonPos, buttonSize, showPrimitiveMenu); - // -------- Gizmo operation sub-menu -------- - if (showGizmoOpMenu) - { - ImNexo::ButtonDropDown(changeGizmoOpPos, buttonSize, gizmoButtons, showGizmoOpMenu); - } + if (showGizmoOpMenu) ImNexo::ButtonDropDown(changeGizmoOpPos, buttonSize, gizmoButtons, showGizmoOpMenu); - // -------- Gizmo mode sub-menu -------- if (showGizmoModeMenu) - { ImNexo::ButtonDropDown(changeGizmoModePos, buttonSize, {inactiveGizmoMode}, showGizmoModeMenu); - } - // -------- Snap sub-menu -------- - if (showSnapToggleMenu) - { - renderSnapSubMenu(toggleSnapPos, buttonSize, showSnapToggleMenu); - } + if (showSnapToggleMenu) renderSnapSubMenu(toggleSnapPos, buttonSize, showSnapToggleMenu); - // -------- Snap settings popup -------- snapSettingsPopup(); - - // -------- Grid settings popup -------- gridSettingsPopup(); - // IMPORTANT: Restore original cursor position so we don't affect layout ImGui::SetCursorPos(originalCursorPos); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EditorScene/Update.cpp b/editor/src/DocumentWindows/EditorScene/Update.cpp index cf1e1af6a..b56ff0a67 100644 --- a/editor/src/DocumentWindows/EditorScene/Update.cpp +++ b/editor/src/DocumentWindows/EditorScene/Update.cpp @@ -14,19 +14,21 @@ #include "EditorScene.hpp" #include "Types.hpp" +#include "components/Parent.hpp" #include "components/Transform.hpp" -#include "context/Selector.hpp" #include "components/Uuid.hpp" -#include "components/Parent.hpp" +#include "context/Selector.hpp" namespace nexo::editor { int EditorScene::sampleEntityTexture(const float mx, const float my) const { const auto &coord = Application::m_coordinator; - const auto &cameraComponent = coord->getComponent(static_cast(m_activeCamera)); + const auto &cameraComponent = + coord->getComponent(static_cast(m_activeCamera)); cameraComponent.m_renderTarget->bind(); - const int entityId = cameraComponent.m_renderTarget->getPixel(1, static_cast(mx), static_cast(my)); + const int entityId = + cameraComponent.m_renderTarget->getPixel(1, static_cast(mx), static_cast(my)); cameraComponent.m_renderTarget->unbind(); return entityId; } @@ -34,7 +36,7 @@ namespace nexo::editor { static SelectionType getSelectionType(const int entityId) { const auto &coord = Application::m_coordinator; - auto selType = SelectionType::ENTITY; + auto selType = SelectionType::ENTITY; if (coord->entityHasComponent(entityId)) { selType = SelectionType::CAMERA; } else if (coord->entityHasComponent(entityId)) { @@ -66,13 +68,13 @@ namespace nexo::editor { ecs::Entity EditorScene::findRootParent(const ecs::Entity entityId) { - const auto &coord = Application::m_coordinator; + const auto &coord = Application::m_coordinator; ecs::Entity currentEntity = entityId; // Traverse up the hierarchy until we find an entity without a parent while (coord->entityHasComponent(currentEntity)) { - const auto& parentComp = coord->getComponent(currentEntity); - currentEntity = parentComp.parent; + const auto &parentComp = coord->getComponent(currentEntity); + currentEntity = parentComp.parent; } return currentEntity; @@ -83,17 +85,16 @@ namespace nexo::editor { const auto &coord = Application::m_coordinator; const auto uuid = coord->tryGetComponent(entityId); - if (!uuid) - return; + if (!uuid) return; - const bool selectWholeHierarchy = ImGui::IsKeyDown(ImGuiKey_V); // Hold V to select hierarchy instead of direct entity + const bool selectWholeHierarchy = + ImGui::IsKeyDown(ImGuiKey_V); // Hold V to select hierarchy instead of direct entity auto &selector = Selector::get(); if (selectWholeHierarchy) { const ecs::Entity rootEntity = findRootParent(entityId); - if (!isShiftPressed && !isCtrlPressed) - selector.clearSelection(); + if (!isShiftPressed && !isCtrlPressed) selector.clearSelection(); selectEntityHierarchy(rootEntity, isCtrlPressed); } else { @@ -127,23 +128,21 @@ namespace nexo::editor { } if (coord->entityHasComponent(entityId)) { - const auto& transform = coord->getComponent(entityId); + const auto &transform = coord->getComponent(entityId); selectModelChildren(transform.children, isCtrlPressed); } } - void EditorScene::selectModelChildren(const std::vector& children, const bool isCtrlPressed) + void EditorScene::selectModelChildren(const std::vector &children, const bool isCtrlPressed) { const auto &coord = Application::m_coordinator; - for (const auto& entity : children) { + for (const auto &entity : children) { selectEntityHierarchy(entity, isCtrlPressed); - if (!coord->entityHasComponent(entity)) - continue; + if (!coord->entityHasComponent(entity)) continue; const auto &childTransform = coord->getComponent(entity); - if (!childTransform.children.empty()) - selectModelChildren(childTransform.children, isCtrlPressed); + if (!childTransform.children.empty()) selectModelChildren(childTransform.children, isCtrlPressed); } } @@ -157,14 +156,13 @@ namespace nexo::editor { my = m_contentSize.y - my; // Check if mouse is inside viewport - if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) - return; + if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) return; const int entityId = sampleEntityTexture(mx, my); // Check for multi-selection key modifiers const bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); - const bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); - auto &selector = Selector::get(); + const bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + auto &selector = Selector::get(); if (entityId == -1) { // Clicked on empty space - clear selection unless shift/ctrl is held @@ -182,18 +180,17 @@ namespace nexo::editor { { const bool isCurrentlyVisible = m_isVisibleInDock || m_wasVisibleLastFrame; - if (!m_opened || m_activeCamera == -1 || !isCurrentlyVisible) - return; + if (!m_opened || m_activeCamera == -1 || !isCurrentlyVisible) return; const SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; Application::SceneInfo sceneInfo{static_cast(m_sceneId), RenderingType::FRAMEBUFFER, sceneType}; - sceneInfo.isChildWindow = true; + sceneInfo.isChildWindow = true; sceneInfo.viewportBounds[0] = glm::vec2{m_viewportBounds[0].x, m_viewportBounds[0].y}; sceneInfo.viewportBounds[1] = glm::vec2{m_viewportBounds[1].x, m_viewportBounds[1].y}; runEngine(sceneInfo); + handleTimecodeUpdate(); // Handle mouse clicks for selection - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsUsing() && m_focused) - handleSelection(); + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsUsing() && m_focused) handleSelection(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/AEntityProperty.hpp b/editor/src/DocumentWindows/EntityProperties/AEntityProperty.hpp index 762c37189..078b8dd34 100644 --- a/editor/src/DocumentWindows/EntityProperties/AEntityProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/AEntityProperty.hpp @@ -17,27 +17,36 @@ namespace nexo::editor { - class InspectorWindow; + class InspectorWindow; class IEntityProperty { - public: - virtual ~IEntityProperty() = default; + public: + virtual ~IEntityProperty() = default; - virtual void show(ecs::Entity entity) = 0; + /** + * @brief Displays the properties of the specified entity in the inspector window. + * + * This pure virtual function must be implemented by derived classes to define how + * the properties of the given entity are presented in the inspector window. + * + * @param entity The entity whose properties are to be displayed. + */ + virtual void show(ecs::Entity entity) = 0; }; class AEntityProperty : public IEntityProperty { - public: - /** - * @brief Constructs an AEntityProperty instance. - * - * Initializes the entity property by storing a reference to the provided InspectorWindow, - * which is used for displaying and managing entity properties in the editor. - * - * @param inspector Reference to the InspectorWindow associated with this property. - */ - explicit AEntityProperty(InspectorWindow &inspector) : m_inspector(inspector) {}; - protected: - InspectorWindow &m_inspector; + public: + /** + * @brief Constructs an AEntityProperty instance. + * + * Initializes the entity property by storing a reference to the provided InspectorWindow, + * which is used for displaying and managing entity properties in the editor. + * + * @param inspector Reference to the InspectorWindow associated with this property. + */ + explicit AEntityProperty(InspectorWindow &inspector) : m_inspector(inspector){}; + + protected: + InspectorWindow &m_inspector; }; -}; +}; // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp index 98cb1623f..0bc8b28de 100644 --- a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp @@ -13,13 +13,13 @@ /////////////////////////////////////////////////////////////////////////////// #include "AmbientLightProperty.hpp" -#include "components/Light.hpp" -#include "ImNexo/Widgets.hpp" -#include "context/actions/EntityActions.hpp" -#include "context/ActionManager.hpp" +#include #include "ImNexo/EntityProperties.hpp" #include "ImNexo/ImNexo.hpp" -#include +#include "ImNexo/Widgets.hpp" +#include "components/Light.hpp" +#include "context/ActionManager.hpp" +#include "context/actions/EntityActions.hpp" namespace nexo::editor { @@ -28,8 +28,7 @@ namespace nexo::editor { auto& ambientComponent = Application::getEntityComponent(entity); static components::AmbientLightComponent::Memento beforeState; - if (ImNexo::Header("##AmbientNode", "Ambient light")) - { + if (ImNexo::Header("##AmbientNode", "Ambient light")) { const auto ambientComponentCopy = ambientComponent; ImNexo::resetItemStates(); ImNexo::Ambient(ambientComponent); @@ -37,7 +36,8 @@ namespace nexo::editor { beforeState = ambientComponentCopy.save(); } else if (ImNexo::isItemDeactivated()) { auto afterState = ambientComponent.save(); - auto action = std::make_unique>(entity, beforeState, afterState); + auto action = std::make_unique>( + entity, beforeState, afterState); ActionManager::get().recordAction(std::move(action)); beforeState = components::AmbientLightComponent::Memento{}; } @@ -45,4 +45,4 @@ namespace nexo::editor { ImGui::TreePop(); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.hpp b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.hpp index f59cac886..558b306c1 100644 --- a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.hpp @@ -16,19 +16,19 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class AmbientLightProperty final : public AEntityProperty { - public: - using AEntityProperty::AEntityProperty; + class AmbientLightProperty final : public AEntityProperty { + public: + using AEntityProperty::AEntityProperty; - /** - * @brief Renders the ambient light properties UI for the specified entity. - * - * Retrieves the ambient light component associated with the entity and displays - * a header along with a color editor widget. When the header is open, the widget - * allows the user to modify the ambient light color, which is then applied to the component. - * - * @param entity The ECS entity whose ambient light properties are to be displayed. - */ - void show(ecs::Entity entity) override; - }; -} + /** + * @brief Renders the ambient light properties UI for the specified entity. + * + * Retrieves the ambient light component associated with the entity and displays + * a header along with a color editor widget. When the header is open, the widget + * allows the user to modify the ambient light color, which is then applied to the component. + * + * @param entity The ECS entity whose ambient light properties are to be displayed. + */ + void show(ecs::Entity entity) override; + }; +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp index d869a6d95..beada6b3a 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp @@ -13,22 +13,21 @@ /////////////////////////////////////////////////////////////////////////////// #include "CameraController.hpp" -#include "ImNexo/ImNexo.hpp" -#include "components/Camera.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" +#include "ImNexo/ImNexo.hpp" +#include "components/Camera.hpp" #include "context/ActionManager.hpp" #include "context/actions/EntityActions.hpp" namespace nexo::editor { - void CameraController::show(const ecs::Entity entity) - { + void CameraController::show(const ecs::Entity entity) + { auto& controllerComponent = Application::getEntityComponent(entity); static components::PerspectiveCameraController::Memento beforeState; - if (ImNexo::Header("##ControllerNode", "Camera Controller")) - { + if (ImNexo::Header("##ControllerNode", "Camera Controller")) { ImGui::Spacing(); const auto controllerComponentCopy = controllerComponent; ImNexo::resetItemStates(); @@ -37,11 +36,12 @@ namespace nexo::editor { beforeState = controllerComponentCopy.save(); } else if (ImNexo::isItemDeactivated()) { auto afterState = controllerComponent.save(); - auto action = std::make_unique>(entity, beforeState, afterState); + auto action = std::make_unique>( + entity, beforeState, afterState); ActionManager::get().recordAction(std::move(action)); beforeState = components::PerspectiveCameraController::Memento{}; } ImGui::TreePop(); } - } -} + } +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/CameraController.hpp b/editor/src/DocumentWindows/EntityProperties/CameraController.hpp index 18a620016..3c0f375c4 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraController.hpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraController.hpp @@ -16,18 +16,18 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class CameraController final : public AEntityProperty { - public: - using AEntityProperty::AEntityProperty; + class CameraController final : public AEntityProperty { + public: + using AEntityProperty::AEntityProperty; - /** - * @brief Renders the camera controller UI. - * - * Retrieves the PerspectiveCameraController component from the specified entity and displays its settings, - * including a control for adjusting mouse sensitivity, using an ImGui layout. - * - * @param entity The entity that holds the camera controller component. - */ - void show(ecs::Entity entity) override; - }; -} + /** + * @brief Renders the camera controller UI. + * + * Retrieves the PerspectiveCameraController component from the specified entity and displays its settings, + * including a control for adjusting mouse sensitivity, using an ImGui layout. + * + * @param entity The entity that holds the camera controller component. + */ + void show(ecs::Entity entity) override; + }; +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp index f58efc729..e14dfab4f 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp @@ -13,22 +13,21 @@ /////////////////////////////////////////////////////////////////////////////// #include "CameraProperty.hpp" -#include "ImNexo/ImNexo.hpp" -#include "components/Camera.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" +#include "ImNexo/ImNexo.hpp" +#include "components/Camera.hpp" #include "context/ActionManager.hpp" #include "context/actions/EntityActions.hpp" namespace nexo::editor { - void CameraProperty::show(const ecs::Entity entity) - { + void CameraProperty::show(const ecs::Entity entity) + { auto& cameraComponent = Application::getEntityComponent(entity); static components::CameraComponent::Memento beforeState; - if (ImNexo::Header("##CameraNode", "Camera")) - { + if (ImNexo::Header("##CameraNode", "Camera")) { const auto cameraComponentCopy = cameraComponent; ImNexo::resetItemStates(); ImNexo::Camera(cameraComponent); @@ -36,11 +35,12 @@ namespace nexo::editor { beforeState = cameraComponentCopy.save(); } else if (ImNexo::isItemDeactivated()) { auto afterState = cameraComponent.save(); - auto action = std::make_unique>(entity, beforeState, afterState); + auto action = std::make_unique>(entity, beforeState, + afterState); ActionManager::get().recordAction(std::move(action)); beforeState = components::CameraComponent::Memento{}; } ImGui::TreePop(); } - } -} + } +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/CameraProperty.hpp b/editor/src/DocumentWindows/EntityProperties/CameraProperty.hpp index aa771fc6c..c2830977c 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraProperty.hpp @@ -16,19 +16,19 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class CameraProperty final : public AEntityProperty { - public: - using AEntityProperty::AEntityProperty; + class CameraProperty final : public AEntityProperty { + public: + using AEntityProperty::AEntityProperty; - /** - * @brief Displays and updates the camera properties UI. - * - * Retrieves the camera component from the given ECS entity and renders a user interface for adjusting - * camera settings. The UI includes controls for modifying viewport dimensions (with a lock toggle), field of view, - * near plane, far plane, and the clear color using ImGui. - * - * @param entity The entity containing the camera component. - */ - void show(ecs::Entity entity) override; - }; -} + /** + * @brief Displays and updates the camera properties UI. + * + * Retrieves the camera component from the given ECS entity and renders a user interface for adjusting + * camera settings. The UI includes controls for modifying viewport dimensions (with a lock toggle), field of + * view, near plane, far plane, and the clear color using ImGui. + * + * @param entity The entity containing the camera component. + */ + void show(ecs::Entity entity) override; + }; +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp index 0ec702f32..e59e51c12 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp @@ -14,10 +14,10 @@ #include "CameraTarget.hpp" #include "Definitions.hpp" -#include "ImNexo/ImNexo.hpp" -#include "components/Camera.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" +#include "ImNexo/ImNexo.hpp" +#include "components/Camera.hpp" #include "context/ActionManager.hpp" namespace nexo::editor { @@ -27,8 +27,7 @@ namespace nexo::editor { auto& targetComponent = Application::getEntityComponent(entity); static components::PerspectiveCameraTarget::Memento beforeState; - if (ImNexo::Header("##TargetNode", "Camera Target")) - { + if (ImNexo::Header("##TargetNode", "Camera Target")) { const auto targetComponentCopy = targetComponent; ImGui::Spacing(); ImNexo::resetItemStates(); @@ -37,11 +36,12 @@ namespace nexo::editor { beforeState = targetComponentCopy.save(); } else if (ImNexo::isItemDeactivated()) { auto afterState = targetComponent.save(); - auto action = std::make_unique>(entity, beforeState, afterState); + auto action = std::make_unique>( + entity, beforeState, afterState); ActionManager::get().recordAction(std::move(action)); beforeState = components::PerspectiveCameraTarget::Memento{}; } ImGui::TreePop(); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp b/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp index 30598254c..8bb504d66 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp @@ -17,9 +17,18 @@ namespace nexo::editor { class CameraTarget final : public AEntityProperty { - public: - using AEntityProperty::AEntityProperty; + public: + using AEntityProperty::AEntityProperty; - void show(ecs::Entity entity) override; - }; -} + /** + * @brief Renders the camera target properties UI for the specified entity. + * + * Retrieves the PerspectiveCameraTarget component associated with the entity and displays + * a header along with a 3D vector editor widget. When the header is open, the widget + * allows the user to modify the camera target position, which is then applied to the component. + * + * @param entity The ECS entity whose camera target properties are to be displayed. + */ + void show(ecs::Entity entity) override; + }; +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp index f972cba18..5650117b8 100644 --- a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp @@ -15,8 +15,8 @@ #include "DirectionalLightProperty.hpp" #include "ImNexo/EntityProperties.hpp" #include "ImNexo/ImNexo.hpp" -#include "components/Light.hpp" #include "ImNexo/Widgets.hpp" +#include "components/Light.hpp" #include "context/ActionManager.hpp" namespace nexo::editor { @@ -26,8 +26,7 @@ namespace nexo::editor { auto& directionalComponent = Application::getEntityComponent(entity); static components::DirectionalLightComponent::Memento beforeState; - if (ImNexo::Header("##DirectionalNode", "Directional light")) - { + if (ImNexo::Header("##DirectionalNode", "Directional light")) { const auto directionalComponentCopy = directionalComponent; ImNexo::resetItemStates(); ImNexo::DirectionalLight(directionalComponent); @@ -35,11 +34,12 @@ namespace nexo::editor { beforeState = directionalComponentCopy.save(); } else if (ImNexo::isItemDeactivated()) { auto afterState = directionalComponent.save(); - auto action = std::make_unique>(entity, beforeState, afterState); + auto action = std::make_unique>( + entity, beforeState, afterState); ActionManager::get().recordAction(std::move(action)); beforeState = components::DirectionalLightComponent::Memento{}; } ImGui::TreePop(); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.hpp b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.hpp index 9aa41f16c..d179ef4ee 100644 --- a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.hpp @@ -16,19 +16,20 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class DirectionalLightProperty final : public AEntityProperty { - public: - using AEntityProperty::AEntityProperty; + class DirectionalLightProperty final : public AEntityProperty { + public: + using AEntityProperty::AEntityProperty; - /** - * @brief Displays and enables editing of an entity's directional light properties. - * - * Retrieves the directional light component from the specified entity and, if the header is expanded, - * renders a GUI section that includes controls for modifying the light's color and direction. The color - * editor uses a customizable picker while the direction is adjusted via draggable float inputs arranged in a table. - * - * @param entity The entity whose directional light properties are to be displayed. - */ - void show(ecs::Entity entity) override; - }; -} + /** + * @brief Displays and enables editing of an entity's directional light properties. + * + * Retrieves the directional light component from the specified entity and, if the header is expanded, + * renders a GUI section that includes controls for modifying the light's color and direction. The color + * editor uses a customizable picker while the direction is adjusted via draggable float inputs arranged in a + * table. + * + * @param entity The entity whose directional light properties are to be displayed. + */ + void show(ecs::Entity entity) override; + }; +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/MaterialProperty.cpp b/editor/src/DocumentWindows/EntityProperties/MaterialProperty.cpp index 9308a2ec7..3d749bab0 100644 --- a/editor/src/DocumentWindows/EntityProperties/MaterialProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/MaterialProperty.cpp @@ -13,16 +13,16 @@ /////////////////////////////////////////////////////////////////////////////// #include "MaterialProperty.hpp" +#include +#include "DocumentWindows/MaterialInspector/MaterialInspector.hpp" #include "ImNexo/Elements.hpp" +#include "ImNexo/Panels.hpp" #include "Types.hpp" +#include "assets/AssetCatalog.hpp" #include "components/Camera.hpp" #include "components/SceneComponents.hpp" #include "context/ThumbnailCache.hpp" -#include "DocumentWindows/MaterialInspector/MaterialInspector.hpp" -#include "assets/AssetCatalog.hpp" -#include "ImNexo/Panels.hpp" #include "utils/ScenePreview.hpp" -#include namespace nexo::editor { @@ -43,26 +43,23 @@ namespace nexo::editor { ImGui::Separator(); static assets::AssetRef materialRef = nullptr; if (!materialRef) { - auto material = std::make_unique(); + auto material = std::make_unique(); material->albedoColor = {0.05f * 1.7, 0.09f * 1.35, 0.13f * 1.45, 1.0f}; - materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::TempMaterial@_internal"), - std::move(material)); + materialRef = assets::AssetCatalog::getInstance().createAsset( + assets::AssetLocation("_internal::TempMaterial@_internal"), std::move(material)); } - const ImVec2 availSize = ImGui::GetContentRegionAvail(); const float totalWidth = availSize.x; - float totalHeight = availSize.y - 40; // Reserve space for bottom buttons + float totalHeight = availSize.y - 40; // Reserve space for bottom buttons // Define layout: 40% for inspector, 60% for preview const float inspectorWidth = totalWidth * 0.4f; - const float previewWidth = totalWidth - inspectorWidth - 8; // Subtract spacing between panels + const float previewWidth = totalWidth - inspectorWidth - 8; // Subtract spacing between panels ImGui::Columns(2, "MaterialPreviewColumns", false); static char materialName[128] = ""; - ImGui::SetColumnWidth(0, inspectorWidth); // --- Left Side: Material Inspector --- { @@ -70,8 +67,8 @@ namespace nexo::editor { ImGui::InputText("Name", materialName, IM_ARRAYSIZE(materialName)); ImGui::Spacing(); - const auto material = materialRef.lock(); - components::Material& materialData = *material->getData(); + const auto material = materialRef.lock(); + components::Material &materialData = *material->getData(); if (ImNexo::MaterialInspector(materialData)) ThumbnailCache::getInstance().updateMaterialThumbnail(materialRef); @@ -87,20 +84,22 @@ namespace nexo::editor { if (!out.sceneGenerated) utils::genScenePreview("Material Creation Scene", {previewWidth - 4, totalHeight}, entity, out); const Application::SceneInfo sceneInfo{out.sceneId, RenderingType::FRAMEBUFFER}; - auto &materialComp = Application::m_coordinator->getComponent(out.entityCopy); + auto &materialComp = + Application::m_coordinator->getComponent(out.entityCopy); materialComp.material = materialRef; Application::getInstance().run(sceneInfo); - const auto &cameraComp = Application::m_coordinator->getComponent(out.cameraId); + const auto &cameraComp = + Application::m_coordinator->getComponent(out.cameraId); const unsigned int textureId = cameraComp.m_renderTarget->getColorAttachmentId(); const float aspectRatio = (previewWidth - 8) / totalHeight; const float displayHeight = totalHeight - 20; - const float displayWidth = displayHeight * aspectRatio; + const float displayWidth = displayHeight * aspectRatio; ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 4, ImGui::GetCursorPosY() + 4)); ImNexo::Image(static_cast(static_cast(textureId)), - ImVec2(displayWidth, displayHeight)); + ImVec2(displayWidth, displayHeight)); ImGui::EndChild(); } @@ -110,17 +109,16 @@ namespace nexo::editor { constexpr float buttonWidth = 120.0f; - if (ImNexo::Button("OK", ImVec2(buttonWidth, 0))) - { + if (ImNexo::Button("OK", ImVec2(buttonWidth, 0))) { // Create a new permanent material based on the preview material if (materialRef.isValid()) { auto name = std::string(materialName); if (name.empty()) { name = std::format("NewMaterial_{}", entity); } - const auto &sceneTag = Application::m_coordinator->getComponent(entity); + const auto &sceneTag = Application::m_coordinator->getComponent(entity); std::string sceneName = Application::getInstance().getSceneManager().getScene(sceneTag.id).getName(); - const auto hashPos = sceneName.find('#'); + const auto hashPos = sceneName.find('#'); if (hashPos != std::string::npos) { sceneName.erase(hashPos); } @@ -128,11 +126,10 @@ namespace nexo::editor { // Create a new material asset by copying the preview material const auto newMaterialRef = assets::AssetCatalog::getInstance().createAsset( - finalLocation, - std::make_unique(*materialRef.lock()->getData()) - ); + finalLocation, std::make_unique(*materialRef.lock()->getData())); - auto& materialComponent = Application::m_coordinator->getComponent(entity); + auto &materialComponent = + Application::m_coordinator->getComponent(entity); materialComponent.material = newMaterialRef; LOG(NEXO_INFO, "Applied new material '{}' to entity {}", finalLocation.getFullLocation(), entity); } @@ -140,11 +137,9 @@ namespace nexo::editor { cleanupPopup(materialRef, out); } ImGui::SameLine(); - if (ImNexo::Button("Cancel", ImVec2(buttonWidth, 0))) - cleanupPopup(materialRef, out); + if (ImNexo::Button("Cancel", ImVec2(buttonWidth, 0))) cleanupPopup(materialRef, out); } - void MaterialProperty::show(const ecs::Entity entity) { if (Application::m_coordinator->entityHasComponent(entity) || @@ -152,9 +147,9 @@ namespace nexo::editor { Application::m_coordinator->entityHasComponent(entity)) return; const auto &materialComponent = Application::getEntityComponent(entity); - if (ImNexo::Header("##MaterialNode", "Material Component")) - { - const ImTextureID textureID = ThumbnailCache::getInstance().getMaterialThumbnail(materialComponent.material); + if (ImNexo::Header("##MaterialNode", "Material Component")) { + const ImTextureID textureID = + ThumbnailCache::getInstance().getMaterialThumbnail(materialComponent.material); ImNexo::Image(textureID, ImVec2(64, 64)); ImGui::SameLine(); @@ -162,24 +157,21 @@ namespace nexo::editor { { // --- Dropdown for Material Types --- static int selectedMaterialIndex = 0; - const char* materialTypes[] = { "Default", "Metal", "Wood", "Plastic" }; + const char *materialTypes[] = {"Default", "Metal", "Wood", "Plastic"}; ImGui::Combo("##MaterialType", &selectedMaterialIndex, materialTypes, IM_ARRAYSIZE(materialTypes)); // --- Material Action Buttons --- - if (ImNexo::Button("Create new material")) - { - m_popupManager.openPopup("Create new material", ImVec2(1440,900)); + if (ImNexo::Button("Create new material")) { + m_popupManager.openPopup("Create new material", ImVec2(1440, 900)); } ImGui::SameLine(); - if (ImNexo::Button("Modify Material")) - { + if (ImNexo::Button("Modify Material")) { m_inspector.setSubInspectorVisibility(true); } MaterialInspectorData data; data.m_selectedEntity = entity; - data.material = materialComponent.material; + data.material = materialComponent.material; m_inspector.setSubInspectorData(data); - } ImGui::EndGroup(); const ImVec2 center = ImGui::GetMainViewport()->GetCenter(); @@ -187,10 +179,9 @@ namespace nexo::editor { } ImGui::TreePop(); - if (m_popupManager.showPopupModal("Create new material")) - { + if (m_popupManager.showPopupModal("Create new material")) { createMaterialPopup(entity); PopupManager::endPopup(); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/MaterialProperty.hpp b/editor/src/DocumentWindows/EntityProperties/MaterialProperty.hpp index 32a89928b..e27d3654f 100644 --- a/editor/src/DocumentWindows/EntityProperties/MaterialProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/MaterialProperty.hpp @@ -27,13 +27,44 @@ namespace nexo::editor { }; class MaterialProperty final : public AEntityProperty { - public: - using AEntityProperty::AEntityProperty; - - void show(ecs::Entity entity) override; - private: - static void createMaterialPopup(ecs::Entity entity); - static void cleanupPopup(assets::AssetRef &materialRef, utils::ScenePreviewOut &out); - PopupManager m_popupManager; + public: + using AEntityProperty::AEntityProperty; + + /** + * @brief Displays and manages the material properties of an entity. + * + * This function checks if the specified entity has a material component and, if so, displays its properties + * in a collapsible header. It includes a thumbnail of the material, options to create a new material or + * modify the existing one, and handles pop-up dialogs for material creation. The function also integrates + * with the inspector window to allow detailed editing of the material properties. + * + * @param entity The entity whose material properties are to be displayed. + */ + void show(ecs::Entity entity) override; + + private: + /** + * @brief Creates the material creation popup UI. + * + * This function sets up the user interface for creating a new material. It includes a preview of the material, + * options to edit its properties, and buttons to apply or cancel the creation process. The function manages + * temporary assets and scene previews to facilitate the material creation workflow. + * + * @param entity The entity for which the new material is being created. + */ + static void createMaterialPopup(ecs::Entity entity); + + /** + * @brief Cleans up resources used in the material creation popup. + * + * This function deletes temporary material assets and scene previews created during the material creation + * process. It ensures that resources are properly released and closes the popup dialog. + * + * @param materialRef A reference to the temporary material asset to be deleted. + * @param out A reference to the scene preview output structure to manage scene cleanup. + */ + static void cleanupPopup(assets::AssetRef &materialRef, utils::ScenePreviewOut &out); + + PopupManager m_popupManager; }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/PhysicsBodyProperty.cpp b/editor/src/DocumentWindows/EntityProperties/PhysicsBodyProperty.cpp index dfbe49b4d..8b6650bab 100644 --- a/editor/src/DocumentWindows/EntityProperties/PhysicsBodyProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/PhysicsBodyProperty.cpp @@ -15,9 +15,9 @@ #include "PhysicsBodyProperty.hpp" +#include "Application.hpp" #include "ImNexo/Components.hpp" #include "ImNexo/Elements.hpp" -#include "Application.hpp" #include "components/Transform.hpp" #include "systems/PhysicsSystem.hpp" @@ -26,119 +26,118 @@ namespace nexo::editor { void PhysicsBodyProperty::show(const ecs::Entity entity) { const auto& coordinator = Application::m_coordinator; - - auto physicsBodyOpt = coordinator->tryGetComponent(entity); + + const auto physicsBodyOpt = coordinator->tryGetComponent(entity); if (!physicsBodyOpt) { return; } - auto& physicsBody = physicsBodyOpt->get(); - - if (ImNexo::Header("##PhysicsBody", "Physics Body Component")) - { + const auto& physicsBody = physicsBodyOpt->get(); + + if (ImNexo::Header("##PhysicsBody", "Physics Body Component")) { const auto currentType = physicsBody.type; - - const char* typeNames[] = { "Static", "Dynamic" }; - int currentTypeIndex = (currentType == components::PhysicsBodyComponent::Type::Static) ? 0 : 1; - int newTypeIndex = currentTypeIndex; - - if (ImGui::Combo("Physics Type", &newTypeIndex, typeNames, IM_ARRAYSIZE(typeNames))) - { - const auto newType = (newTypeIndex == 0) - ? components::PhysicsBodyComponent::Type::Static - : components::PhysicsBodyComponent::Type::Dynamic; - + + constexpr std::array typeNames = {"Static", "Dynamic"}; + const int currentTypeIndex = (currentType == components::PhysicsBodyComponent::Type::Static) ? 0 : 1; + int newTypeIndex = currentTypeIndex; + + if (ImGui::Combo("Physics Type", &newTypeIndex, typeNames.data(), typeNames.size())) { + const auto newType = (newTypeIndex == 0) ? components::PhysicsBodyComponent::Type::Static : + components::PhysicsBodyComponent::Type::Dynamic; + if (newType != currentType) { recreatePhysicsBody(entity, newType); } } - + ImGui::Separator(); ImGui::Text("Body ID: %u", physicsBody.bodyID.GetIndex()); - - auto& app = Application::getInstance(); - if (auto physicsSystem = app.getPhysicsSystem()) { + + const auto& app = Application::getInstance(); + if (const auto physicsSystem = app.getPhysicsSystem()) { const bool isActive = physicsSystem->getBodyInterface()->IsActive(physicsBody.bodyID); ImGui::Text("Active: %s", isActive ? "Yes" : "No"); } - + ImGui::TreePop(); } } - void PhysicsBodyProperty::recreatePhysicsBody(const ecs::Entity entity, const components::PhysicsBodyComponent::Type newType) + void PhysicsBodyProperty::recreatePhysicsBody(const ecs::Entity entity, + const components::PhysicsBodyComponent::Type newType) { - const auto& coordinator = Application::m_coordinator; - auto& app = Application::getInstance(); - auto physicsSystem = app.getPhysicsSystem(); - + const auto& coordinator = Application::m_coordinator; + const auto& app = Application::getInstance(); + const auto physicsSystem = app.getPhysicsSystem(); + if (!physicsSystem) { LOG(NEXO_ERROR, "PhysicsSystem not available"); return; } - auto physicsBodyOpt = coordinator->tryGetComponent(entity); - auto transformOpt = coordinator->tryGetComponent(entity); - + const auto physicsBodyOpt = coordinator->tryGetComponent(entity); + const auto transformOpt = coordinator->tryGetComponent(entity); + if (!physicsBodyOpt || !transformOpt) { LOG(NEXO_ERROR, "Entity {} missing required components for physics body recreation", entity); return; } - auto& physicsBody = physicsBodyOpt->get(); + auto& physicsBody = physicsBodyOpt->get(); const auto& transform = transformOpt->get(); - + const JPH::BodyID oldBodyID = physicsBody.bodyID; - + try { if (!oldBodyID.IsInvalid()) { physicsSystem->getBodyInterface()->RemoveBody(oldBodyID); physicsSystem->getBodyInterface()->DestroyBody(oldBodyID); } - + JPH::BodyID newBodyID; if (newType == components::PhysicsBodyComponent::Type::Static) { newBodyID = physicsSystem->createStaticBody(entity, transform); } else { newBodyID = physicsSystem->createDynamicBody(entity, transform); } - + if (newBodyID.IsInvalid()) { LOG(NEXO_ERROR, "Failed to create new physics body for entity {}", entity); return; } - + physicsBody.bodyID = newBodyID; - physicsBody.type = newType; - - LOG(NEXO_INFO, "Successfully recreated physics body for entity {} (type: {})", - entity, (newType == components::PhysicsBodyComponent::Type::Static) ? "Static" : "Dynamic"); - + physicsBody.type = newType; + + LOG(NEXO_INFO, "Successfully recreated physics body for entity {} (type: {})", entity, + (newType == components::PhysicsBodyComponent::Type::Static) ? "Static" : "Dynamic"); + } catch (const std::exception& e) { - LOG(NEXO_ERROR, "Exception during physics body recreation for entity {}: {}", entity, e.what()); + THROW_EXCEPTION(PhysicBodyCreationException, entity, e.what()); } } void PhysicsBodyProperty::addPhysicsComponentToEntity(const ecs::Entity entity, const bool isDynamic) { - const auto& coordinator = Application::m_coordinator; - auto& app = Application::getInstance(); - auto physicsSystem = app.getPhysicsSystem(); - + const auto& coordinator = Application::m_coordinator; + const auto& app = Application::getInstance(); + const auto physicsSystem = app.getPhysicsSystem(); + if (!physicsSystem) { LOG(NEXO_ERROR, "PhysicsSystem not available"); return; } - auto transformOpt = coordinator->tryGetComponent(entity); + const auto transformOpt = coordinator->tryGetComponent(entity); if (!transformOpt) { LOG(NEXO_ERROR, "Entity {} missing TransformComponent for physics body creation", entity); return; } const auto& transform = transformOpt->get(); - const auto type = isDynamic ? components::PhysicsBodyComponent::Type::Dynamic : components::PhysicsBodyComponent::Type::Static; - + const auto type = isDynamic ? components::PhysicsBodyComponent::Type::Dynamic : + components::PhysicsBodyComponent::Type::Static; + try { JPH::BodyID bodyID; if (isDynamic) { @@ -146,20 +145,19 @@ namespace nexo::editor { } else { bodyID = physicsSystem->createStaticBody(entity, transform); } - + if (bodyID.IsInvalid()) { LOG(NEXO_ERROR, "Failed to create physics body for entity {}", entity); return; } - + coordinator->addComponent(entity, components::PhysicsBodyComponent{bodyID, type}); - - LOG(NEXO_INFO, "Added physics component to entity {} (type: {})", - entity, isDynamic ? "Dynamic" : "Static"); - + + LOG(NEXO_INFO, "Added physics component to entity {} (type: {})", entity, isDynamic ? "Dynamic" : "Static"); + } catch (const std::exception& e) { - LOG(NEXO_ERROR, "Exception during physics component creation for entity {}: {}", entity, e.what()); + THROW_EXCEPTION(PhysicComponentCreationException, entity, e.what()); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/PhysicsBodyProperty.hpp b/editor/src/DocumentWindows/EntityProperties/PhysicsBodyProperty.hpp index 7d65310bf..3518495ff 100644 --- a/editor/src/DocumentWindows/EntityProperties/PhysicsBodyProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/PhysicsBodyProperty.hpp @@ -21,34 +21,42 @@ namespace nexo::editor { class PhysicsBodyProperty final : public AEntityProperty { - public: - explicit PhysicsBodyProperty(InspectorWindow &inspector) - : AEntityProperty(inspector) - { - } - - /** - * @brief Displays and edits the physics body properties with proper Jolt synchronization. - * - * Shows the physics body type (Static/Dynamic) and handles the recreation - * of the physics body in Jolt when properties are modified. - * - * @param entity The entity whose physics body properties are rendered. - */ - void show(ecs::Entity entity) override; - - static void addPhysicsComponentToEntity(ecs::Entity entity, bool isDynamic); - - private: - /** - * @brief Recreates the physics body in Jolt with new properties. - * - * Properly removes the old body and creates a new one with updated settings. - * - * @param entity The entity whose physics body needs recreation - * @param newType The new physics body type - */ - void recreatePhysicsBody(ecs::Entity entity, components::PhysicsBodyComponent::Type newType); + public: + explicit PhysicsBodyProperty(InspectorWindow &inspector) : AEntityProperty(inspector) + {} + + /** + * @brief Displays and edits the physics body properties with proper Jolt synchronization. + * + * Shows the physics body type (Static/Dynamic) and handles the recreation + * of the physics body in Jolt when properties are modified. + * + * @param entity The entity whose physics body properties are rendered. + */ + void show(ecs::Entity entity) override; + + /** + * @brief Adds a physics component to the specified entity. + * + * Creates and attaches a PhysicsBodyComponent to the entity, initializing it + * as either static or dynamic based on the provided flag. This function also + * ensures that the physics body is properly created in the Jolt Physics engine. + * + * @param entity The entity to which the physics component will be added. + * @param isDynamic If true, creates a dynamic body; otherwise, creates a static body. + */ + static void addPhysicsComponentToEntity(ecs::Entity entity, bool isDynamic); + + private: + /** + * @brief Recreates the physics body in Jolt with new properties. + * + * Properly removes the old body and creates a new one with updated settings. + * + * @param entity The entity whose physics body needs recreation + * @param newType The new physics body type + */ + static void recreatePhysicsBody(ecs::Entity entity, components::PhysicsBodyComponent::Type newType); }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp index 0f9c4549c..b8495eb27 100644 --- a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp @@ -15,42 +15,43 @@ #include "PointLightProperty.hpp" #include "ImNexo/EntityProperties.hpp" #include "ImNexo/ImNexo.hpp" +#include "ImNexo/Widgets.hpp" #include "components/Light.hpp" #include "components/Transform.hpp" -#include "context/actions/EntityActions.hpp" -#include "ImNexo/Widgets.hpp" #include "context/ActionManager.hpp" +#include "context/actions/EntityActions.hpp" namespace nexo::editor { - void PointLightProperty::show(const ecs::Entity entity) - { + void PointLightProperty::show(const ecs::Entity entity) + { auto& pointComponent = Application::getEntityComponent(entity); - auto &transform = Application::getEntityComponent(entity); + auto& transform = Application::getEntityComponent(entity); static components::PointLightComponent::Memento beforeStatePoint; static components::TransformComponent::Memento beforeStateTransform; - if (ImNexo::Header("##PointNode", "Point light")) - { - auto pointComponentCopy = pointComponent; + if (ImNexo::Header("##PointNode", "Point light")) { + auto pointComponentCopy = pointComponent; auto transformComponentCopy = transform; ImNexo::resetItemStates(); ImNexo::PointLight(pointComponent, transform); if (ImNexo::isItemActivated()) { - beforeStatePoint = pointComponentCopy.save(); + beforeStatePoint = pointComponentCopy.save(); beforeStateTransform = transformComponentCopy.save(); } else if (ImNexo::isItemDeactivated()) { - auto afterStatePoint = pointComponent.save(); + auto afterStatePoint = pointComponent.save(); auto afterStateTransform = transform.save(); - auto actionGroup = ActionManager::createActionGroup(); - auto pointAction = std::make_unique>(entity, beforeStatePoint, afterStatePoint); - auto transformAction = std::make_unique>(entity, beforeStateTransform, afterStateTransform); + auto actionGroup = ActionManager::createActionGroup(); + auto pointAction = std::make_unique>( + entity, beforeStatePoint, afterStatePoint); + auto transformAction = std::make_unique>( + entity, beforeStateTransform, afterStateTransform); actionGroup->addAction(std::move(pointAction)); actionGroup->addAction(std::move(transformAction)); ActionManager::get().recordAction(std::move(actionGroup)); } - ImGui::TreePop(); + ImGui::TreePop(); } - } -} + } +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.hpp b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.hpp index 07baa7547..1e23014c2 100644 --- a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.hpp @@ -16,19 +16,19 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class PointLightProperty final : public AEntityProperty { - public: - using AEntityProperty::AEntityProperty; + class PointLightProperty final : public AEntityProperty { + public: + using AEntityProperty::AEntityProperty; - /** - * @brief Displays and edits the point light properties of a given entity. - * - * Retrieves the PointLightComponent from the specified entity and renders a UI for editing its properties. - * The interface allows for color adjustment, position modification via a draggable table, and max distance tuning - * using a slider, which in turn updates the light's attenuation coefficients. - * - * @param entity The entity owning the PointLightComponent to be modified. - */ - void show(ecs::Entity entity) override; - }; -} + /** + * @brief Displays and edits the point light properties of a given entity. + * + * Retrieves the PointLightComponent from the specified entity and renders a UI for editing its properties. + * The interface allows for color adjustment, position modification via a draggable table, and max distance + * tuning using a slider, which in turn updates the light's attenuation coefficients. + * + * @param entity The entity owning the PointLightComponent to be modified. + */ + void show(ecs::Entity entity) override; + }; +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index 3294c9288..38c40a09d 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -14,17 +14,17 @@ #include -#include "RenderProperty.hpp" #include "Application.hpp" #include "Framebuffer.hpp" -#include "components/Light.hpp" -#include "context/actions/EntityActions.hpp" -#include "utils/ScenePreview.hpp" +#include "ImNexo/Components.hpp" +#include "ImNexo/Elements.hpp" +#include "RenderProperty.hpp" #include "components/Camera.hpp" +#include "components/Light.hpp" #include "components/Render.hpp" #include "context/ActionManager.hpp" -#include "ImNexo/Elements.hpp" -#include "ImNexo/Components.hpp" +#include "context/actions/EntityActions.hpp" +#include "utils/ScenePreview.hpp" namespace nexo::editor { @@ -42,19 +42,19 @@ namespace nexo::editor { const ImVec2 availSize = ImGui::GetContentRegionAvail(); const float totalWidth = availSize.x; - float totalHeight = availSize.y - 40; // Reserve space for bottom buttons + float totalHeight = availSize.y - 40; // Reserve space for bottom buttons // Define layout: 40% for inspector, 60% for preview const float inspectorWidth = totalWidth * 0.4f; - const float previewWidth = totalWidth - inspectorWidth - 8; // Subtract spacing between panels + const float previewWidth = totalWidth - inspectorWidth - 8; // Subtract spacing between panels static utils::ScenePreviewOut scenePreviewInfo; - if (!scenePreviewInfo.sceneGenerated) - { + if (!scenePreviewInfo.sceneGenerated) { utils::genScenePreview("New Material Preview", {previewWidth - 8, totalHeight}, entity, scenePreviewInfo); - auto &cameraComponent = Application::m_coordinator->getComponent(scenePreviewInfo.cameraId); - cameraComponent.clearColor = {67.0f/255.0f, 65.0f/255.0f, 80.0f/255.0f, 111.0f/255.0f}; - cameraComponent.render = true; + auto &cameraComponent = + Application::m_coordinator->getComponent(scenePreviewInfo.cameraId); + cameraComponent.clearColor = {67.0f / 255.0f, 65.0f / 255.0f, 80.0f / 255.0f, 111.0f / 255.0f}; + cameraComponent.render = true; } ImGui::Columns(2, "MaterialPreviewColumns", false); @@ -77,18 +77,19 @@ namespace nexo::editor { auto &app = getApp(); const Application::SceneInfo sceneInfo{scenePreviewInfo.sceneId, nexo::RenderingType::FRAMEBUFFER}; app.run(sceneInfo); - auto const &cameraComponent = Application::m_coordinator->getComponent(scenePreviewInfo.cameraId); + auto const &cameraComponent = + Application::m_coordinator->getComponent(scenePreviewInfo.cameraId); const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); - const float aspectRatio = static_cast(cameraComponent.width) / - static_cast(cameraComponent.height); + const float aspectRatio = + static_cast(cameraComponent.width) / static_cast(cameraComponent.height); const float displayHeight = totalHeight - 20; - const float displayWidth = displayHeight * aspectRatio; + const float displayWidth = displayHeight * aspectRatio; ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 4, ImGui::GetCursorPosY() + 4)); ImNexo::Image(static_cast(static_cast(textureId)), - ImVec2(displayWidth, displayHeight)); + ImVec2(displayWidth, displayHeight)); ImGui::EndChild(); } @@ -99,13 +100,11 @@ namespace nexo::editor { // Bottom buttons - centered constexpr float buttonWidth = 120.0f; - if (ImNexo::Button("OK", ImVec2(buttonWidth, 0))) - { + if (ImNexo::Button("OK", ImVec2(buttonWidth, 0))) { // TODO: Insert logic to create the new material // Clean up preview scene - if (scenePreviewInfo.sceneGenerated) - { + if (scenePreviewInfo.sceneGenerated) { auto &app = getApp(); app.getSceneManager().deleteScene(scenePreviewInfo.sceneId); scenePreviewInfo.sceneGenerated = false; @@ -114,10 +113,8 @@ namespace nexo::editor { ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - if (ImNexo::Button("Cancel", ImVec2(buttonWidth, 0))) - { - if (scenePreviewInfo.sceneGenerated) - { + if (ImNexo::Button("Cancel", ImVec2(buttonWidth, 0))) { + if (scenePreviewInfo.sceneGenerated) { auto &app = getApp(); app.getSceneManager().deleteScene(scenePreviewInfo.sceneId); scenePreviewInfo.sceneGenerated = false; @@ -133,18 +130,18 @@ namespace nexo::editor { Application::m_coordinator->entityHasComponent(entity) || Application::m_coordinator->entityHasComponent(entity)) return; - auto& renderComponent = Application::getEntityComponent(entity); + auto &renderComponent = Application::getEntityComponent(entity); - if (ImNexo::Header("##RenderNode", "Render Component")) - { + if (ImNexo::Header("##RenderNode", "Render Component")) { ImGui::Text("Hide"); ImGui::SameLine(0, 12); bool hidden = !renderComponent.isRendered; if (ImGui::Checkbox("##HideCheckBox", &hidden)) { - auto beforeState = renderComponent.save(); + auto beforeState = renderComponent.save(); renderComponent.isRendered = !hidden; - auto afterState = renderComponent.save(); - auto action = std::make_unique>(entity, beforeState, afterState); + auto afterState = renderComponent.save(); + auto action = std::make_unique>(entity, beforeState, + afterState); ActionManager::get().recordAction(std::move(action)); } @@ -158,4 +155,4 @@ namespace nexo::editor { ImGui::TreePop(); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.hpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.hpp index d95ab4653..2e705574e 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.hpp @@ -17,37 +17,29 @@ #include "AEntityProperty.hpp" #include "DocumentWindows/PopupManager.hpp" -namespace nexo::editor -{ +#include - constexpr const char *PrimitiveTypeNames[] = { - "UNKNOWN", - "CUBE", - "MESH", - "BILLBOARD" - }; +namespace nexo::editor { - static_assert( - static_cast(components::PrimitiveType::_COUNT) == std::size(PrimitiveTypeNames), - "PrimitiveTypeNames array size must match PrimitiveType enum size" - ); + constexpr std::array PrimitiveTypeNames = {"UNKNOWN", "CUBE", "MESH", "BILLBOARD"}; + static_assert(static_cast(components::PrimitiveType::_COUNT) == std::size(PrimitiveTypeNames), + "PrimitiveTypeNames array size must match PrimitiveType enum size"); - class RenderProperty final : public AEntityProperty - { - public: + class RenderProperty final : public AEntityProperty { + public: using AEntityProperty::AEntityProperty; /** - * @brief Displays and manages the render properties UI for the specified entity. - * - * This function retrieves the render component of the given entity and renders a user interface - * for its render properties using ImGui. It configures the material inspector for 3D entities and - * sets up a placeholder for 2D entities. When the material section is active, a scene preview is generated, - * allowing the user to view the material in a framebuffer. The UI also offers controls to toggle entity - * visibility, select a material type from a dropdown, and open popups to create or modify materials. - * - * @param entity The entity whose render properties are to be displayed. - */ + * @brief Displays and manages the render properties UI for the specified entity. + * + * This function retrieves the render component of the given entity and renders a user interface + * for its render properties using ImGui. It configures the material inspector for 3D entities and + * sets up a placeholder for 2D entities. When the material section is active, a scene preview is generated, + * allowing the user to view the material in a framebuffer. The UI also offers controls to toggle entity + * visibility, select a material type from a dropdown, and open popups to create or modify materials. + * + * @param entity The entity whose render properties are to be displayed. + */ void show(ecs::Entity entity) override; /** @@ -55,14 +47,15 @@ namespace nexo::editor * * Renders a two-column popup using ImGui where the left panel accepts material inputs (name and properties) * via a material inspector, and the right panel shows a live preview of the material rendered in a framebuffer. - * If a preview scene hasn’t been generated yet, it is created for the specified dimensions. When the user confirms - * by clicking "OK", the material is linked to the entity's render component and the preview scene is cleaned up; - * clicking "Cancel" simply deletes the preview scene. + * If a preview scene hasn’t been generated yet, it is created for the specified dimensions. When the user + * confirms by clicking "OK", the material is linked to the entity's render component and the preview scene is + * cleaned up; clicking "Cancel" simply deletes the preview scene. * * @param entity The entity associated with the material being created. */ static void createMaterialPopup(ecs::Entity entity); - private: + + private: PopupManager m_popupManager; }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp index 7630696fb..98656a125 100644 --- a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp @@ -15,42 +15,43 @@ #include "SpotLightProperty.hpp" #include "ImNexo/EntityProperties.hpp" #include "ImNexo/ImNexo.hpp" +#include "ImNexo/Widgets.hpp" #include "components/Light.hpp" #include "components/Transform.hpp" -#include "context/actions/EntityActions.hpp" -#include "ImNexo/Widgets.hpp" #include "context/ActionManager.hpp" +#include "context/actions/EntityActions.hpp" namespace nexo::editor { - void SpotLightProperty::show(ecs::Entity entity) - { - auto& spotComponent = Application::getEntityComponent(entity); - auto &transformComponent = Application::getEntityComponent(entity); + void SpotLightProperty::show(ecs::Entity entity) + { + auto& spotComponent = Application::getEntityComponent(entity); + auto& transformComponent = Application::getEntityComponent(entity); static components::SpotLightComponent::Memento beforeStateSpot; static components::TransformComponent::Memento beforeStateTransform; - if (ImNexo::Header("##SpotNode", "Spot light")) - { + if (ImNexo::Header("##SpotNode", "Spot light")) { ImNexo::resetItemStates(); - auto spotComponentCopy = spotComponent; + auto spotComponentCopy = spotComponent; auto transformComponentCopy = transformComponent; ImNexo::SpotLight(spotComponent, transformComponent); if (ImNexo::isItemActivated()) { - beforeStateSpot = spotComponentCopy.save(); + beforeStateSpot = spotComponentCopy.save(); beforeStateTransform = transformComponentCopy.save(); } else if (ImNexo::isItemDeactivated()) { - auto afterStateSpot = spotComponent.save(); + auto afterStateSpot = spotComponent.save(); auto afterStateTransform = transformComponent.save(); - auto actionGroup = ActionManager::createActionGroup(); - auto spotAction = std::make_unique>(entity, beforeStateSpot, afterStateSpot); - auto transformAction = std::make_unique>(entity, beforeStateTransform, afterStateTransform); + auto actionGroup = ActionManager::createActionGroup(); + auto spotAction = std::make_unique>( + entity, beforeStateSpot, afterStateSpot); + auto transformAction = std::make_unique>( + entity, beforeStateTransform, afterStateTransform); actionGroup->addAction(std::move(spotAction)); actionGroup->addAction(std::move(transformAction)); ActionManager::get().recordAction(std::move(actionGroup)); } - ImGui::TreePop(); + ImGui::TreePop(); } - } -} + } +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.hpp b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.hpp index cf451c3b1..93f5ad77c 100644 --- a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.hpp @@ -16,20 +16,20 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class SpotLightProperty final : public AEntityProperty { - public: - using AEntityProperty::AEntityProperty; + class SpotLightProperty final : public AEntityProperty { + public: + using AEntityProperty::AEntityProperty; - /** - * @brief Renders the spotlight properties editor for the specified entity. - * - * Retrieves the spotlight component from the given entity and displays a user interface - * for editing its properties, including color, direction, position, and cutoff parameters. - * Adjusting the maximum distance automatically recalculates the linear and quadratic - * attenuation factors, while the cutoff angles are presented in degrees for easier modification. - * - * @param entity The entity whose spotlight component will be edited. - */ - void show(ecs::Entity entity) override; - }; -} + /** + * @brief Renders the spotlight properties editor for the specified entity. + * + * Retrieves the spotlight component from the given entity and displays a user interface + * for editing its properties, including color, direction, position, and cutoff parameters. + * Adjusting the maximum distance automatically recalculates the linear and quadratic + * attenuation factors, while the cutoff angles are presented in degrees for easier modification. + * + * @param entity The entity whose spotlight component will be edited. + */ + void show(ecs::Entity entity) override; + }; +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp index daefd6a58..e03bbb3e2 100644 --- a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp @@ -14,10 +14,10 @@ #include -#include "TransformProperty.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" #include "ImNexo/ImNexo.hpp" +#include "TransformProperty.hpp" #include "components/Light.hpp" #include "components/Transform.hpp" #include "context/ActionManager.hpp" @@ -33,8 +33,7 @@ namespace nexo::editor { static glm::vec3 lastDisplayedEuler(0.0f); static components::TransformComponent::Memento beforeState; - if (ImNexo::Header("##TransformNode", "Transform Component")) - { + if (ImNexo::Header("##TransformNode", "Transform Component")) { const auto transformComponentCopy = transformComponent; ImNexo::resetItemStates(); ImNexo::Transform(transformComponent, lastDisplayedEuler); @@ -42,10 +41,11 @@ namespace nexo::editor { beforeState = transformComponentCopy.save(); } else if (ImNexo::isItemDeactivated()) { auto afterState = transformComponent.save(); - auto action = std::make_unique>(entity, beforeState, afterState); + auto action = std::make_unique>( + entity, beforeState, afterState); ActionManager::get().recordAction(std::move(action)); } ImGui::TreePop(); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/TransformProperty.hpp b/editor/src/DocumentWindows/EntityProperties/TransformProperty.hpp index 9f844dded..c755e2b74 100644 --- a/editor/src/DocumentWindows/EntityProperties/TransformProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/TransformProperty.hpp @@ -18,19 +18,19 @@ namespace nexo::editor { class TransformProperty final : public AEntityProperty { - public: - using AEntityProperty::AEntityProperty; + public: + using AEntityProperty::AEntityProperty; - /** - * @brief Displays and edits the transform properties of an entity using an ImGui interface. - * - * Retrieves the transform component (position, scale, and rotation quaternion) of the given entity, - * displaying the values in an ImGui table. The rotation is converted from a quaternion to Euler angles - * to allow intuitive editing; any changes in Euler angles are applied incrementally back to the quaternion, - * ensuring it remains normalized. - * - * @param entity The entity whose transform properties are rendered. - */ - void show(ecs::Entity entity) override; + /** + * @brief Displays and edits the transform properties of an entity using an ImGui interface. + * + * Retrieves the transform component (position, scale, and rotation quaternion) of the given entity, + * displaying the values in an ImGui table. The rotation is converted from a quaternion to Euler angles + * to allow intuitive editing; any changes in Euler angles are applied incrementally back to the quaternion, + * ensuring it remains normalized. + * + * @param entity The entity whose transform properties are rendered. + */ + void show(ecs::Entity entity) override; }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp b/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp index fc56c8e9f..de01b4a5a 100644 --- a/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp @@ -20,65 +20,69 @@ #include "ImNexo/Elements.hpp" namespace nexo::editor { - - void showField(const ecs::Field& field, void *data) + void showField(const ecs::Field &field, void *data) { + using enum ecs::FieldType; switch (field.type) { - case ecs::FieldType::Bool: + case Bool: static_assert(sizeof(bool) == 1 && "Size of bool must be 1 byte"); ImGui::Checkbox(field.name.c_str(), static_cast(data)); break; - case ecs::FieldType::Int8: + case Int8: ImGui::InputScalar(field.name.c_str(), ImGuiDataType_S8, data); break; - case ecs::FieldType::Int16: + case Int16: ImGui::InputScalar(field.name.c_str(), ImGuiDataType_S16, data); break; - case ecs::FieldType::Int32: + case Int32: ImGui::InputScalar(field.name.c_str(), ImGuiDataType_S32, data); break; - case ecs::FieldType::Int64: + case Int64: ImGui::InputScalar(field.name.c_str(), ImGuiDataType_S64, data); break; - case ecs::FieldType::UInt8: + case UInt8: ImGui::InputScalar(field.name.c_str(), ImGuiDataType_U8, data); - break;; - case ecs::FieldType::UInt16: + break; + case UInt16: ImGui::InputScalar(field.name.c_str(), ImGuiDataType_U16, data); break; - case ecs::FieldType::UInt32: + case UInt32: ImGui::InputScalar(field.name.c_str(), ImGuiDataType_U32, data); break; - case ecs::FieldType::UInt64: + case UInt64: ImGui::InputScalar(field.name.c_str(), ImGuiDataType_U64, data); break; - case ecs::FieldType::Float: + case Float: ImGui::InputFloat(field.name.c_str(), static_cast(data)); break; - case ecs::FieldType::Double: + case Double: ImGui::InputDouble(field.name.c_str(), static_cast(data)); break; // Widgets - case ecs::FieldType::Vector3: - if (ImGui::BeginTable("InspectorTransformTable", 4, - ImGuiTableFlags_SizingStretchProp)) - { + case Vector3: + if (ImGui::BeginTable("InspectorTransformTable", 4, ImGuiTableFlags_SizingStretchProp)) { // Only the first column has a fixed width - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##X", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Y", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Z", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImNexo::RowDragFloat3(field.name.c_str(), "X", "Y", "Z", static_cast(data)); ImGui::EndTable(); } break; - case ecs::FieldType::Vector4: - ImGui::Text("Cannot edit Vector4 for now"); // TODO: Implement Vector4 editing + case Vector4: + ImGui::Text("Cannot edit Vector4 for now"); + // TODO: Implement Vector4 editing + break; + default: break; - default: return; } } @@ -89,7 +93,7 @@ namespace nexo::editor { return; } - const auto& coordinator = Application::m_coordinator; + const auto &coordinator = Application::m_coordinator; const auto componentData = static_cast(coordinator->tryGetComponentById(m_componentType, entity)); if (!componentData) { @@ -97,9 +101,8 @@ namespace nexo::editor { return; } - if (ImNexo::Header(std::format("##{}", m_description->name), m_description->name + " Component")) - { - for (const auto& field : m_description->fields) { + if (ImNexo::Header(std::format("##{}", m_description->name), m_description->name + " Component")) { + for (const auto &field : m_description->fields) { // Move to pointer to next field data const auto currentComponentData = componentData + field.offset; // Show the field in the UI @@ -108,9 +111,6 @@ namespace nexo::editor { ImGui::TreePop(); } - - } - -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.hpp b/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.hpp index ab05d075c..feaa450a1 100644 --- a/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.hpp @@ -20,27 +20,34 @@ namespace nexo::editor { class TypeErasedProperty final : public AEntityProperty { - public: - TypeErasedProperty(InspectorWindow &inspector, const ecs::ComponentType componentType, const std::shared_ptr& description) - : AEntityProperty(inspector) - , m_componentType(componentType), m_description(description) - { - } + public: + /** + * @brief Constructs a TypeErasedProperty for displaying and editing properties of a specific component type. + * + * @param inspector Reference to the InspectorWindow managing this property. + * @param componentType The type identifier of the component whose properties will be displayed. + * @param description Shared pointer to the ComponentDescription containing metadata about the component's + * fields. + */ + TypeErasedProperty(InspectorWindow& inspector, const ecs::ComponentType componentType, + const std::shared_ptr& description) + : AEntityProperty(inspector), m_componentType(componentType), m_description(description) + {} - /** - * @brief Displays and edits the properties of an entity component using an ImGui interface. - * - * Retrieves the component data for the given entity based on the stored component type, - * and renders editable fields according to the component description metadata. - * Supports various field types including primitives and vector types. - * - * @param entity The entity whose component properties are rendered. - */ - void show(ecs::Entity entity) override; + /** + * @brief Displays and edits the properties of an entity component using an ImGui interface. + * + * Retrieves the component data for the given entity based on the stored component type, + * and renders editable fields according to the component description metadata. + * Supports various field types including primitives and vector types. + * + * @param entity The entity whose component properties are rendered. + */ + void show(ecs::Entity entity) override; - private: - const ecs::ComponentType m_componentType; // Type of the component being displayed - const std::shared_ptr m_description; // Description of the component being displayed + private: + const ecs::ComponentType m_componentType; // Type of the component being displayed + const std::shared_ptr m_description; // Description of the component being displayed }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/GameWindow/GameWindow.cpp b/editor/src/DocumentWindows/GameWindow/GameWindow.cpp index c94573bf5..a747f7854 100644 --- a/editor/src/DocumentWindows/GameWindow/GameWindow.cpp +++ b/editor/src/DocumentWindows/GameWindow/GameWindow.cpp @@ -6,7 +6,7 @@ // zzz zzz zzz z zzzz zzzz zzzz zzzz // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // -// Author: Cardonne +// Author: Jean CARDONNE // Date: 2025-06-24 // Description: Main implementation file for GameWindow // @@ -21,9 +21,9 @@ namespace nexo::editor { m_sceneId = sceneId; } - void GameWindow::setSceneUuid(const std::string& sceneUuid) + void GameWindow::setSceneUuid(const std::string_view sceneUuid) { m_sceneUuid = sceneUuid; } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/GameWindow/GameWindow.hpp b/editor/src/DocumentWindows/GameWindow/GameWindow.hpp index 455eefff1..aa809961a 100644 --- a/editor/src/DocumentWindows/GameWindow/GameWindow.hpp +++ b/editor/src/DocumentWindows/GameWindow/GameWindow.hpp @@ -6,7 +6,7 @@ // zzz zzz zzz z zzzz zzzz zzzz zzzz // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // -// Author: Cardonne +// Author: Jean CARDONNE // Date: 2025-06-24 // Description: Game window for play mode - displays game view without editor elements // @@ -14,30 +14,72 @@ #pragma once -#include "ADocumentWindow.hpp" +#include #include "../PopupManager.hpp" +#include "ADocumentWindow.hpp" #include "ecs/Definitions.hpp" -#include -namespace nexo::editor -{ +namespace nexo::editor { class EditorScene; - class GameWindow final : public ADocumentWindow - { - public: + class GameWindow final : public ADocumentWindow { + public: using ADocumentWindow::ADocumentWindow; + /** + * @brief Sets up the game window, initializing necessary resources and configurations. + * + * This method is called when the game window is created. It initializes the viewport, + * camera entity, and any other resources required for rendering the game view. + */ void setup() override; + + /** + * @brief Cleans up resources used by the game window. + * + * This method is called when the game window is closed. It releases any resources + * allocated during setup and ensures that the game window is properly shut down. + */ void shutdown() override; + + /** + * @brief Renders the game window and its contents. + * + * This method is called to display the game window. It handles rendering the game scene, + * updating the viewport, and managing user interactions within the game window. + */ void show() override; + + /** + * @brief Updates the game window state. + * + * This method is called periodically to update the game window's state, including + * handling input, updating the scene, and refreshing the display as needed. + */ void update() override; + /** + * @brief Sets the ID of the scene to be displayed in the game window. + * + * This method assigns the specified scene ID to the game window, allowing it to + * load and render the corresponding scene. + * + * @param sceneId The ID of the scene to be set for the game window. + */ void setSceneId(unsigned int sceneId); - void setSceneUuid(const std::string &sceneUuid); - private: + /** + * @brief Sets the UUID of the scene to be displayed in the game window. + * + * This method assigns the specified UUID to the scene that the game window will render. + * It is used to identify and load the correct scene for display. + * + * @param sceneUuid The UUID of the scene to be set for the game window. + */ + void setSceneUuid(std::string_view sceneUuid); + + private: unsigned int m_sceneId{0}; std::string m_sceneUuid; @@ -48,8 +90,21 @@ namespace nexo::editor bool m_isPaused{false}; PopupManager m_popupManager; + /** + * @brief Renders the viewport area of the game window. + * + * This method handles the rendering of the game viewport, including setting up + * the camera view, drawing the scene, and managing any necessary rendering states. + */ void renderViewport(); + + /** + * @brief Renders the toolbar of the game window. + * + * This method is responsible for drawing the toolbar at the top of the game window, + * which may include buttons for play, pause, stop, and other game controls. + */ void renderToolbar(); }; -} \ No newline at end of file +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/GameWindow/Setup.cpp b/editor/src/DocumentWindows/GameWindow/Setup.cpp index cbfe93242..b3418e87d 100644 --- a/editor/src/DocumentWindows/GameWindow/Setup.cpp +++ b/editor/src/DocumentWindows/GameWindow/Setup.cpp @@ -12,27 +12,26 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "GameWindow.hpp" #include "Application.hpp" -#include "ecs/Coordinator.hpp" +#include "CameraFactory.hpp" +#include "GameWindow.hpp" +#include "Logger.hpp" #include "components/Camera.hpp" #include "components/Transform.hpp" #include "core/scene/SceneManager.hpp" +#include "ecs/Coordinator.hpp" #include "renderer/Framebuffer.hpp" -#include "CameraFactory.hpp" -#include "Logger.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void GameWindow::setup() { LOG(NEXO_INFO, "Setting up GameWindow for scene {}", m_sceneId); auto &coordinator = *Application::m_coordinator; - auto &app = getApp(); - auto &scene = app.getSceneManager().getScene(m_sceneId); - + auto &app = getApp(); + auto &scene = app.getSceneManager().getScene(m_sceneId); + // Check if a main camera already exists in the scene bool mainCameraFound = false; for (const auto &entity : scene.getEntities()) { @@ -40,16 +39,16 @@ namespace nexo::editor auto &camera = coordinator.getComponent(entity); if (camera.main) { // Found an existing main camera, use it with its existing render target - m_gameCamera = entity; - camera.render = true; - camera.active = true; + m_gameCamera = entity; + camera.render = true; + camera.active = true; mainCameraFound = true; LOG(NEXO_INFO, "Using existing main camera {} for scene {}", m_gameCamera, m_sceneId); break; } } } - + // Only create a new camera if no main camera exists if (!mainCameraFound) { // Create render target specs for new camera @@ -58,18 +57,16 @@ namespace nexo::editor renderer::NxFrameBufferTextureFormats::RGBA8, renderer::NxFrameBufferTextureFormats::RED_INTEGER, // Required by render system renderer::NxFrameBufferTextureFormats::Depth}; - framebufferSpecs.width = 1280; // Default size, will be resized + framebufferSpecs.width = 1280; // Default size, will be resized framebufferSpecs.height = 720; const auto renderTarget = renderer::NxFramebuffer::create(framebufferSpecs); - + // Create a render camera - m_gameCamera = CameraFactory::createPerspectiveCamera( - {0.0f, 0.0f, 6.0f}, // Default position - framebufferSpecs.width, - framebufferSpecs.height, - renderTarget); + m_gameCamera = + CameraFactory::createPerspectiveCamera({0.0f, 0.0f, 6.0f}, // Default position + framebufferSpecs.width, framebufferSpecs.height, renderTarget); - auto &cameraComponent = coordinator.getComponent(m_gameCamera); + auto &cameraComponent = coordinator.getComponent(m_gameCamera); cameraComponent.render = true; cameraComponent.active = true; @@ -80,4 +77,4 @@ namespace nexo::editor } } -} \ No newline at end of file +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/GameWindow/Show.cpp b/editor/src/DocumentWindows/GameWindow/Show.cpp index 8f8de2355..a4d6b2b7b 100644 --- a/editor/src/DocumentWindows/GameWindow/Show.cpp +++ b/editor/src/DocumentWindows/GameWindow/Show.cpp @@ -12,20 +12,19 @@ // /////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include "Application.hpp" #include "GameWindow.hpp" #include "IconsFontAwesome.h" #include "ImNexo/Elements.hpp" #include "ImNexo/Widgets.hpp" -#include "Application.hpp" -#include "ecs/Coordinator.hpp" #include "components/Camera.hpp" -#include -#include -#include +#include "ecs/Coordinator.hpp" #include "ecs/Definitions.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void GameWindow::show() { @@ -37,7 +36,7 @@ namespace nexo::editor ImGui::Begin(windowTitle.c_str(), &m_opened, ImGuiWindowFlags_NoCollapse); // Call beginRender to handle docking and state tracking - beginRender("###GameWindow" + std::to_string(m_sceneId)); + beginRender(std::format("###GameWindow%s", std::to_string(m_sceneId))); // Render the viewport first renderViewport(); @@ -49,11 +48,11 @@ namespace nexo::editor void GameWindow::renderToolbar() { - constexpr float buttonWidth = 35.0f; + constexpr float buttonWidth = 35.0f; constexpr float buttonHeight = 35.0f; const ImVec2 windowContentMin = ImGui::GetWindowContentRegionMin(); - ImVec2 toolbarPos = m_windowPos; + ImVec2 toolbarPos = m_windowPos; toolbarPos.x += windowContentMin.x + 10.0f; toolbarPos.y += 20.0f; @@ -63,50 +62,41 @@ namespace nexo::editor ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.1f, 0.1f, 0.1f, 0.0f)); ImGui::BeginChild("##GameToolbarOverlay", toolbarSize, 0, - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoSavedSettings); + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings); ImGui::SetCursorPosY((ImGui::GetWindowHeight() - ImGui::GetFrameHeight()) * 0.5f); // Stop button - static const std::vector standardGradient = { - {0.0f, IM_COL32(50, 50, 70, 230)}, - {1.0f, IM_COL32(30, 30, 45, 230)}}; + static const std::vector standardGradient = {{0.0f, IM_COL32(50, 50, 70, 230)}, + {1.0f, IM_COL32(30, 30, 45, 230)}}; - if (ImNexo::IconGradientButton("stop_game", ICON_FA_STOP, ImVec2(buttonWidth, buttonHeight), standardGradient)) - { + if (ImNexo::IconGradientButton("stop_game", ICON_FA_STOP, ImVec2(buttonWidth, buttonHeight), + standardGradient)) { m_opened = false; } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Stop game"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Stop game"); ImGui::SameLine(); // Pause/Resume button - if (m_isPaused) - { + if (m_isPaused) { // Resume button - static const std::vector selectedGradient = { - {0.0f, IM_COL32(70, 70, 120, 230)}, - {1.0f, IM_COL32(50, 50, 100, 230)}}; + static const std::vector selectedGradient = {{0.0f, IM_COL32(70, 70, 120, 230)}, + {1.0f, IM_COL32(50, 50, 100, 230)}}; - if (ImNexo::IconGradientButton("resume_game", ICON_FA_PLAY, ImVec2(buttonWidth, buttonHeight), selectedGradient)) - { + if (ImNexo::IconGradientButton("resume_game", ICON_FA_PLAY, ImVec2(buttonWidth, buttonHeight), + selectedGradient)) { m_isPaused = false; } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Resume game"); - } - else - { + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Resume game"); + } else { // Pause button - if (ImNexo::IconGradientButton("pause_game", ICON_FA_PAUSE, ImVec2(buttonWidth, buttonHeight), standardGradient)) - { + if (ImNexo::IconGradientButton("pause_game", ICON_FA_PAUSE, ImVec2(buttonWidth, buttonHeight), + standardGradient)) { m_isPaused = true; } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Pause game"); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Pause game"); } ImGui::EndChild(); @@ -117,8 +107,7 @@ namespace nexo::editor { auto &coordinator = *Application::m_coordinator; - if (m_gameCamera == ecs::INVALID_ENTITY) - { + if (m_gameCamera == ecs::INVALID_ENTITY) { // No game camera, render a message const ImVec2 textSize = ImGui::CalcTextSize("No game camera"); ImGui::SetCursorPos(ImVec2((m_contentSize.x - textSize.x) / 2, (m_contentSize.y - textSize.y) / 2)); @@ -128,10 +117,9 @@ namespace nexo::editor // Try to get camera component - entity might have been deleted const auto cameraCompOpt = coordinator.tryGetComponent(m_gameCamera); - if (!cameraCompOpt) - { + if (!cameraCompOpt) { // Camera entity was deleted, reset to invalid - m_gameCamera = ecs::INVALID_ENTITY; + m_gameCamera = ecs::INVALID_ENTITY; const ImVec2 textSize = ImGui::CalcTextSize("Camera was deleted"); ImGui::SetCursorPos(ImVec2((m_contentSize.x - textSize.x) / 2, (m_contentSize.y - textSize.y) / 2)); ImGui::Text("Camera was deleted"); @@ -139,14 +127,13 @@ namespace nexo::editor } auto &cameraComponent = cameraCompOpt->get(); - if (!cameraComponent.m_renderTarget) - return; + if (!cameraComponent.m_renderTarget) return; const glm::vec2 renderTargetSize = cameraComponent.m_renderTarget->getSize(); // Resize handling - if (!cameraComponent.viewportLocked && (m_contentSize.x > 0 && m_contentSize.y > 0) && (m_contentSize.x != renderTargetSize.x || m_contentSize.y != renderTargetSize.y)) - { + if (!cameraComponent.viewportLocked && (m_contentSize.x > 0 && m_contentSize.y > 0) && + (m_contentSize.x != renderTargetSize.x || m_contentSize.y != renderTargetSize.y)) { cameraComponent.resize(static_cast(m_contentSize.x), static_cast(m_contentSize.y)); } @@ -154,10 +141,10 @@ namespace nexo::editor // Store viewport bounds after rendering the image const ImVec2 viewportMin = ImGui::GetItemRectMin(); const ImVec2 viewportMax = ImGui::GetItemRectMax(); - m_viewportBounds[0] = viewportMin; - m_viewportBounds[1] = viewportMax; + m_viewportBounds[0] = viewportMin; + m_viewportBounds[1] = viewportMax; const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); ImNexo::Image(static_cast(static_cast(textureId)), m_contentSize); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/GameWindow/Update.cpp b/editor/src/DocumentWindows/GameWindow/Update.cpp index 82ea6a4fc..982ff2e0b 100644 --- a/editor/src/DocumentWindows/GameWindow/Update.cpp +++ b/editor/src/DocumentWindows/GameWindow/Update.cpp @@ -12,21 +12,19 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "GameWindow.hpp" #include "Application.hpp" -#include "ecs/Coordinator.hpp" -#include "ecs/Definitions.hpp" +#include "GameWindow.hpp" +#include "Logger.hpp" #include "components/Camera.hpp" #include "core/scene/SceneManager.hpp" -#include "Logger.hpp" +#include "ecs/Coordinator.hpp" +#include "ecs/Definitions.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void GameWindow::update() { - if (!m_opened) - { + if (!m_opened) { // the window will just close return; } @@ -34,26 +32,24 @@ namespace nexo::editor // When paused, we still render the scene but don't update game logic // TODO: When the engine supports pausing game systems, implement it here // For now, the pause state is tracked but doesn't affect the actual game update - + // The actual scene update and rendering happens through the render systems // which are managed by the Application class } void GameWindow::shutdown() { - auto &coordinator = *Application::m_coordinator; - auto &app = getApp(); + auto &coordinator = *Application::m_coordinator; + auto &app = getApp(); auto &sceneManager = app.getSceneManager(); // Clean up the game camera - if (m_gameCamera != ecs::INVALID_ENTITY) - { + if (m_gameCamera != ecs::INVALID_ENTITY) { // Try to get the camera component const auto cameraCompOpt = coordinator.tryGetComponent(m_gameCamera); - if (cameraCompOpt) - { + if (cameraCompOpt) { // Disable rendering - auto &cameraComp = cameraCompOpt->get(); + auto &cameraComp = cameraCompOpt->get(); cameraComp.render = false; cameraComp.active = false; @@ -64,9 +60,7 @@ namespace nexo::editor coordinator.destroyEntity(m_gameCamera); LOG(NEXO_INFO, "Destroyed game camera entity {}", m_gameCamera); - } - else - { + } else { // Camera was already destroyed LOG(NEXO_WARN, "Failed to properly clean up game camera"); } @@ -75,4 +69,4 @@ namespace nexo::editor } } -} \ No newline at end of file +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/InspectorWindow/Init.cpp b/editor/src/DocumentWindows/InspectorWindow/Init.cpp index b1856dc5b..e90daefe0 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Init.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Init.cpp @@ -12,19 +12,19 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "InspectorWindow.hpp" -#include "../EntityProperties/RenderProperty.hpp" -#include "../EntityProperties/TransformProperty.hpp" #include "../EntityProperties/AmbientLightProperty.hpp" -#include "../EntityProperties/DirectionalLightProperty.hpp" -#include "../EntityProperties/PointLightProperty.hpp" -#include "../EntityProperties/SpotLightProperty.hpp" -#include "../EntityProperties/CameraProperty.hpp" #include "../EntityProperties/CameraController.hpp" +#include "../EntityProperties/CameraProperty.hpp" #include "../EntityProperties/CameraTarget.hpp" -#include "DocumentWindows/EntityProperties/TypeErasedProperty.hpp" +#include "../EntityProperties/DirectionalLightProperty.hpp" #include "../EntityProperties/MaterialProperty.hpp" #include "../EntityProperties/PhysicsBodyProperty.hpp" +#include "../EntityProperties/PointLightProperty.hpp" +#include "../EntityProperties/RenderProperty.hpp" +#include "../EntityProperties/SpotLightProperty.hpp" +#include "../EntityProperties/TransformProperty.hpp" +#include "DocumentWindows/EntityProperties/TypeErasedProperty.hpp" +#include "InspectorWindow.hpp" #include "components/Camera.hpp" #include "components/MaterialComponent.hpp" #include "components/PhysicsBodyComponent.hpp" @@ -51,7 +51,7 @@ namespace nexo::editor { void InspectorWindow::registerTypeErasedProperties() { // Register TypeErased components - const auto& coordinator = Application::m_coordinator; + const auto& coordinator = Application::m_coordinator; const auto& componentDescriptions = coordinator->getComponentDescriptions(); for (const auto& [componentType, description] : componentDescriptions) { @@ -59,4 +59,4 @@ namespace nexo::editor { } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp index cb69279e6..b815b815f 100644 --- a/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp +++ b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp @@ -22,185 +22,196 @@ namespace nexo::editor { class InspectorWindow final : public ADocumentWindow { - public: - using ADocumentWindow::ADocumentWindow; - ~InspectorWindow() override = default; - - /** - * @brief Initializes the property handlers for various entity component types. - * - * This method populates the internal map that links component type identifiers (obtained via - * typeid) to their corresponding property display handlers, such as TransformProperty, - * RenderProperty, and various light and camera properties. These handlers are constructed - * with the current InspectorWindow instance and later used to display component-specific - * properties in the inspector UI. - */ - void setup() override; - - /** - * @brief Registers type-erased properties for all component descriptions in the coordinator. - * - * This method should be called after the scripting system has been initialized - * to ensure all managed components have been registered with their field metadata. - */ - void registerTypeErasedProperties(); - - // No-op method in this class - void shutdown() override; - - /** - * @brief Renders the Inspector window. - * - * Opens an ImGui window titled "Inspector" and, on its first display, configures docking by calling - * firstDockSetup("Inspector"). It retrieves the currently selected entity via the Selector singleton and, - * if a valid selection exists, displays either scene or entity properties depending on the selection type. - */ - void show() override; - - // No-op method in this class - void update() override; - - /** - * @brief Sets the visibility flag for the sub-inspector associated with type T. - * - * This template method updates the internal mapping of sub-inspector visibility, - * associating the specified visibility state with the sub-inspector corresponding - * to type T. - * - * @param visible The desired visibility state (true for visible, false for hidden). - */ - template - void setSubInspectorVisibility(const bool visible) - { - m_subInspectorVisibility[std::type_index(typeid(T))] = visible; - } - - /** - * @brief Associates material data with a sub-inspector of the specified type. - * - * Maps the type index of the templated sub-inspector (T) to the provided material data. - * If an entry for the given type already exists, it is updated with the new data. - * - * @tparam T The type of the sub-inspector. - * @param data Pointer to the material data to associate with the sub-inspector. - */ - template - void setSubInspectorData(Data &&data) - { - m_subInspectorData[std::type_index(typeid(T))] = std::forward(data); - } - - /** - * @brief Retrieves the visibility flag for a specific sub-inspector type. - * - * This function checks if a visibility flag has been set for the sub-inspector designated by the template parameter T. - * If no flag is found, it returns false. - * - * @tparam T The type of the sub-inspector. - * @return true if the sub-inspector is marked as visible; otherwise, false. - */ - template - bool getSubInspectorVisibility() const - { - const auto it = m_subInspectorVisibility.find(std::type_index(typeid(T))); - return (it != m_subInspectorVisibility.end()) ? it->second : false; - } - - /** - * @brief Retrieves a modifiable reference to the visibility flag for a sub-inspector. - * - * This template method returns a reference to the boolean flag representing the visibility - * of the sub-inspector associated with type T. If the flag does not exist, it is default- - * initialized (typically to false) and inserted. - * - * @tparam T The sub-inspector type. - * @return bool& A modifiable reference to the visibility flag for the specified sub-inspector. - */ - template - bool& getSubInspectorVisibility() - { - return m_subInspectorVisibility[std::type_index(typeid(T))]; - } - - /** - * @brief Retrieves the material data associated with the specified sub-inspector window type. - * - * This templated function searches for data in the sub-inspector data map using the type index of WindowType. - * If an entry for WindowType exists, it returns the associated pointer to a Data type; otherwise, it returns nullptr - * - * @tparam WindowType The sub-inspector type used to look up the associated data. - * @tparam Data The type of data to retrieve. - * @return A pointer to the Data type if found, or nullptr if not found. - */ - template - std::optional getSubInspectorData() const - { - const auto it = m_subInspectorData.find(std::type_index(typeid(WindowType))); - if (it != m_subInspectorData.end()) { - if (auto ptr = std::any_cast(&it->second)) { - return *ptr; - } - LOG(NEXO_ERROR, "Failed to cast sub-inspector data for type {}", - typeid(WindowType).name()); - return std::nullopt; + public: + using ADocumentWindow::ADocumentWindow; + ~InspectorWindow() override = default; + + /** + * @brief Initializes the property handlers for various entity component types. + * + * This method populates the internal map that links component type identifiers (obtained via + * typeid) to their corresponding property display handlers, such as TransformProperty, + * RenderProperty, and various light and camera properties. These handlers are constructed + * with the current InspectorWindow instance and later used to display component-specific + * properties in the inspector UI. + */ + void setup() override; + + /** + * @brief Registers type-erased properties for all component descriptions in the coordinator. + * + * This method should be called after the scripting system has been initialized + * to ensure all managed components have been registered with their field metadata. + */ + void registerTypeErasedProperties(); + + // No-op method in this class + void shutdown() override; + + /** + * @brief Renders the Inspector window. + * + * Opens an ImGui window titled "Inspector" and, on its first display, configures docking by calling + * firstDockSetup("Inspector"). It retrieves the currently selected entity via the Selector singleton and, + * if a valid selection exists, displays either scene or entity properties depending on the selection type. + */ + void show() override; + + // No-op method in this class + void update() override; + + /** @brief Sets the visibility flag for a specific sub-inspector type. + * + * This function updates the visibility flag for the sub-inspector designated by the template + * parameter T. The visibility state is stored in a map using the type index of T as the key. + * + * @tparam T The type of the sub-inspector. + * @param visible The visibility state to set (true for visible, false for hidden). + */ + template + void setSubInspectorVisibility(const bool visible) + { + m_subInspectorVisibility[std::type_index(typeid(T))] = visible; + } + + /** + * @brief Associates material data with a sub-inspector of the specified type. + * + * Maps the type index of the templated sub-inspector (T) to the provided material data. + * If an entry for the given type already exists, it is updated with the new data. + * + * @tparam T The type of the sub-inspector. + * @param data Pointer to the material data to associate with the sub-inspector. + */ + template + void setSubInspectorData(Data&& data) + { + m_subInspectorData[std::type_index(typeid(T))] = std::forward(data); + } + + /** + * @brief Retrieves the visibility flag for a specific sub-inspector type. + * + * This function checks if a visibility flag has been set for the sub-inspector designated by the template + * parameter T. If no flag is found, it returns false. + * + * @tparam T The type of the sub-inspector. + * @return true if the sub-inspector is marked as visible; otherwise, false. + */ + template + [[nodiscard]] bool getSubInspectorVisibility() const + { + const auto it = m_subInspectorVisibility.find(std::type_index(typeid(T))); + return (it != m_subInspectorVisibility.end()) ? it->second : false; + } + + /** + * @brief Retrieves a modifiable reference to the visibility flag for a sub-inspector. + * + * This template method returns a reference to the boolean flag representing the visibility + * of the sub-inspector associated with type T. If the flag does not exist, it is default- + * initialized (typically to false) and inserted. + * + * @tparam T The sub-inspector type. + * @return bool& A modifiable reference to the visibility flag for the specified sub-inspector. + */ + template + bool& getSubInspectorVisibility() + { + return m_subInspectorVisibility[std::type_index(typeid(T))]; + } + + /** + * @brief Retrieves the material data associated with the specified sub-inspector window type. + * + * This templated function searches for data in the sub-inspector data map using the type index of WindowType. + * If an entry for WindowType exists, it returns the associated pointer to a Data type; otherwise, it returns + * nullptr + * + * @tparam WindowType The sub-inspector type used to look up the associated data. + * @tparam Data The type of data to retrieve. + * @return A pointer to the Data type if found, or nullptr if not found. + */ + template + std::optional getSubInspectorData() const + { + const auto it = m_subInspectorData.find(std::type_index(typeid(WindowType))); + if (it != m_subInspectorData.end()) { + if (auto ptr = std::any_cast(&it->second)) { + return *ptr; } + LOG(NEXO_ERROR, "Failed to cast sub-inspector data for type {}", typeid(WindowType).name()); return std::nullopt; } - private: - std::unordered_map> m_entityProperties; - - std::unordered_map m_subInspectorVisibility; - std::unordered_map m_subInspectorData; - - /** - * @brief Displays the scene's properties in the inspector UI. - * - * Retrieves the scene corresponding to the provided SceneId and renders UI controls that allow toggling the scene's - * render and active statuses. The UI is configured with two columns where the "Hide" checkbox inverts the scene's - * rendering state and the "Pause" checkbox inverts its active state. An icon prefix is removed from the scene's UI handle - * before display. - * - * @param sceneId The identifier of the scene whose properties are to be displayed. - */ - static void showSceneProperties(scene::SceneId sceneId); - - /** - * @brief Renders the UI for the properties of an entity's components. - * - * Iterates through all component types associated with the given entity and, - * for each type that has a registered property handler in the m_entityProperties map, - * invokes its show() method to display the component's properties in the inspector. - * - * @param entity The entity whose component properties are being displayed. - */ - void showEntityProperties(ecs::Entity entity); - - /** - * @brief Registers a property for a given component type. - * - * This function creates a new property instance of type @p Property (which must be derived from AEntityProperty) - * associated with the given @p Component type. The property is stored in the internal entity properties map, - * using the type index of @p Component as the key. - * - * @tparam Component The type of the component that the property is associated with. - * @tparam Property The type of the property to register. Must be derived from AEntityProperty. - */ - template - requires std::derived_from - void registerProperty() - { - const auto type = Application::m_coordinator->getComponentType(); - m_entityProperties[type] = std::make_shared(*this); - } - - void registerProperty(const ecs::ComponentType type, std::shared_ptr property) - { - if (!property) { - LOG(NEXO_ERROR, "Attempted to register a null property for component type {}", type); - return; - } - m_entityProperties[type] = std::move(property); + return std::nullopt; + } + + private: + std::unordered_map> m_entityProperties; + + std::unordered_map m_subInspectorVisibility; + std::unordered_map m_subInspectorData; + + /** + * @brief Displays the scene's properties in the inspector UI. + * + * Retrieves the scene corresponding to the provided SceneId and renders UI controls that allow toggling the + * scene's render and active statuses. The UI is configured with two columns where the "Hide" checkbox inverts + * the scene's rendering state and the "Pause" checkbox inverts its active state. An icon prefix is removed from + * the scene's UI handle before display. + * + * @param sceneId The identifier of the scene whose properties are to be displayed. + */ + static void showSceneProperties(scene::SceneId sceneId); + + /** + * @brief Renders the UI for the properties of an entity's components. + * + * Iterates through all component types associated with the given entity and, + * for each type that has a registered property handler in the m_entityProperties map, + * invokes its show() method to display the component's properties in the inspector. + * + * @param entity The entity whose component properties are being displayed. + */ + void showEntityProperties(ecs::Entity entity); + + /** + * @brief Registers a property handler for a specific component type. + * + * This templated method allows registering a property handler for a given component type by + * specifying the component and property types. The property type must derive from AEntityProperty. + * The method retrieves the component type identifier from the coordinator and associates it with + * a new instance of the specified property type, constructed with the current InspectorWindow instance. + * + * @tparam Component The component type for which the property handler is being registered. + * @tparam Property The property handler type that must derive from AEntityProperty. + */ + template + requires std::derived_from + void registerProperty() + { + const auto type = Application::m_coordinator->getComponentType(); + m_entityProperties[type] = std::make_shared(*this); + } + + /** + * @brief Registers a property handler for a specific component type. + * + * This method allows registering a property handler for a given component type by directly providing + * the component type identifier and a shared pointer to an IEntityProperty instance. If the provided + * property pointer is null, an error is logged and the registration is aborted. + * + * @param type The component type identifier for which the property handler is being registered. + * @param property A shared pointer to the IEntityProperty instance that handles the properties of the specified + * component type. + */ + void registerProperty(const ecs::ComponentType type, std::shared_ptr property) + { + if (!property) { + LOG(NEXO_ERROR, "Attempted to register a null property for component type {}", type); + return; } - + m_entityProperties[type] = std::move(property); + } }; -}; +}; // namespace nexo::editor diff --git a/editor/src/DocumentWindows/InspectorWindow/Show.cpp b/editor/src/DocumentWindows/InspectorWindow/Show.cpp index 00d15d5af..33dc3ddfa 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Show.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Show.cpp @@ -12,27 +12,26 @@ // /////////////////////////////////////////////////////////////////////////////// +#include "IconsFontAwesome.h" +#include "ImNexo/Components.hpp" #include "InspectorWindow.hpp" #include "context/Selector.hpp" -#include "ImNexo/Components.hpp" -#include "IconsFontAwesome.h" namespace nexo::editor { void InspectorWindow::showSceneProperties(const scene::SceneId sceneId) { - auto &app = getApp(); - auto &selector = Selector::get(); + auto &app = getApp(); + auto &selector = Selector::get(); scene::SceneManager &manager = app.getSceneManager(); - scene::Scene &scene = manager.getScene(sceneId); - std::string uiHandle = selector.getUiHandle(scene.getUuid(), ""); + scene::Scene &scene = manager.getScene(sceneId); + std::string uiHandle = selector.getUiHandle(scene.getUuid(), ""); // Remove the icon prefix if (const size_t spacePos = uiHandle.find(' '); spacePos != std::string::npos) uiHandle = uiHandle.substr(spacePos + 1); - if (ImNexo::Header("##SceneNode", uiHandle)) - { + if (ImNexo::Header("##SceneNode", uiHandle)) { ImGui::Spacing(); ImGui::Columns(2, "sceneProps"); ImGui::SetColumnWidth(0, 80); @@ -58,11 +57,9 @@ namespace nexo::editor { void InspectorWindow::showEntityProperties(const ecs::Entity entity) { - const std::vector& componentsType = Application::getAllEntityComponentTypes(entity); - for (auto& type : componentsType) - { - if (m_entityProperties.contains(type)) - { + const std::vector &componentsType = Application::getAllEntityComponentTypes(entity); + for (auto &type : componentsType) { + if (m_entityProperties.contains(type)) { m_entityProperties[type]->show(entity); } } @@ -77,15 +74,14 @@ namespace nexo::editor { if (selector.getPrimarySelectionType() == SelectionType::SCENE) { // Scene selection stays the same - only show the selected scene showSceneProperties(selector.getSelectedScene()); - } - else if (selector.hasSelection()) { + } else if (selector.hasSelection()) { const ecs::Entity primaryEntity = selector.getPrimaryEntity(); - const auto& selectedEntities = selector.getSelectedEntities(); + const auto &selectedEntities = selector.getSelectedEntities(); if (selectedEntities.size() > 1) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.0f, 1.0f)); ImGui::TextWrapped("%zu entities selected. Displaying properties for the primary entity.", - selectedEntities.size()); + selectedEntities.size()); ImGui::PopStyleColor(); ImGui::Separator(); } @@ -95,5 +91,4 @@ namespace nexo::editor { ImGui::End(); } - -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp b/editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp index cadc04517..2d7e3e808 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp @@ -20,5 +20,4 @@ namespace nexo::editor { { // Nothing to clear for now } - -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/InspectorWindow/Update.cpp b/editor/src/DocumentWindows/InspectorWindow/Update.cpp index bea86f4f6..eb76ae926 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Update.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Update.cpp @@ -20,5 +20,4 @@ namespace nexo::editor { { // Nothing to update here } - -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/MaterialInspector/Init.cpp b/editor/src/DocumentWindows/MaterialInspector/Init.cpp index 0a8792880..b63f56710 100644 --- a/editor/src/DocumentWindows/MaterialInspector/Init.cpp +++ b/editor/src/DocumentWindows/MaterialInspector/Init.cpp @@ -17,8 +17,8 @@ namespace nexo::editor { void MaterialInspector::setup() - { - // No need to setup anything - } + { + // No need to set up anything + } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp b/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp index a17b74a16..e3542355d 100644 --- a/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp +++ b/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp @@ -17,41 +17,57 @@ namespace nexo::editor { - class MaterialInspector final : public ADocumentWindow { - public: - using ADocumentWindow::ADocumentWindow; - void setup() override; - - /** - * @brief Displays the Material Inspector window. - * - * This method retrieves the selected entity and the inspector window, then renders the Material Inspector UI using ImGui. - * It sets up appropriate window flags, performs a first-time docking setup when necessary, and delegates material - * rendering to renderMaterialInspector() if the Material Inspector is visible. - */ - void show() override; - - // No-op method in this class - void shutdown() override; - - // No-op method in this class - void update() override; - - private: - - /** - * @brief Renders the material inspector for the selected entity. - * - * When a valid entity is provided (i.e., `selectedEntity` is not -1) and differs from the current entity, - * this method marks the material as modified and regenerates a scene preview. If the material has been modified, - * it updates the preview by running a framebuffer render pass, displays the resulting image using ImGui, - * and processes potential material changes through the inspector widget. - * - * @throw BackendRendererApiFatalFailure Thrown if the framebuffer fails to initialize. - */ - void renderMaterialInspector(); - - bool m_materialModified = true; - }; - -} + class MaterialInspector final : public ADocumentWindow { + public: + using ADocumentWindow::ADocumentWindow; + + /** + * @brief Destructor for the MaterialInspector class. + * + * This destructor is defaulted, indicating that no special cleanup is required when an instance of + * MaterialInspector is destroyed. + */ + void setup() override; + + /** + * @brief Displays the Material Inspector window. + * + * This method retrieves the selected entity and the inspector window, then renders the Material Inspector UI + * using ImGui. It sets up appropriate window flags, performs a first-time docking setup when necessary, and + * delegates material rendering to renderMaterialInspector() if the Material Inspector is visible. + */ + void show() override; + + /** + * @brief Shuts down the Material Inspector window. + * + * This method is currently a no-op in the MaterialInspector class, as there are no specific shutdown actions + * required for this window at this time. + */ + void shutdown() override; + + /** + * @brief Updates the Material Inspector window. + * + * This method is currently a no-op in the MaterialInspector class, as there are no specific update actions + * required for this window at this time. + */ + void update() override; + + private: + /** + * @brief Renders the material inspector for the selected entity. + * + * When a valid entity is provided (i.e., `selectedEntity` is not -1) and differs from the current entity, + * this method marks the material as modified and regenerates a scene preview. If the material has been + * modified, it updates the preview by running a framebuffer render pass, displays the resulting image using + * ImGui, and processes potential material changes through the inspector widget. + * + * @throw BackendRendererApiFatalFailure Thrown if the framebuffer fails to initialize. + */ + void renderMaterialInspector(); + + bool m_materialModified = true; + }; + +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/MaterialInspector/Show.cpp b/editor/src/DocumentWindows/MaterialInspector/Show.cpp index 10e34ca54..a090c9215 100644 --- a/editor/src/DocumentWindows/MaterialInspector/Show.cpp +++ b/editor/src/DocumentWindows/MaterialInspector/Show.cpp @@ -13,64 +13,58 @@ /////////////////////////////////////////////////////////////////////////////// #include "DocumentWindows/EntityProperties/MaterialProperty.hpp" -#include "MaterialInspector.hpp" -#include "components/MaterialComponent.hpp" -#include "context/ThumbnailCache.hpp" #include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/Panels.hpp" +#include "MaterialInspector.hpp" +#include "components/MaterialComponent.hpp" #include "context/Selector.hpp" +#include "context/ThumbnailCache.hpp" namespace nexo::editor { void MaterialInspector::renderMaterialInspector() { const auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); - const auto dataOpt = inspectorWindow->getSubInspectorData(); - if (!dataOpt.has_value()) - return; + const auto dataOpt = inspectorWindow->getSubInspectorData(); + if (!dataOpt.has_value()) return; const auto& data = *dataOpt; if (!Application::m_coordinator->entityHasComponent(data.m_selectedEntity)) return; unsigned int textureID = 0; - if (m_materialModified) - { + if (m_materialModified) { textureID = ThumbnailCache::getInstance().updateMaterialThumbnail(data.material); } else { textureID = ThumbnailCache::getInstance().getMaterialThumbnail(data.material); } - if (!textureID) - return; + if (!textureID) return; // --- Material preview --- ImNexo::Image(static_cast(static_cast(textureID)), {64, 64}); ImGui::SameLine(); - const auto material = data.material.lock(); + const auto material = data.material.lock(); components::Material& materialData = *material->getData(); m_materialModified = ImNexo::MaterialInspector(materialData); } - void MaterialInspector::show() - { - const auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); - if (!inspectorWindow) - return; - if (inspectorWindow->getSubInspectorVisibility()) - { - ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse; - if (m_firstOpened) - window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; + void MaterialInspector::show() + { + const auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); + if (!inspectorWindow) return; + if (inspectorWindow->getSubInspectorVisibility()) { + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse; + if (m_firstOpened) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; - if (ImGui::Begin("Material Inspector" NEXO_WND_USTRID_MATERIAL_INSPECTOR, &inspectorWindow->getSubInspectorVisibility(), window_flags)) - { - beginRender(NEXO_WND_USTRID_MATERIAL_INSPECTOR); - renderMaterialInspector(); - } - ImGui::End(); - } - } -} + if (ImGui::Begin("Material Inspector" NEXO_WND_USTRID_MATERIAL_INSPECTOR, + &inspectorWindow->getSubInspectorVisibility(), window_flags)) { + beginRender(NEXO_WND_USTRID_MATERIAL_INSPECTOR); + renderMaterialInspector(); + } + ImGui::End(); + } + } +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp b/editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp index f5f26157a..d83ef9bab 100644 --- a/editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp +++ b/editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp @@ -17,8 +17,8 @@ namespace nexo::editor { void MaterialInspector::shutdown() - { - // No need to delete anything since the destructor of the framebuffer will handle it - } + { + // No need to delete anything since the destructor of the framebuffer will handle it + } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/MaterialInspector/Update.cpp b/editor/src/DocumentWindows/MaterialInspector/Update.cpp index ad8af8908..a1f31f177 100644 --- a/editor/src/DocumentWindows/MaterialInspector/Update.cpp +++ b/editor/src/DocumentWindows/MaterialInspector/Update.cpp @@ -17,8 +17,8 @@ namespace nexo::editor { void MaterialInspector::update() - { - // No need to update anything - } + { + // No need to update anything + } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/PopupManager.cpp b/editor/src/DocumentWindows/PopupManager.cpp index 66d41cd46..8e2821c86 100644 --- a/editor/src/DocumentWindows/PopupManager.cpp +++ b/editor/src/DocumentWindows/PopupManager.cpp @@ -23,21 +23,14 @@ namespace nexo::editor { void PopupManager::openPopup(const std::string &popupName, const ImVec2 &popupSize) { - const PopupProps props{ - .open = true, - .callback = nullptr, - .size = popupSize - }; + const PopupProps props{.open = true, .callback = nullptr, .size = popupSize}; m_popups[popupName] = props; } - void PopupManager::openPopupWithCallback(const std::string &popupName, PopupCallback callback, const ImVec2 &popupSize) + void PopupManager::openPopupWithCallback(const std::string &popupName, PopupCallback callback, + const ImVec2 &popupSize) { - const PopupProps props{ - .open = true, - .callback = std::move(callback), - .size = popupSize - }; + const PopupProps props{.open = true, .callback = std::move(callback), .size = popupSize}; m_popups[popupName] = props; } @@ -53,16 +46,13 @@ namespace nexo::editor { bool PopupManager::showPopup(const std::string &popupName) { - if (!m_popups.contains(popupName)) - return false; + if (!m_popups.contains(popupName)) return false; PopupProps &props = m_popups.at(popupName); - if (props.open) - { + if (props.open) { ImGui::OpenPopup(popupName.c_str()); props.open = false; } - if (props.size.x != 0 && props.size.y != 0) - { + if (props.size.x != 0 && props.size.y != 0) { ImGui::SetNextWindowSize(props.size); } return ImGui::BeginPopup(popupName.c_str()); @@ -70,35 +60,30 @@ namespace nexo::editor { bool PopupManager::showPopupModal(const std::string &popupModalName) { - if (!m_popups.contains(popupModalName)) - return false; + if (!m_popups.contains(popupModalName)) return false; PopupProps &props = m_popups.at(popupModalName); - if (m_popups.at(popupModalName).open) - { + if (m_popups.at(popupModalName).open) { ImGui::OpenPopup(popupModalName.c_str()); props.open = false; } - if (props.size.x != 0 && props.size.y != 0) - { + if (props.size.x != 0 && props.size.y != 0) { ImGui::SetNextWindowSize(props.size); } - ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize - | ImGuiWindowFlags_NoBackground - | ImGuiWindowFlags_NoTitleBar; - if (!ImGui::BeginPopupModal(popupModalName.c_str(), nullptr, flags)) - return false; - const ImVec2 pMin = ImGui::GetWindowPos(); - const ImVec2 size = ImGui::GetWindowSize(); - const auto pMax = ImVec2(pMin.x + size.x, pMin.y + size.y); - ImDrawList* drawList = ImGui::GetWindowDrawList(); + const ImGuiWindowFlags flags = + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar; + if (!ImGui::BeginPopupModal(popupModalName.c_str(), nullptr, flags)) return false; + const ImVec2 pMin = ImGui::GetWindowPos(); + const ImVec2 size = ImGui::GetWindowSize(); + const auto pMax = ImVec2(pMin.x + size.x, pMin.y + size.y); + ImDrawList *drawList = ImGui::GetWindowDrawList(); const std::vector stops = { - { 0.06f, IM_COL32(58 / 3, 124 / 3, 161 / 3, 255) }, - { 0.26f, IM_COL32(88 / 3, 87 / 3, 154 / 3, 255) }, - { 0.50f, IM_COL32(88 / 3, 87 / 3, 154 / 3, 255) }, - { 0.73f, IM_COL32(58 / 3, 124 / 3, 161 / 3, 255) }, + {0.06f, IM_COL32(58 / 3, 124 / 3, 161 / 3, 255)}, + {0.26f, IM_COL32(88 / 3, 87 / 3, 154 / 3, 255)}, + {0.50f, IM_COL32(88 / 3, 87 / 3, 154 / 3, 255)}, + {0.73f, IM_COL32(58 / 3, 124 / 3, 161 / 3, 255)}, }; constexpr float angle = 148.0f; @@ -109,10 +94,9 @@ namespace nexo::editor { void PopupManager::runPopupCallback(const std::string &popupName) const { - if (m_popups.contains(popupName) && m_popups.at(popupName).callback != nullptr) - { + if (m_popups.contains(popupName) && m_popups.at(popupName).callback != nullptr) { m_popups.at(popupName).callback(); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/PopupManager.hpp b/editor/src/DocumentWindows/PopupManager.hpp index 071753334..11fd3dec1 100644 --- a/editor/src/DocumentWindows/PopupManager.hpp +++ b/editor/src/DocumentWindows/PopupManager.hpp @@ -13,111 +13,120 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include -#include #include #include +#include +#include namespace nexo::editor { - /** + /** * @brief Manages the state of popups within the UI. * * The PopupManager allows you to open, display, and close popups by name. * It internally stores popup states and uses ImGui to render popups. */ class PopupManager { - public: - using PopupCallback = std::function; + public: + using PopupCallback = std::function; - /** - * @brief Properties of a popup. - * - * Contains state information for a popup, including whether it should - * be opened, its associated callback function, and its size. - */ - struct PopupProps { - bool open = false; ///< Whether the popup is marked to open - PopupCallback callback = nullptr; ///< Optional callback function - ImVec2 size = ImVec2(0, 0); ///< Size of the popup (0,0 means auto-size) - }; + /** + * @brief Properties of a popup. + * + * Contains state information for a popup, including whether it should + * be opened, its associated callback function, and its size. + */ + struct PopupProps { + bool open = false; ///< Whether the popup is marked to open + PopupCallback callback = nullptr; ///< Optional callback function + ImVec2 size = ImVec2(0, 0); ///< Size of the popup (0,0 means auto-size) + }; - /** - * @brief Opens a popup by name. - * - * Marks the popup as active so that it will be opened in the next frame. - * - * @param popupName The unique name of the popup. - * @param popupSize Optional size for the popup window (0,0 for auto-size). - */ - void openPopup(const std::string &popupName, const ImVec2 &popupSize = ImVec2(0, 0)); + /** + * @brief Opens a popup by name. + * + * Marks the popup as active so that it will be opened in the next frame. + * + * @param popupName The unique name of the popup. + * @param popupSize Optional size for the popup window (0,0 for auto-size). + */ + void openPopup(const std::string &popupName, const ImVec2 &popupSize = ImVec2(0, 0)); - /** - * @brief Opens a popup with an associated callback function. - * - * Marks the popup as active and associates a callback function with it. - * The callback can later be executed via runPopupCallback(). - * - * @param popupName The unique name of the popup. - * @param callback Function to be called when the popup is processed. - * @param popupSize Optional size for the popup window (0,0 for auto-size). - */ - void openPopupWithCallback(const std::string &popupName, PopupCallback callback, const ImVec2 &popupSize = ImVec2(0, 0)); + /** + * @brief Opens a popup with an associated callback function. + * + * Marks the popup as active and associates a callback function with it. + * The callback can later be executed via runPopupCallback(). + * + * @param popupName The unique name of the popup. + * @param callback Function to be called when the popup is processed. + * @param popupSize Optional size for the popup window (0,0 for auto-size). + */ + void openPopupWithCallback(const std::string &popupName, PopupCallback callback, + const ImVec2 &popupSize = ImVec2(0, 0)); - /** - * @brief Displays a modal popup. - * - * If the popup was marked as active, opens it using ImGui's modal popup functions. - * This function returns true if the modal popup is open. - * - * @param popupModalName The unique name of the modal popup. - * @return true if the modal popup is currently open; false otherwise. - */ - bool showPopupModal(const std::string &popupModalName); + /** + * @brief Displays a modal popup. + * + * If the popup was marked as active, opens it using ImGui's modal popup functions. + * This function returns true if the modal popup is open. + * + * @param popupModalName The unique name of the modal popup. + * @return true if the modal popup is currently open; false otherwise. + */ + bool showPopupModal(const std::string &popupModalName); - /** - * @brief Displays a non-modal popup. - * - * If the popup was marked as active, opens it using ImGui's non-modal popup functions. - * This function returns true if the popup is open. - * - * @param popupName The unique name of the popup. - * @return true if the popup is currently open; false otherwise. - */ - bool showPopup(const std::string &popupName); + /** + * @brief Displays a non-modal popup. + * + * If the popup was marked as active, opens it using ImGui's non-modal popup functions. + * This function returns true if the popup is open. + * + * @param popupName The unique name of the popup. + * @return true if the popup is currently open; false otherwise. + */ + bool showPopup(const std::string &popupName); - /** - * @brief Closes the current popup. - * - * Ends the current ImGui popup. - */ - static void endPopup() ; + /** + * @brief Closes the current popup. + * + * Ends the current ImGui popup. + */ + static void endPopup(); - /** - * @brief Closes the current popup in its context. - * - * Requests ImGui to close the current popup. - */ - static void closePopup() ; + /** + * @brief Closes the current popup in its context. + * + * Requests ImGui to close the current popup. + */ + static void closePopup(); - /** - * @brief Executes the callback associated with a popup. - * - * If a callback function was registered with the specified popup, - * this method will execute it. - * - * @param popupName The name of the popup whose callback should be executed. - */ - void runPopupCallback(const std::string &popupName) const; + /** + * @brief Executes the callback associated with a popup. + * + * If a callback function was registered with the specified popup, + * this method will execute it. + * + * @param popupName The name of the popup whose callback should be executed. + */ + void runPopupCallback(const std::string &popupName) const; - private: - struct TransparentHasher { - using is_transparent = void; // Required for heterogeneous lookup - std::size_t operator()(std::string_view key) const noexcept { - return std::hash{}(key); - } - }; + private: + /** + * @brief Custom hasher for string_view to enable heterogeneous lookup in unordered_map. + * + * This struct provides a hash function for std::string_view, allowing + * the unordered_map to perform lookups using string_view keys without + * needing to create temporary std::string objects. + */ + struct TransparentHasher { + using is_transparent = void; // Required for heterogeneous lookup + std::size_t operator()(std::string_view key) const noexcept + { + return std::hash{}(key); + } + }; - std::unordered_map> m_popups; + std::unordered_map> m_popups; }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/Primitive.hpp b/editor/src/DocumentWindows/Primitive.hpp deleted file mode 100644 index 0f44b8ca8..000000000 --- a/editor/src/DocumentWindows/Primitive.hpp +++ /dev/null @@ -1,59 +0,0 @@ -//// Primitive.hpp //////////////////////////////////////////////////////////// -// -// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz -// zzzzzzz zzz zzzz zzzz zzzz zzzz -// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz -// zzz zzz zzz z zzzz zzzz zzzz zzzz -// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz -// -// Author: Marie GIACOMEL -// Date: 17/11/2024 -// Description: Primitive functions -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef PRIMITIVE_HPP -#define PRIMITIVE_HPP - -#include "EntityFactory2D.hpp" -#include "EntityFactory3D.hpp" - -namespace nexo::editor -{ - using Primitive2DFunction = ecs::Entity(*)(glm::vec3 pos, glm::vec2 size, float rotation, glm::vec4 color); - inline const char* primitives2DNames[] = { - " Cube", " Plan", " Sphere", - " Cylinder", " Cone", " Polygon", " Torus", " Knot", - " Hemisphere" - }; - inline Primitive2DFunction addPrimitive2D[] = { - &EntityFactory2D::createQuad, - &EntityFactory2D::createQuad, - &EntityFactory2D::createQuad, - &EntityFactory2D::createQuad, - &EntityFactory2D::createQuad, - &EntityFactory2D::createQuad, - &EntityFactory2D::createQuad, - &EntityFactory2D::createQuad, - &EntityFactory2D::createQuad - }; - - using Primitive3DFunction = ecs::Entity(*)(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, glm::vec4 color); - inline const char* primitives3DNames[] = { - " Cube", " Plan", " Sphere", " Tetrahedron", - " Pyramid", " Cylinder", " Cone", " Polygon", " Torus" - }; - inline Primitive3DFunction addPrimitive3D[] = { - &EntityFactory3D::createCube, - &EntityFactory3D::createCube, - &EntityFactory3D::createCube, - &EntityFactory3D::createTetrahedron, - &EntityFactory3D::createPyramid, - &EntityFactory3D::createCylinder, - &EntityFactory3D::createCube, - &EntityFactory3D::createCube, - &EntityFactory3D::createCube - }; -} - -#endif //PRIMITIVE_HPP diff --git a/editor/src/DocumentWindows/PrimitiveWindow/Init.cpp b/editor/src/DocumentWindows/PrimitiveWindow/Init.cpp index 8f4b58b41..c62ed2304 100644 --- a/editor/src/DocumentWindows/PrimitiveWindow/Init.cpp +++ b/editor/src/DocumentWindows/PrimitiveWindow/Init.cpp @@ -14,10 +14,9 @@ #include "PrimitiveWindow.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void PrimitiveWindow::setup() { - // No need to setup anything + // No need to set up anything } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/PrimitiveWindow/PrimitiveWindow.hpp b/editor/src/DocumentWindows/PrimitiveWindow/PrimitiveWindow.hpp index 4159bc7f4..3ec1ff23d 100644 --- a/editor/src/DocumentWindows/PrimitiveWindow/PrimitiveWindow.hpp +++ b/editor/src/DocumentWindows/PrimitiveWindow/PrimitiveWindow.hpp @@ -6,7 +6,7 @@ // zzz zzzzzzz zzzz zzzz zzzz zzzz zzzz // zzz zzzzzz zzzzzzzzzzzzz zzzz zzzz zzzzz zzzzz // -// Author: Marie Giacomel +// Author: Marie GIACOMEL // Date: 06/06/2025 // Description: Header file for the primitive customization window // @@ -16,11 +16,9 @@ #include "ADocumentWindow.hpp" #include "EntityFactory3D.hpp" -namespace nexo::editor -{ - class PrimitiveWindow final : public ADocumentWindow - { - public: +namespace nexo::editor { + class PrimitiveWindow final : public ADocumentWindow { + public: using ADocumentWindow::ADocumentWindow; void setup() override; @@ -42,7 +40,7 @@ namespace nexo::editor m_selectedPrimitive = primitive; } - private: + private: /** * @brief Renders the primitive customization window for the selected primitive. * @@ -54,4 +52,4 @@ namespace nexo::editor Primitives m_selectedPrimitive = CUBE; // Default primitive type }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/PrimitiveWindow/Show.cpp b/editor/src/DocumentWindows/PrimitiveWindow/Show.cpp index 7e8a4b2c6..e70957d00 100644 --- a/editor/src/DocumentWindows/PrimitiveWindow/Show.cpp +++ b/editor/src/DocumentWindows/PrimitiveWindow/Show.cpp @@ -12,15 +12,13 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "PrimitiveWindow.hpp" #include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" +#include "PrimitiveWindow.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void PrimitiveWindow::renderPrimitiveWindow() const { - switch (m_selectedPrimitive) - { + switch (m_selectedPrimitive) { case CUBE: ImGui::Text("Cube customization options"); break; @@ -44,21 +42,17 @@ namespace nexo::editor void PrimitiveWindow::show() { const auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); - if (!inspectorWindow) - return; - if (inspectorWindow->getSubInspectorVisibility()) - { + if (!inspectorWindow) return; + if (inspectorWindow->getSubInspectorVisibility()) { ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoCollapse; - if (m_firstOpened) - window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; + if (m_firstOpened) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; if (ImGui::Begin("Primitive Window" NEXO_WND_USTRID_PRIMITIVE_WINDOW, - &inspectorWindow->getSubInspectorVisibility(), window_flags)) - { + &inspectorWindow->getSubInspectorVisibility(), window_flags)) { beginRender(NEXO_WND_USTRID_PRIMITIVE_WINDOW); renderPrimitiveWindow(); } ImGui::End(); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/PrimitiveWindow/Shutdown.cpp b/editor/src/DocumentWindows/PrimitiveWindow/Shutdown.cpp index 9cc752dbf..6ee67424e 100644 --- a/editor/src/DocumentWindows/PrimitiveWindow/Shutdown.cpp +++ b/editor/src/DocumentWindows/PrimitiveWindow/Shutdown.cpp @@ -14,10 +14,9 @@ #include "PrimitiveWindow.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void PrimitiveWindow::shutdown() { // No need to delete anything since the destructor of the framebuffer will handle it } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/PrimitiveWindow/Update.cpp b/editor/src/DocumentWindows/PrimitiveWindow/Update.cpp index 7cee238a4..2412cae79 100644 --- a/editor/src/DocumentWindows/PrimitiveWindow/Update.cpp +++ b/editor/src/DocumentWindows/PrimitiveWindow/Update.cpp @@ -14,10 +14,9 @@ #include "PrimitiveWindow.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void PrimitiveWindow::update() { // No need to update anything } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp index 089cc4a33..ee80ae8b0 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/DragDrop.cpp @@ -12,22 +12,21 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "SceneTreeWindow.hpp" +#include +#include +#include #include "DocumentWindows/AssetManager/AssetManagerWindow.hpp" -#include "components/Uuid.hpp" -#include "context/ActionManager.hpp" -#include "context/Selector.hpp" -#include "context/actions/EntityActions.hpp" -#include "components/Parent.hpp" -#include "components/Transform.hpp" -#include "components/Render3D.hpp" #include "EntityFactory3D.hpp" +#include "SceneTreeWindow.hpp" #include "assets/AssetCatalog.hpp" #include "assets/Assets/Model/Model.hpp" #include "assets/Assets/Texture/Texture.hpp" -#include -#include -#include +#include "components/Parent.hpp" +#include "components/Render3D.hpp" +#include "components/Transform.hpp" +#include "context/ActionManager.hpp" +#include "context/Selector.hpp" +#include "context/actions/EntityActions.hpp" #define GLM_ENABLE_EXPERIMENTAL #include @@ -35,18 +34,11 @@ namespace nexo::editor { void SceneTreeWindow::handleDragSource(const SceneObject& object) { - if (object.type == SelectionType::SCENE || object.type == SelectionType::NONE) - return; - - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - const SceneTreeDragDropPayload payload{ - object.data.entity, - object.data.sceneProperties.sceneId, - object.type, - object.uuid, - object.uiName - }; + if (object.type == SelectionType::SCENE || object.type == SelectionType::NONE) return; + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + const SceneTreeDragDropPayload payload{object.data.entity, object.data.sceneProperties.sceneId, object.type, + object.uuid, object.uiName}; ImGui::SetDragDropPayload("SCENE_TREE_NODE", &payload, sizeof(payload)); ImGui::EndDragDropSource(); @@ -55,21 +47,17 @@ namespace nexo::editor { void SceneTreeWindow::handleDropTarget(const SceneObject& object) { - if (ImGui::BeginDragDropTarget()) - { + if (ImGui::BeginDragDropTarget()) { // Handle drops from scene tree nodes - if (const ImGuiPayload* imguiPayload = ImGui::AcceptDragDropPayload("SCENE_TREE_NODE")) - { + if (const ImGuiPayload* imguiPayload = ImGui::AcceptDragDropPayload("SCENE_TREE_NODE")) { IM_ASSERT(imguiPayload->DataSize == sizeof(SceneTreeDragDropPayload)); const auto& payload = *static_cast(imguiPayload->Data); - if (canAcceptDrop(object, payload)) - handleDropFromSceneTree(object, payload); + if (canAcceptDrop(object, payload)) handleDropFromSceneTree(object, payload); } // Handle drops from asset manager - if (const ImGuiPayload* assetPayload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) - { + if (const ImGuiPayload* assetPayload = ImGui::AcceptDragDropPayload("ASSET_DRAG")) { IM_ASSERT(assetPayload->DataSize == sizeof(AssetDragDropPayload)); const auto& payload = *static_cast(assetPayload->Data); @@ -83,39 +71,34 @@ namespace nexo::editor { bool SceneTreeWindow::canAcceptDrop(const SceneObject& dropTarget, const SceneTreeDragDropPayload& payload) { // Can't drop on itself - if (dropTarget.type != SelectionType::SCENE && dropTarget.data.entity == payload.entity) - return false; + if (dropTarget.type != SelectionType::SCENE && dropTarget.data.entity == payload.entity) return false; // Can't drop a parent onto its child (prevent circular references) - if (dropTarget.type != SelectionType::SCENE) - { + if (dropTarget.type != SelectionType::SCENE) { ecs::Entity currentEntity = dropTarget.data.entity; auto parentComp = Application::m_coordinator->tryGetComponent(currentEntity); - while (parentComp.has_value()) - { - if (parentComp->get().parent == payload.entity) - return false; + while (parentComp.has_value()) { + if (parentComp->get().parent == payload.entity) return false; currentEntity = parentComp->get().parent; - parentComp = Application::m_coordinator->tryGetComponent(currentEntity); + parentComp = Application::m_coordinator->tryGetComponent(currentEntity); } } return true; } - void SceneTreeWindow::handleDropFromSceneTree(const SceneObject& dropTarget, const SceneTreeDragDropPayload& payload) + void SceneTreeWindow::handleDropFromSceneTree(const SceneObject& dropTarget, + const SceneTreeDragDropPayload& payload) { - auto& app = Application::getInstance(); + auto& app = Application::getInstance(); auto& sceneManager = app.getSceneManager(); - auto& coordinator = *Application::m_coordinator; + auto& coordinator = *Application::m_coordinator; auto& sourceScene = sceneManager.getScene(payload.sourceSceneId); - if (dropTarget.type == SelectionType::SCENE) - { + if (dropTarget.type == SelectionType::SCENE) { // Dropping onto a scene - move entity to that scene - if (payload.sourceSceneId != dropTarget.data.sceneProperties.sceneId) - { + if (payload.sourceSceneId != dropTarget.data.sceneProperties.sceneId) { sourceScene.removeEntity(payload.entity); auto& targetScene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); @@ -123,11 +106,10 @@ namespace nexo::editor { // Remove parent relationship if moving to different scene auto parentComp = coordinator.tryGetComponent(payload.entity); - if (parentComp.has_value()) - { - auto parentTransform = coordinator.tryGetComponent(parentComp->get().parent); - if (parentTransform.has_value()) - parentTransform->get().removeChild(payload.entity); + if (parentComp.has_value()) { + auto parentTransform = + coordinator.tryGetComponent(parentComp->get().parent); + if (parentTransform.has_value()) parentTransform->get().removeChild(payload.entity); coordinator.removeComponent(payload.entity); } @@ -136,54 +118,45 @@ namespace nexo::editor { // TODO: Create a specific action for moving entities between scenes // For now, we just perform the operation without undo support for scene moves } - } - else if (dropTarget.type == SelectionType::ENTITY) - { + } else if (dropTarget.type == SelectionType::ENTITY) { // Dropping onto an entity - create parent-child relationship ecs::Entity parentEntity = dropTarget.data.entity; ecs::Entity childEntity = payload.entity; auto childTransformOpt = coordinator.tryGetComponent(childEntity); - if (!childTransformOpt.has_value()) - return; - auto &childTransform = childTransformOpt->get(); + if (!childTransformOpt.has_value()) return; + auto& childTransform = childTransformOpt->get(); glm::mat4 childWorldMat = childTransform.worldMatrix; auto parentTransformOpt = coordinator.tryGetComponent(parentEntity); - if (!parentTransformOpt.has_value()) - return; - auto& parentTransform = parentTransformOpt->get(); + if (!parentTransformOpt.has_value()) return; + auto& parentTransform = parentTransformOpt->get(); glm::mat4 parentWorldMat = parentTransform.worldMatrix; // Compute the new localMatrix so that parentWorldMat * local = old world - glm::mat4 invParent = glm::inverse(parentWorldMat); + glm::mat4 invParent = glm::inverse(parentWorldMat); glm::mat4 newLocalMat = invParent * childWorldMat; - glm::vec3 skew, scale, translation; + glm::vec3 skew; + glm::vec3 scale; + glm::vec3 translation; glm::quat rotation; glm::vec4 perspective; - glm::decompose( - newLocalMat, - scale, - rotation, - translation, - skew, - perspective - ); + glm::decompose(newLocalMat, scale, rotation, translation, skew, perspective); childTransform.pos = translation; childTransform.quat = rotation; childTransform.size = scale; ecs::Entity oldParent = ecs::INVALID_ENTITY; - auto oldParentComp = coordinator.tryGetComponent(childEntity); - if (oldParentComp.has_value()) - { + auto oldParentComp = coordinator.tryGetComponent(childEntity); + if (oldParentComp.has_value()) { oldParent = oldParentComp->get().parent; if (auto oldPT = coordinator.tryGetComponent(oldParent)) { oldPT->get().removeChild(childEntity); - if (oldPT->get().children.empty() && coordinator.entityHasComponent(oldParent)) + if (oldPT->get().children.empty() && + coordinator.entityHasComponent(oldParent)) coordinator.removeComponent(oldParent); } } @@ -199,31 +172,24 @@ namespace nexo::editor { pt.addChild(childEntity); if (!coordinator.entityHasComponent(parentEntity) && - !coordinator.entityHasComponent(parentEntity)) - { + !coordinator.entityHasComponent(parentEntity)) { std::string name = dropTarget.uiName; - const auto it = ObjectTypeToIcon.find(dropTarget.type); - if (it != ObjectTypeToIcon.end()) - { + const auto it = ObjectTypeToIcon.find(dropTarget.type); + if (it != ObjectTypeToIcon.end()) { const std::string& icon = it->second; - if (name.rfind(icon, 0) == 0) - name.erase(0, icon.size()); + if (name.rfind(icon, 0) == 0) name.erase(0, icon.size()); } - coordinator.addComponent( - parentEntity, - components::RootComponent{ name, nullptr, 1 } - ); + coordinator.addComponent(parentEntity, components::RootComponent{name, nullptr, 1}); } - if (payload.sourceSceneId != dropTarget.data.sceneProperties.sceneId) - { + if (payload.sourceSceneId != dropTarget.data.sceneProperties.sceneId) { sourceScene.removeEntity(childEntity); auto& targetScene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); targetScene.addEntity(childEntity); auto& sceneTag = coordinator.getComponent(childEntity); - sceneTag.id = dropTarget.data.sceneProperties.sceneId; + sceneTag.id = dropTarget.data.sceneProperties.sceneId; } auto action = std::make_unique(childEntity, oldParent, parentEntity); @@ -233,47 +199,32 @@ namespace nexo::editor { void SceneTreeWindow::handleDropFromAssetManager(const SceneObject& dropTarget, const AssetDragDropPayload& payload) { - if (dropTarget.type == SelectionType::SCENE) - { - auto& app = Application::getInstance(); + if (dropTarget.type == SelectionType::SCENE) { + auto& app = Application::getInstance(); auto& sceneManager = app.getSceneManager(); - if (payload.type == assets::AssetType::MODEL) - { + if (payload.type == assets::AssetType::MODEL) { const auto modelRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (!modelRef) - return; - if (const auto model = modelRef.as(); model) - { - ecs::Entity newEntity = EntityFactory3D::createModel( - model, - {0.0f, 0.0f, 0.0f}, - {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f} - ); + if (!modelRef) return; + if (const auto model = modelRef.as(); model) { + ecs::Entity newEntity = + EntityFactory3D::createModel(model, {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}); auto& scene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); scene.addEntity(newEntity); auto action = std::make_unique(newEntity); ActionManager::get().recordAction(std::move(action)); } - } - else if (payload.type == assets::AssetType::TEXTURE) - { + } else if (payload.type == assets::AssetType::TEXTURE) { const auto textureRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (!textureRef) - return; - if (const auto texture = textureRef.as(); texture) - { + if (!textureRef) return; + if (const auto texture = textureRef.as(); texture) { components::Material material; material.albedoTexture = texture; - material.albedoColor = glm::vec4(1.0f); + material.albedoColor = glm::vec4(1.0f); - ecs::Entity newEntity = EntityFactory3D::createBillboard( - {0.0f, 0.0f, 0.0f}, - {1.0f, 1.0f, 1.0f}, - material - ); + ecs::Entity newEntity = + EntityFactory3D::createBillboard({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, material); auto& scene = sceneManager.getScene(dropTarget.data.sceneProperties.sceneId); scene.addEntity(newEntity); @@ -282,32 +233,27 @@ namespace nexo::editor { ActionManager::get().recordAction(std::move(action)); } } - } - else if (dropTarget.type == SelectionType::ENTITY) - { - const auto matCompOpt = Application::m_coordinator->tryGetComponent(dropTarget.data.entity); - if (!matCompOpt) - { ImGui::EndDragDropTarget(); return; } + } else if (dropTarget.type == SelectionType::ENTITY) { + const auto matCompOpt = + Application::m_coordinator->tryGetComponent(dropTarget.data.entity); + if (!matCompOpt) { + ImGui::EndDragDropTarget(); + return; + } auto& matComp = matCompOpt->get(); - if (payload.type == assets::AssetType::TEXTURE) - { + if (payload.type == assets::AssetType::TEXTURE) { const auto texRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (const auto tex = texRef.as(); tex) - { + if (const auto tex = texRef.as(); tex) { const auto mat = matComp.material.lock(); - if (!mat) - return; + if (!mat) return; mat->getData()->albedoTexture = tex; } - } - else if (payload.type == assets::AssetType::MATERIAL) - { + } else if (payload.type == assets::AssetType::MATERIAL) { auto const matRef = assets::AssetCatalog::getInstance().getAsset(payload.id); - if (const auto m = matRef.as(); m) - { - auto oldMat = matComp.material; + if (const auto m = matRef.as(); m) { + auto oldMat = matComp.material; matComp.material = m; } } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp index 218f6ffcd..a58cc953c 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp @@ -20,17 +20,13 @@ namespace nexo::editor { { auto &cameraComponent = Application::m_coordinator->getComponent(obj.data.entity); - if (cameraComponent.m_renderTarget) - { + if (cameraComponent.m_renderTarget) { ImGui::BeginTooltip(); - constexpr float previewSize = 200.0f; - cameraComponent.render = true; + constexpr float previewSize = 200.0f; + cameraComponent.render = true; const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); - ImNexo::Image( - static_cast(static_cast(textureId)), - ImVec2(previewSize, previewSize) - ); + ImNexo::Image(static_cast(static_cast(textureId)), ImVec2(previewSize, previewSize)); ImGui::EndTooltip(); } @@ -45,9 +41,10 @@ namespace nexo::editor { cameraHoveredLastFrame = true; } else if (cameraHoveredLastFrame) { cameraHoveredLastFrame = false; - auto &cameraComponent = Application::m_coordinator->getComponent(obj.data.entity); + auto &cameraComponent = + Application::m_coordinator->getComponent(obj.data.entity); cameraComponent.render = false; } } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp index c61efed26..608da9699 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp @@ -21,4 +21,4 @@ namespace nexo::editor { setupShortcuts(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp b/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp index 6af3d8e86..73b93e7f2 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp @@ -18,68 +18,75 @@ namespace nexo::editor { - // Node creation methods - SceneObject SceneTreeWindow::newSceneNode(const std::string &sceneName, const scene::SceneId sceneId, const WindowId uiId) + SceneObject SceneTreeWindow::newSceneNode(const std::string &sceneName, const scene::SceneId sceneId, + const WindowId uiId) { SceneObject sceneNode; - const std::string uiName = ObjectTypeToIcon.at(SelectionType::SCENE) + sceneName; + const std::string uiName = ObjectTypeToIcon.at(SelectionType::SCENE) + sceneName; sceneNode.data.sceneProperties = SceneProperties{sceneId, uiId}; - sceneNode.data.entity = sceneId; - sceneNode.type = SelectionType::SCENE; - auto &app = Application::getInstance(); - auto &selector = Selector::get(); - sceneNode.uuid = app.getSceneManager().getScene(sceneId).getUuid(); - sceneNode.uiName = selector.getUiHandle(sceneNode.uuid, uiName); + sceneNode.data.entity = sceneId; + sceneNode.type = SelectionType::SCENE; + auto &app = Application::getInstance(); + auto &selector = Selector::get(); + sceneNode.uuid = app.getSceneManager().getScene(sceneId).getUuid(); + sceneNode.uiName = selector.getUiHandle(sceneNode.uuid, uiName); return sceneNode; } - void SceneTreeWindow::newLightNode(SceneObject &lightNode, const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity, const std::string &uiName) + void SceneTreeWindow::newLightNode(SceneObject &lightNode, const scene::SceneId sceneId, const WindowId uiId, + const ecs::Entity lightEntity, const std::string &uiName) { const SceneProperties sceneProperties{sceneId, uiId}; lightNode.data.sceneProperties = sceneProperties; - lightNode.data.entity = lightEntity; - auto &selector = Selector::get(); + lightNode.data.entity = lightEntity; + auto &selector = Selector::get(); const auto entityUuid = Application::m_coordinator->tryGetComponent(lightEntity); - if (entityUuid) - { - lightNode.uuid = entityUuid->get().uuid; + if (entityUuid) { + lightNode.uuid = entityUuid->get().uuid; lightNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); } else lightNode.uiName = uiName; } - SceneObject SceneTreeWindow::newAmbientLightNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity) + SceneObject SceneTreeWindow::newAmbientLightNode(const scene::SceneId sceneId, const WindowId uiId, + const ecs::Entity lightEntity) { SceneObject lightNode; - lightNode.type = SelectionType::AMBIENT_LIGHT; + lightNode.type = SelectionType::AMBIENT_LIGHT; const std::string uiName = std::format("{}Ambient light ", ObjectTypeToIcon.at(lightNode.type)); newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); return lightNode; } - SceneObject SceneTreeWindow::newDirectionalLightNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity) + SceneObject SceneTreeWindow::newDirectionalLightNode(const scene::SceneId sceneId, const WindowId uiId, + const ecs::Entity lightEntity) { SceneObject lightNode; lightNode.type = SelectionType::DIR_LIGHT; - const std::string uiName = std::format("{}Directional light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbDirLights); + const std::string uiName = + std::format("{}Directional light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbDirLights); newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); return lightNode; } - SceneObject SceneTreeWindow::newSpotLightNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity) + SceneObject SceneTreeWindow::newSpotLightNode(const scene::SceneId sceneId, const WindowId uiId, + const ecs::Entity lightEntity) { SceneObject lightNode; lightNode.type = SelectionType::SPOT_LIGHT; - const std::string uiName = std::format("{}Spot light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbSpotLights); + const std::string uiName = + std::format("{}Spot light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbSpotLights); newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); return lightNode; } - SceneObject SceneTreeWindow::newPointLightNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity) + SceneObject SceneTreeWindow::newPointLightNode(const scene::SceneId sceneId, const WindowId uiId, + const ecs::Entity lightEntity) { SceneObject lightNode; lightNode.type = SelectionType::POINT_LIGHT; - const std::string uiName = std::format("{}Point light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbPointLights); + const std::string uiName = + std::format("{}Point light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbPointLights); newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); return lightNode; } @@ -89,15 +96,15 @@ namespace nexo::editor { { SceneObject cameraNode; const std::string uiName = ObjectTypeToIcon.at(SelectionType::CAMERA) + std::string("Camera"); - cameraNode.type = SelectionType::CAMERA; + cameraNode.type = SelectionType::CAMERA; const SceneProperties sceneProperties{sceneId, uiId}; cameraNode.data.sceneProperties = sceneProperties; - cameraNode.data.entity = cameraEntity; - auto &selector = Selector::get(); - const auto entityUuid = nexo::Application::m_coordinator->tryGetComponent(cameraEntity); - if (entityUuid) - { - cameraNode.uuid = entityUuid->get().uuid; + cameraNode.data.entity = cameraEntity; + auto &selector = Selector::get(); + const auto entityUuid = + nexo::Application::m_coordinator->tryGetComponent(cameraEntity); + if (entityUuid) { + cameraNode.uuid = entityUuid->get().uuid; cameraNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); } else cameraNode.uiName = uiName; @@ -112,20 +119,18 @@ namespace nexo::editor { std::string uiName; if (Application::m_coordinator->entityHasComponent(entity)) { const auto &nameComponent = Application::m_coordinator->getComponent(entity); - uiName = nameComponent.name; + uiName = nameComponent.name; } else uiName = std::format("{}{}", ObjectTypeToIcon.at(SelectionType::ENTITY), entity); - entityNode.type = SelectionType::ENTITY; + entityNode.type = SelectionType::ENTITY; entityNode.data.sceneProperties = SceneProperties{sceneId, uiId}; - entityNode.data.entity = entity; + entityNode.data.entity = entity; const auto entityUuid = Application::m_coordinator->tryGetComponent(entity); - if (entityUuid) - { - entityNode.uuid = entityUuid->get().uuid; + if (entityUuid) { + entityNode.uuid = entityUuid->get().uuid; entityNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); - } - else + } else entityNode.uiName = uiName; return entityNode; } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp index 105c18e3e..72a8b21d9 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp @@ -29,26 +29,23 @@ namespace nexo::editor { *(result.out) = '\0'; ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove border - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); // No rounding + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); // No rounding ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); if (ImGui::InputText("##Rename", buffer, sizeof(buffer), - ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) - { - obj.uiName = ObjectTypeToIcon.at(obj.type) + std::string(buffer); + ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) { + obj.uiName = ObjectTypeToIcon.at(obj.type) + std::string(buffer); auto &selector = Selector::get(); selector.setUiHandle(obj.uuid, obj.uiName); - if (obj.type == SelectionType::SCENE) - { + if (obj.type == SelectionType::SCENE) { auto &app = getApp(); app.getSceneManager().getScene(obj.data.sceneProperties.sceneId).setName(obj.uiName); } m_renameTarget.reset(); } - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) - m_renameTarget.reset(); + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) m_renameTarget.reset(); ImGui::PopStyleVar(3); ImGui::EndGroup(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp index fdfb48927..815e25c97 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp @@ -12,17 +12,17 @@ // /////////////////////////////////////////////////////////////////////////////// +#include #include "SceneTreeWindow.hpp" #include "utils/Config.hpp" -#include namespace nexo::editor { - bool SceneTreeWindow::setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName) const + bool SceneTreeWindow::setupNewDockSpaceNode(const std::string &floatingWindowName, + const std::string &newSceneName) const { - const ImGuiWindow* floatingWindow = ImGui::FindWindowByName(floatingWindowName.c_str()); - if (!floatingWindow) - return false; + const ImGuiWindow *floatingWindow = ImGui::FindWindowByName(floatingWindowName.c_str()); + if (!floatingWindow) return false; // Create a new docking node const auto newDockId = ImGui::GetID("##DockNode"); @@ -32,7 +32,7 @@ namespace nexo::editor { ImGui::DockBuilderAddNode(newDockId, ImGuiDockNodeFlags_None); // Set node size and position based on the floating window - const ImVec2 windowPos = floatingWindow->Pos; + const ImVec2 windowPos = floatingWindow->Pos; const ImVec2 windowSize = floatingWindow->Size; ImGui::DockBuilderSetNodeSize(newDockId, windowSize); ImGui::DockBuilderSetNodePos(newDockId, windowPos); @@ -65,9 +65,9 @@ namespace nexo::editor { const std::vector &editorSceneInConfig = findAllEditorScenes(); if (!editorSceneInConfig.empty()) { const auto dockId = m_windowRegistry.getDockId(editorSceneInConfig[0]); - if (!dockId.has_value()) - return false; - m_windowRegistry.setDockId(std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, newScene->getSceneId()), *dockId); + if (!dockId.has_value()) return false; + m_windowRegistry.setDockId(std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, newScene->getSceneId()), + *dockId); return true; } // If nothing is present in config file, simply let it float @@ -75,14 +75,18 @@ namespace nexo::editor { } // Else we retrieve the first active editor scene - const std::string windowName = std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, currentEditorSceneWindow[0]->getSceneId()); + const std::string windowName = + std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, currentEditorSceneWindow[0]->getSceneId()); const auto dockId = m_windowRegistry.getDockId(windowName); - // If we dont find the dockId, it means the scene is floating, so we create a new dock space node + // If we don't find the dockId, it means the scene is floating, so we create a new dock space node if (!dockId.has_value()) { - setupNewDockSpaceNode(windowName, std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, newScene->getSceneId())); + if (!setupNewDockSpaceNode(windowName, + std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, newScene->getSceneId()))) { + return false; + } return true; } m_windowRegistry.setDockId(std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, newScene->getSceneId()), *dockId); return true; } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp index c72b9a2ec..01b0f34b1 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp @@ -17,28 +17,26 @@ #include "DocumentWindows/EditorScene/EditorScene.hpp" #include "IconsFontAwesome.h" #include "Nexo.hpp" -#include "core/scene/SceneManager.hpp" #include "context/Selector.hpp" +#include "core/scene/SceneManager.hpp" #include "../PopupManager.hpp" -#include -#include #include #include +#include +#include -namespace nexo::editor -{ +namespace nexo::editor { /** * @brief Stores scene identification information. * * Contains the scene's unique identifier and its associated window ID - * to facilitate operations that require both scene and UI context. + * to facilitate operations that require both scenes and UI context. */ - struct SceneProperties - { + struct SceneProperties { scene::SceneId sceneId; ///< The unique identifier for the scene - WindowId windowId; ///< The associated window identifier in the UI + WindowId windowId; ///< The associated window identifier in the UI }; /** @@ -47,10 +45,9 @@ namespace nexo::editor * Combines entity ID with scene properties to maintain the hierarchical * relationship between entities and their containing scenes. */ - struct EntityProperties - { + struct EntityProperties { SceneProperties sceneProperties; ///< Properties of the scene containing this entity - ecs::Entity entity; ///< The entity identifier + ecs::Entity entity; ///< The entity identifier }; /** @@ -63,27 +60,32 @@ namespace nexo::editor {SelectionType::AMBIENT_LIGHT, ICON_FA_ADJUST " "}, {SelectionType::DIR_LIGHT, ICON_FA_SUN_O " "}, {SelectionType::POINT_LIGHT, ICON_FA_LIGHTBULB_O " "}, - {SelectionType::SPOT_LIGHT, ICON_FA_ARROW_CIRCLE_DOWN " "} - }; + {SelectionType::SPOT_LIGHT, ICON_FA_ARROW_CIRCLE_DOWN " "}}; /** * @brief Represents an object in the scene tree. * * Contains a UI name, UUID, selection type, associated data, and potential child nodes. */ - struct SceneObject - { - std::string uiName; ///< The UI display name for the object. - std::string uuid; ///< The unique identifier (UUID) of the object. - SelectionType type; ///< The type of the object. - EntityProperties data; ///< Associated data (scene properties and entity). + struct SceneObject { + std::string uiName; ///< The UI display name for the object. + std::string uuid; ///< The unique identifier (UUID) of the object. + SelectionType type; ///< The type of the object. + EntityProperties data; ///< Associated data (scene properties and entity). std::vector children; ///< Child objects (if any). + /** + * @brief Constructs a SceneObject with the given parameters. + * + * @param name The UI display name for the object. + * @param children A vector of child SceneObject instances. + * @param type The selection type of the object. + * @param data The associated entity properties. + */ explicit SceneObject(std::string name = "", std::vector children = {}, const SelectionType type = SelectionType::NONE, const EntityProperties data = {}) : uiName(std::move(name)), type(type), data(data), children(std::move(children)) - { - } + {} }; /** @@ -93,14 +95,24 @@ namespace nexo::editor * renaming, context menus, and scene/node creation. */ class SceneTreeWindow final : public ADocumentWindow { - public: - using ADocumentWindow::ADocumentWindow; - ~SceneTreeWindow() override = default; + public: + using ADocumentWindow::ADocumentWindow; + ~SceneTreeWindow() override = default; - // No-op method in this class + /** + * @brief Sets up the Scene Tree window. + * + * This method is called during the initialization phase of the Scene Tree window. + * It is currently a no-op, as there are no specific setup actions required for this window. + */ void setup() override; - // No-op method in this class + /** + * @brief Shuts down the Scene Tree window. + * + * This method is called during the shutdown phase of the Scene Tree window. + * It is currently a no-op, as there are no specific shutdown actions required for this window. + */ void shutdown() override; /** @@ -124,22 +136,56 @@ namespace nexo::editor */ void update() override; - void generateHierarchicalNodes(std::map &scenes); - void buildChildNodesForEntity( - ecs::Entity parentEntity, - SceneObject& parentNode, - std::unordered_set& processedEntities); - static SceneObject createEntityNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity entity) ; + /** + * @brief Generates hierarchical nodes for all entities in the scenes. + * + * This function iterates through each scene in the provided map and generates hierarchical nodes + * for all entities within those scenes. It ensures that parent-child relationships are maintained + * by recursively building child nodes for each entity, avoiding duplication by tracking processed entities. + * + * @param scenes A map of scene IDs to their corresponding SceneObject representations. + */ + static void generateHierarchicalNodes(std::map& scenes); + + /** + * @brief Recursively builds child nodes for a given entity and its children. + * + * This function takes a parent entity and its corresponding SceneObject node, then recursively + * processes its children to build a hierarchical structure. It uses a set to track processed entities + * to avoid duplication and ensure each entity is only added once to the scene tree. + * + * @param parentEntity The entity whose children are to be processed. + * @param parentNode The SceneObject node representing the parent entity. + * @param processedEntities A set of entities that have already been processed to prevent duplication. + */ + static void buildChildNodesForEntity(ecs::Entity parentEntity, SceneObject& parentNode, + std::unordered_set& processedEntities); - private: - SceneObject root_; ///< Root node of the scene tree. - unsigned int m_nbDirLights = 0; ///< Counter for directional lights. - unsigned int m_nbPointLights = 0; ///< Counter for point lights. - unsigned int m_nbSpotLights = 0; ///< Counter for spot lights. - std::optional> m_renameTarget; ///< Target for renaming. - std::string m_renameBuffer; ///< Buffer for rename input. - PopupManager m_popupManager; ///< Manages context and creation popups. - ecs::Entity m_pendingPhysicsEntity = 0; ///< Entity waiting for physics component addition. + /** + * @brief Creates a new entity node for the scene tree. + * + * Constructs and initializes a SceneObject to represent an entity within the scene tree UI. + * The node's properties are configured using the provided scene ID, UI window ID, and entity ID: + * the scene properties are set from the scene and UI IDs, the entity ID is assigned, the entity's + * unique UUID is obtained from the UuidComponent, and a UI handle is generated by concatenating an + * entity icon with the entity's name (or "Entity" if no name is found). + * + * @param sceneId The unique identifier of the scene containing the entity. + * @param uiId The identifier for the scene's UI element. + * @param entity The entity identifier. + * @return SceneObject The newly created entity node with initialized properties. + */ + static SceneObject createEntityNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity entity); + + private: + SceneObject root_; ///< Root node of the scene tree. + unsigned int m_nbDirLights = 0; ///< Counter for directional lights. + unsigned int m_nbPointLights = 0; ///< Counter for point lights. + unsigned int m_nbSpotLights = 0; ///< Counter for spot lights. + std::optional> m_renameTarget; ///< Target for renaming. + std::string m_renameBuffer; ///< Buffer for rename input. + PopupManager m_popupManager; ///< Manages context and creation popups. + ecs::Entity m_pendingPhysicsEntity = 0; ///< Entity waiting for physics component addition. /** * @brief Generates nodes for all entities matching the specified components. @@ -153,15 +199,13 @@ namespace nexo::editor * @param scenes Map of scene IDs to their SceneObject nodes. * @param nodeCreator Function that creates a SceneObject given a scene ID, UI window ID, and entity. */ - template + template static void generateNodes(std::map& scenes, NodeCreator nodeCreator) { const std::vector entities = Application::m_coordinator->getAllEntitiesWith(); - for (const ecs::Entity entity : entities) - { + for (const ecs::Entity entity : entities) { const auto& sceneTag = Application::m_coordinator->getComponent(entity); - if (auto it = scenes.find(sceneTag.id); it != scenes.end()) - { + if (auto it = scenes.find(sceneTag.id); it != scenes.end()) { SceneObject newNode = nodeCreator(it->second.data.sceneProperties.sceneId, it->second.data.sceneProperties.windowId, entity); it->second.children.push_back(newNode); @@ -169,50 +213,51 @@ namespace nexo::editor } } - /** - * @brief Creates a new scene node for the scene tree. - * - * Constructs and initializes a SceneObject to represent a scene within the scene tree UI. - * The node's properties are configured using the provided scene and UI identifiers: scene properties - * and type are set from these identifiers, the scene's unique UUID is obtained from the Scene Manager, - * and a UI handle is generated by concatenating a scene icon with the scene name from the Scene View Manager. - * - * @param sceneName The name of the scene. - * @param sceneId The unique identifier of the scene. - * @param uiId The identifier for the scene's UI element. - * @return SceneObject The newly created scene node with initialized properties. - */ - static SceneObject newSceneNode(const std::string &sceneName, scene::SceneId sceneId, WindowId uiId); - - /** - * @brief Creates a new light node and adds properties to it. - * - * @param lightNode The SceneObject to populate. - * @param sceneId The scene ID. - * @param uiId The UI window ID. - * @param lightEntity The light entity. - * @param uiName The UI display name. - */ - static void newLightNode(SceneObject &lightNode, scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity, const std::string &uiName); - - /** - * @brief Creates a new ambient light node. - * - * @param sceneId The scene ID. - * @param uiId The UI window ID. - * @param lightEntity The ambient light entity. - * @return A SceneObject representing the ambient light. - */ - static SceneObject newAmbientLightNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity); - /** - * @brief Creates a new directional light node. - * - * @param sceneId The scene ID. - * @param uiId The UI window ID. - * @param lightEntity The directional light entity. - * @return A SceneObject representing the directional light. - */ - SceneObject newDirectionalLightNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity); + /** + * @brief Creates a new scene node for the scene tree. + * + * Constructs and initializes a SceneObject to represent a scene within the scene tree UI. + * The node's properties are configured using the provided scene and UI identifiers: scene properties + * and type are set from these identifiers, the scene's unique UUID is obtained from the Scene Manager, + * and a UI handle is generated by concatenating a scene icon with the scene name from the Scene View Manager. + * + * @param sceneName The name of the scene. + * @param sceneId The unique identifier of the scene. + * @param uiId The identifier for the scene's UI element. + * @return SceneObject The newly created scene node with initialized properties. + */ + static SceneObject newSceneNode(const std::string& sceneName, scene::SceneId sceneId, WindowId uiId); + + /** + * @brief Creates a new light node and adds properties to it. + * + * @param lightNode The SceneObject to populate. + * @param sceneId The scene ID. + * @param uiId The UI window ID. + * @param lightEntity The light entity. + * @param uiName The UI display name. + */ + static void newLightNode(SceneObject& lightNode, scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity, + const std::string& uiName); + + /** + * @brief Creates a new ambient light node. + * + * @param sceneId The scene ID. + * @param uiId The UI window ID. + * @param lightEntity The ambient light entity. + * @return A SceneObject representing the ambient light. + */ + static SceneObject newAmbientLightNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity); + /** + * @brief Creates a new directional light node. + * + * @param sceneId The scene ID. + * @param uiId The UI window ID. + * @param lightEntity The directional light entity. + * @return A SceneObject representing the directional light. + */ + SceneObject newDirectionalLightNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity); /** * @brief Creates a new spot light node. @@ -312,8 +357,8 @@ namespace nexo::editor * @brief Displays a context menu option for deleting a camera. * * When the "Delete Camera" menu item is selected, the function retrieves the active SceneViewManager, - * finds the scene associated with the camera's window identifier, and deletes the camera entity from that scene. - * It also clears any current selection and notifies the application to delete the camera entity. + * finds the scene associated with the camera's window identifier, and deletes the camera entity from that + * scene. It also clears any current selection and notifies the application to delete the camera entity. * * @param obj The scene object representing the camera to be deleted. */ @@ -357,6 +402,16 @@ namespace nexo::editor */ void sceneContextMenu(); + /** + * @brief Displays the context menu for a selected scene. + * + * Provides options such as deleting the scene or renaming it when + * right-clicking on a scene node. + * + * @param sceneId The ID of the selected scene. + * @param uuid The UUID of the selected scene (optional). + * @param uiName The UI name of the selected scene (optional). + */ void showSceneSelectionContextMenu(scene::SceneId sceneId, const std::string& uuid = "", const std::string& uiName = ""); @@ -394,7 +449,7 @@ namespace nexo::editor * @param newSceneName The name for the new scene. * @return true if the scene was created successfully, false otherwise. */ - bool handleSceneCreation(const std::string& newSceneName) const; + [[nodiscard]] bool handleSceneCreation(const std::string& newSceneName) const; /** * @brief Sets up docking for a new scene window. @@ -406,67 +461,138 @@ namespace nexo::editor * @param newSceneName The name of the new scene window to dock. * @return true if the docking setup was successful, false otherwise. */ - bool setupNewDockSpaceNode(const std::string& floatingWindowName, const std::string& newSceneName) const; + [[nodiscard]] bool setupNewDockSpaceNode(const std::string& floatingWindowName, + const std::string& newSceneName) const; - enum class SceneTreeState - { - GLOBAL, - NB_STATE - }; + enum class SceneTreeState { GLOBAL, NB_STATE }; WindowState m_defaultState; - bool m_forceExpandAll = false; + bool m_forceExpandAll = false; bool m_forceCollapseAll = false; bool m_resetExpandState = false; - // Add these method declarations + /** + * @brief Sets up keyboard shortcuts for scene tree actions. + * + * This method configures keyboard shortcuts for various scene tree operations, + * such as deleting selected items, adding entities, expanding/collapsing all nodes, + * renaming selected items, duplicating selected items, selecting all items, + * hiding selected items, and showing all items. + */ void setupShortcuts(); + + /** + * @brief Resets the scene tree window to its default state. + * + * This method clears any current selections, resets internal counters, + * and prepares the scene tree window for a fresh start. + */ void setupDefaultState(); - // Add these callback methods + /** @brief Callback for deleting the selected item(s) in the scene tree. + * + * This method is invoked when the user triggers the delete action (e.g., via a keyboard shortcut). + * It handles the deletion of the currently selected entity or scene, ensuring that the selection + * state is updated accordingly. + */ void deleteSelectedCallback(); + + /** @brief Callback for adding a new entity to the scene. + * + * This method is invoked when the user triggers the add entity action (e.g., via a keyboard shortcut). + * It handles the creation of a new entity within the currently active scene, updating the scene tree + * to reflect the addition. + */ void addEntityCallback(); + + /** @brief Callback for expanding all nodes in the scene tree. + * + * This method is invoked when the user triggers the expand all action (e.g., via a keyboard shortcut). + * It sets a flag to force all nodes in the scene tree to be expanded, allowing the user to view + * all entities and their hierarchies at once. + */ void expandAllCallback(); + + /** @brief Callback for collapsing all nodes in the scene tree. + * + * This method is invoked when the user triggers the collapse all action (e.g., via a keyboard shortcut). + * It sets a flag to force all nodes in the scene tree to be collapsed, providing a cleaner view + * of the top-level scenes. + */ void collapseAllCallback(); - void renameSelectedCallback(); + + /** @brief Callback for renaming the selected item in the scene tree. + * + * This method is invoked when the user triggers the rename action (e.g., via a keyboard shortcut). + * It initiates the renaming process for the currently selected entity or scene, allowing the user + * to input a new name. + */ + static void renameSelectedCallback(); + + /** @brief Callback for duplicating the selected item in the scene tree. + * + * This method is invoked when the user triggers the duplicate action (e.g., via a keyboard shortcut). + * It handles the duplication of the currently selected entity, creating a copy within the same scene + * and updating the scene tree to reflect the new entity. + */ static void duplicateSelectedCallback(); + /** @brief Callback for selecting all items in the scene tree. + * + * This method is invoked when the user triggers the select all action (e.g., via a keyboard shortcut). + * It updates the selection state to include all entities and scenes in the scene tree. + */ static void selectAllCallback(); + + /** @brief Callback for hiding the selected item(s) in the scene tree. + * + * This method is invoked when the user triggers the hide action (e.g., via a keyboard shortcut). + * It sets the visibility of the currently selected entity or entities to hidden, updating the + * scene tree and rendering accordingly. + */ static void hideSelectedCallback(); + + /** @brief Callback for showing all items in the scene tree. + * + * This method is invoked when the user triggers the show all action (e.g., via a keyboard shortcut). + * It sets the visibility of all entities in the scene tree to visible, ensuring that all items + * are displayed in the scene and rendering. + */ static void showAllCallback(); /** - * @brief Handles drag source setup for scene objects. - * @param object The scene object being dragged. - */ + * @brief Handles drag source setup for scene objects. + * @param object The scene object being dragged. + */ static void handleDragSource(const SceneObject& object); /** - * @brief Handles drop target setup for scene objects. - * @param object The scene object that can receive drops. - */ + * @brief Handles drop target setup for scene objects. + * @param object The scene object that can receive drops. + */ static void handleDropTarget(const SceneObject& object); /** - * @brief Processes the drop operation from the scene tree itself. - * @param dropTarget The target scene object receiving the drop. - * @param payload The drag-and-drop payload data. - */ - static void handleDropFromSceneTree(const SceneObject& dropTarget, const struct SceneTreeDragDropPayload& payload); + * @brief Processes the drop operation from the scene tree itself. + * @param dropTarget The target scene object receiving the drop. + * @param payload The drag-and-drop payload data. + */ + static void handleDropFromSceneTree(const SceneObject& dropTarget, + const struct SceneTreeDragDropPayload& payload); /** - * @brief Processes the drop operation from the asset manager. - * @param dropTarget The target scene object receiving the drop. - * @param payload The drag-and-drop payload data. - */ + * @brief Processes the drop operation from the asset manager. + * @param dropTarget The target scene object receiving the drop. + * @param payload The drag-and-drop payload data. + */ static void handleDropFromAssetManager(const SceneObject& dropTarget, const AssetDragDropPayload& payload); /** - * @brief Validates if a drop operation is allowed. - * @param dropTarget The target scene object. - * @param payload The drag-and-drop payload data. - * @return true if the drop is valid, false otherwise. - */ + * @brief Validates if a drop operation is allowed. + * @param dropTarget The target scene object. + * @param payload The drag-and-drop payload data. + * @return true if the drop is valid, false otherwise. + */ static bool canAcceptDrop(const SceneObject& dropTarget, const SceneTreeDragDropPayload& payload); }; @@ -476,12 +602,11 @@ namespace nexo::editor * Contains all necessary information to perform entity/scene drag and drop * operations including validation and hierarchy updates. */ - struct SceneTreeDragDropPayload - { - ecs::Entity entity; ///< The entity being dragged + struct SceneTreeDragDropPayload { + ecs::Entity entity; ///< The entity being dragged scene::SceneId sourceSceneId; ///< The scene the entity originated from - SelectionType type; ///< The type of object being dragged - std::string uuid; ///< UUID of the dragged object - std::string name; ///< Display name of the dragged object + SelectionType type; ///< The type of object being dragged + std::string uuid; ///< UUID of the dragged object + std::string name; ///< Display name of the dragged object }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp index f04dba9bb..30a3530e5 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp @@ -13,65 +13,56 @@ /////////////////////////////////////////////////////////////////////////////// #include "SceneTreeWindow.hpp" -#include "EntityFactory3D.hpp" -#include "utils/EditorProps.hpp" +#include "components/PhysicsBodyComponent.hpp" #include "context/ActionManager.hpp" #include "context/actions/EntityActions.hpp" -#include "components/PhysicsBodyComponent.hpp" -#include "components/Transform.hpp" -#include "systems/PhysicsSystem.hpp" +#include "utils/EditorProps.hpp" -#include #include +#include namespace nexo::editor { void SceneTreeWindow::entitySelected(const SceneObject &obj) { auto &selector = Selector::get(); - auto &app = getApp(); + auto &app = getApp(); // Check if we're operating on a single item or multiple items - const auto& selectedEntities = selector.getSelectedEntities(); - const bool multipleSelected = selectedEntities.size() > 1; + const auto &selectedEntities = selector.getSelectedEntities(); + const bool multipleSelected = selectedEntities.size() > 1; // Only show "Add Component" for single entity selection if (!multipleSelected) { - if (ImGui::BeginMenu("Add Component")) - { + if (ImGui::BeginMenu("Add Component")) { // Check if entity already has physics component - auto& coordinator = *Application::m_coordinator; - const bool hasPhysics = coordinator.entityHasComponent(obj.data.entity); - - if (!hasPhysics && ImGui::MenuItem("Physics Body")) - { + const auto &coordinator = *Application::m_coordinator; + const bool hasPhysics = + coordinator.entityHasComponent(obj.data.entity); + + if (!hasPhysics && ImGui::MenuItem("Physics Body")) { // Store the entity ID for the popup m_pendingPhysicsEntity = obj.data.entity; // Use PopupManager to open the popup m_popupManager.openPopup("Physics Type Selection"); - } - else if (hasPhysics) - { + } else if (hasPhysics) { ImGui::TextDisabled("Physics Body (already added)"); } - - + ImGui::EndMenu(); } - + ImGui::Separator(); } - const std::string menuText = multipleSelected ? - std::format("Delete Selected Entities ({})", selectedEntities.size()) : - "Delete Entity"; + const std::string menuText = + multipleSelected ? std::format("Delete Selected Entities ({})", selectedEntities.size()) : "Delete Entity"; - if (ImGui::MenuItem(menuText.c_str())) - { + if (ImGui::MenuItem(menuText.c_str())) { if (multipleSelected) { // Delete all selected entities auto actionGroup = ActionManager::createActionGroup(); - for (const auto& entityId : selectedEntities) { + for (const auto &entityId : selectedEntities) { auto deleteAction = std::make_unique(entityId); actionGroup->addAction(std::move(deleteAction)); app.deleteEntity(entityId); @@ -86,29 +77,25 @@ namespace nexo::editor { app.deleteEntity(obj.data.entity); } } - - } void SceneTreeWindow::cameraSelected(const SceneObject &obj) const { - auto &app = Application::getInstance(); + auto &app = Application::getInstance(); auto &selector = Selector::get(); // Check if we're operating on a single item or multiple items - const auto& selectedEntities = selector.getSelectedEntities(); - const bool multipleSelected = selectedEntities.size() > 1; + const auto &selectedEntities = selector.getSelectedEntities(); + const bool multipleSelected = selectedEntities.size() > 1; - const std::string deleteMenuText = multipleSelected ? - std::format("Delete Selected Entities ({})", selectedEntities.size()) : - "Delete Camera"; + const std::string deleteMenuText = + multipleSelected ? std::format("Delete Selected Entities ({})", selectedEntities.size()) : "Delete Camera"; - if (ImGui::MenuItem(deleteMenuText.c_str())) - { + if (ImGui::MenuItem(deleteMenuText.c_str())) { if (multipleSelected) { // Delete all selected entities auto actionGroup = ActionManager::createActionGroup(); - for (const auto& entityId : selectedEntities) { + for (const auto &entityId : selectedEntities) { auto deleteAction = std::make_unique(entityId); actionGroup->addAction(std::move(deleteAction)); app.deleteEntity(entityId); @@ -125,12 +112,12 @@ namespace nexo::editor { } // Switch to camera only makes sense for a single camera - if (!multipleSelected && ImGui::MenuItem("Switch to")) - { - auto &cameraComponent = Application::m_coordinator->getComponent(obj.data.entity); + if (!multipleSelected && ImGui::MenuItem("Switch to")) { + auto &cameraComponent = + Application::m_coordinator->getComponent(obj.data.entity); cameraComponent.render = true; cameraComponent.active = true; - const auto &scenes = m_windowRegistry.getWindows(); + const auto &scenes = m_windowRegistry.getWindows(); for (const auto &scene : scenes) { if (scene->getSceneId() == obj.data.sceneProperties.sceneId) { scene->setCamera(obj.data.entity); @@ -142,23 +129,21 @@ namespace nexo::editor { void SceneTreeWindow::lightSelected(const SceneObject &obj) { - auto &app = Application::getInstance(); + auto &app = Application::getInstance(); auto &selector = Selector::get(); // Check if we're operating on a single item or multiple items - const auto& selectedEntities = selector.getSelectedEntities(); - const bool multipleSelected = selectedEntities.size() > 1; + const auto &selectedEntities = selector.getSelectedEntities(); + const bool multipleSelected = selectedEntities.size() > 1; - const std::string menuText = multipleSelected ? - std::format("Delete Selected Entities ({})", selectedEntities.size()) : - "Delete Light"; + const std::string menuText = + multipleSelected ? std::format("Delete Selected Entities ({})", selectedEntities.size()) : "Delete Light"; - if (ImGui::MenuItem(menuText.c_str())) - { + if (ImGui::MenuItem(menuText.c_str())) { if (multipleSelected) { // Delete all selected entities auto actionGroup = ActionManager::createActionGroup(); - for (const auto& entityId : selectedEntities) { + for (const auto &entityId : selectedEntities) { auto deleteAction = std::make_unique(entityId); actionGroup->addAction(std::move(deleteAction)); app.deleteEntity(entityId); @@ -177,24 +162,21 @@ namespace nexo::editor { void SceneTreeWindow::sceneSelected([[maybe_unused]] const SceneObject &obj) { - m_popupManager.openPopupWithCallback( - "Scene selection context menu", - [this, obj]() {this->showSceneSelectionContextMenu(obj.data.sceneProperties.sceneId, obj.uuid, obj.uiName);} - ); + m_popupManager.openPopupWithCallback("Scene selection context menu", [this, obj]() { + this->showSceneSelectionContextMenu(obj.data.sceneProperties.sceneId, obj.uuid, obj.uiName); + }); } bool SceneTreeWindow::handleSelection(const SceneObject &obj, const std::string &uniqueLabel, const ImGuiTreeNodeFlags baseFlags) { const bool nodeOpen = ImGui::TreeNodeEx(uniqueLabel.c_str(), baseFlags); - if (!nodeOpen) - return nodeOpen; + if (!nodeOpen) return nodeOpen; - if (ImGui::IsItemClicked()) - { - auto &selector = Selector::get(); + if (ImGui::IsItemClicked()) { + auto &selector = Selector::get(); const bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); - const bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + const bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); if (isCtrlPressed) selector.toggleSelection(obj.uuid, static_cast(obj.data.entity), obj.type); @@ -206,4 +188,4 @@ namespace nexo::editor { } return nodeOpen; } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp index f202fbce9..4fbad2501 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp @@ -14,8 +14,8 @@ #include "SceneTreeWindow.hpp" #include "components/SceneComponents.hpp" -#include "context/ActionManager.hpp" #include "components/Uuid.hpp" +#include "context/ActionManager.hpp" #include "context/actions/EntityActions.hpp" namespace nexo::editor { @@ -35,119 +35,87 @@ namespace nexo::editor { .description("Control context") .key("Ctrl") .modifier(true) + .addChild(Command::create() + .description("Select all") + .key("A") + .onPressed([]() { selectAllCallback(); }) + .build()) + .addChild(Command::create() + .description("Duplicate") + .key("D") + .onPressed([]() { duplicateSelectedCallback(); }) + .build()) .addChild( - Command::create() - .description("Select all") - .key("A") - .onPressed([](){ - selectAllCallback(); }) - .build() - ) - .addChild( - Command::create() - .description("Duplicate") - .key("D") - .onPressed([](){ duplicateSelectedCallback(); }) - .build() - ) - .addChild( - Command::create() - .description("Unhide all") - .key("H") - .onPressed([](){ showAllCallback(); }) - .build() - ) - .addChild( - Command::create() - .description("Create Scene") - .key("N") - .onPressed([this](){ this->m_popupManager.openPopup("Create New Scene"); }) - .build() - ) - .build() - ); + Command::create().description("Unhide all").key("H").onPressed([]() { showAllCallback(); }).build()) + .addChild(Command::create() + .description("Create Scene") + .key("N") + .onPressed([this]() { this->m_popupManager.openPopup("Create New Scene"); }) + .build()) + .build()); // Delete selected m_defaultState.registerCommand( - Command::create() - .description("Add Entity") - .key("A") - .onPressed([this](){ addEntityCallback(); }) - .build() - ); + Command::create().description("Add Entity").key("A").onPressed([this]() { addEntityCallback(); }).build()); // Delete selected - m_defaultState.registerCommand( - Command::create() - .description("Delete") - .key("Delete") - .onPressed([this](){ deleteSelectedCallback(); }) - .build() - ); + m_defaultState.registerCommand(Command::create() + .description("Delete") + .key("Delete") + .onPressed([this]() { deleteSelectedCallback(); }) + .build()); // Rename selected - m_defaultState.registerCommand( - Command::create() - .description("Rename") - .key("F2") - .onPressed([this](){ renameSelectedCallback(); }) - .build() - ); + m_defaultState.registerCommand(Command::create() + .description("Rename") + .key("F2") + .onPressed([this]() { renameSelectedCallback(); }) + .build()); // Expand all nodes - m_defaultState.registerCommand( - Command::create() - .description("Expand all") - .key("Down") - .onPressed([this](){ expandAllCallback(); }) - .build() - ); + m_defaultState.registerCommand(Command::create() + .description("Expand all") + .key("Down") + .onPressed([this]() { expandAllCallback(); }) + .build()); // Collapse all nodes - m_defaultState.registerCommand( - Command::create() - .description("Collapse all") - .key("Up") - .onPressed([this](){ collapseAllCallback(); }) - .build() - ); + m_defaultState.registerCommand(Command::create() + .description("Collapse all") + .key("Up") + .onPressed([this]() { collapseAllCallback(); }) + .build()); // Hide selected m_defaultState.registerCommand( - Command::create() - .description("Hide") - .key("H") - .onPressed([](){ hideSelectedCallback(); }) - .build() - ); + Command::create().description("Hide").key("H").onPressed([]() { hideSelectedCallback(); }).build()); } void SceneTreeWindow::addEntityCallback() { - const auto &selector = Selector::get(); - int currentSceneId = selector.getSelectedScene(); - if (currentSceneId == -1) - return; - - m_popupManager.openPopupWithCallback( - "Scene selection context menu", - [this, currentSceneId]() {showSceneSelectionContextMenu(currentSceneId);} - ); + const auto& selector = Selector::get(); + int currentSceneId = selector.getSelectedScene(); + if (currentSceneId == -1) return; + + m_popupManager.openPopupWithCallback("Scene selection context menu", [this, currentSceneId]() { + showSceneSelectionContextMenu(currentSceneId); + }); } void SceneTreeWindow::selectAllCallback() { - auto& selector = Selector::get(); + auto& selector = Selector::get(); const int currentSceneId = selector.getSelectedScene(); if (currentSceneId != -1) { - auto& app = getApp(); + auto& app = getApp(); const auto& scene = app.getSceneManager().getScene(currentSceneId); selector.clearSelection(); for (const auto entity : scene.getEntities()) { - const auto uuidComponent = Application::m_coordinator->tryGetComponent(entity); + const auto uuidComponent = + Application::m_coordinator->tryGetComponent(entity); if (uuidComponent) { selector.addToSelection(uuidComponent->get().uuid, static_cast(entity)); } @@ -157,13 +125,12 @@ namespace nexo::editor { void SceneTreeWindow::deleteSelectedCallback() { - auto& selector = Selector::get(); + auto& selector = Selector::get(); const auto& selectedEntities = selector.getSelectedEntities(); - if (selectedEntities.empty()) - return; + if (selectedEntities.empty()) return; - auto& app = getApp(); + auto& app = getApp(); auto& actionManager = ActionManager::get(); if (selectedEntities.size() > 1) { @@ -183,38 +150,35 @@ namespace nexo::editor { m_windowState = m_defaultState; } - void SceneTreeWindow::expandAllCallback() { m_forceCollapseAll = false; - m_forceExpandAll = true; + m_forceExpandAll = true; } void SceneTreeWindow::collapseAllCallback() { m_forceCollapseAll = true; - m_forceExpandAll = false; + m_forceExpandAll = false; } void SceneTreeWindow::renameSelectedCallback() { - //TODO: Implement rename callback + // TODO: Implement rename callback } void SceneTreeWindow::duplicateSelectedCallback() { - auto& selector = Selector::get(); + auto& selector = Selector::get(); const auto& selectedEntities = selector.getSelectedEntities(); - if (selectedEntities.empty()) - return; + if (selectedEntities.empty()) return; - auto& app = nexo::getApp(); - auto& actionManager = ActionManager::get(); + auto& app = nexo::getApp(); + auto& actionManager = ActionManager::get(); const int currentSceneId = selector.getSelectedScene(); - if (currentSceneId == -1) - return; + if (currentSceneId == -1) return; std::vector newEntities; newEntities.reserve(selectedEntities.size()); @@ -236,7 +200,8 @@ namespace nexo::editor { // Select all the newly created entities for (const auto newEntity : newEntities) { - const auto uuidComponent = Application::m_coordinator->tryGetComponent(newEntity); + const auto uuidComponent = + Application::m_coordinator->tryGetComponent(newEntity); if (uuidComponent) { selector.addToSelection(uuidComponent->get().uuid, static_cast(newEntity)); } @@ -245,22 +210,21 @@ namespace nexo::editor { void SceneTreeWindow::hideSelectedCallback() { - const auto& selector = Selector::get(); + const auto& selector = Selector::get(); const auto& selectedEntities = selector.getSelectedEntities(); - if (selectedEntities.empty()) - return; + if (selectedEntities.empty()) return; auto& actionManager = ActionManager::get(); - auto actionGroup = ActionManager::createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : selectedEntities) { if (Application::m_coordinator->entityHasComponent(entity)) { auto& renderComponent = Application::m_coordinator->getComponent(entity); if (renderComponent.isRendered) { - auto beforeState = renderComponent.save(); + auto beforeState = renderComponent.save(); renderComponent.isRendered = !renderComponent.isRendered; - auto afterState = renderComponent.save(); + auto afterState = renderComponent.save(); actionGroup->addAction(std::make_unique>( entity, beforeState, afterState)); } @@ -272,23 +236,23 @@ namespace nexo::editor { void SceneTreeWindow::showAllCallback() { - const auto& selector = Selector::get(); + const auto& selector = Selector::get(); const int currentSceneId = selector.getSelectedScene(); if (currentSceneId == -1) return; - auto& app = getApp(); - const auto& scene = app.getSceneManager().getScene(currentSceneId); + auto& app = getApp(); + const auto& scene = app.getSceneManager().getScene(currentSceneId); auto& actionManager = ActionManager::get(); - auto actionGroup = ActionManager::createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : scene.getEntities()) { if (Application::m_coordinator->entityHasComponent(entity)) { auto& renderComponent = Application::m_coordinator->getComponent(entity); if (!renderComponent.isRendered) { - auto beforeState = renderComponent.save(); + auto beforeState = renderComponent.save(); renderComponent.isRendered = true; - auto afterState = renderComponent.save(); + auto afterState = renderComponent.save(); actionGroup->addAction(std::make_unique>( entity, beforeState, afterState)); } @@ -297,4 +261,4 @@ namespace nexo::editor { actionManager.recordAction(std::move(actionGroup)); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp index feb17ca5d..36bcf691c 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -12,26 +12,22 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "SceneTreeWindow.hpp" #include "../EntityProperties/PhysicsBodyProperty.hpp" #include "EntityFactory3D.hpp" +#include "ImNexo/Panels.hpp" #include "LightFactory.hpp" +#include "SceneTreeWindow.hpp" +#include "components/Transform.hpp" +#include "context/ActionManager.hpp" #include "context/actions/EntityActions.hpp" #include "utils/EditorProps.hpp" -#include "ImNexo/Panels.hpp" -#include "context/ActionManager.hpp" -#include "components/PhysicsBodyComponent.hpp" -#include "components/Transform.hpp" -#include "systems/PhysicsSystem.hpp" -namespace nexo::editor -{ +namespace nexo::editor { void SceneTreeWindow::showSceneSelectionContextMenu(scene::SceneId sceneId, const std::string& uuid, const std::string& uiName) { - if (!uuid.empty() && !uiName.empty() && ImGui::MenuItem("Delete Scene")) - { - auto& app = Application::getInstance(); + if (!uuid.empty() && !uiName.empty() && ImGui::MenuItem("Delete Scene")) { + auto& app = Application::getInstance(); auto& selector = Selector::get(); selector.clearSelection(); const std::string& sceneName = selector.getUiHandle(uuid, uiName); @@ -40,42 +36,36 @@ namespace nexo::editor } // ---- Add Entity submenu ---- - if (ImGui::BeginMenu("Add Entity")) - { - auto& app = Application::getInstance(); + if (ImGui::BeginMenu("Add Entity")) { + auto& app = Application::getInstance(); auto& sceneManager = app.getSceneManager(); // --- Primitives submenu --- - const auto& selector = Selector::get(); + const auto& selector = Selector::get(); const int currentSceneId = selector.getSelectedScene(); ImNexo::PrimitiveSubMenu(currentSceneId, m_popupManager); // --- Model item (with file‑dialog) --- - if (ImGui::MenuItem("Model")) - { - //TODO: import model + if (ImGui::MenuItem("Model")) { + // TODO: import model } // --- Lights submenu --- - if (ImGui::BeginMenu("Lights")) - { - if (ImGui::MenuItem("Directional")) - { + if (ImGui::BeginMenu("Lights")) { + if (ImGui::MenuItem("Directional")) { const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.0f, -1.0f, 0.0f}); sceneManager.getScene(sceneId).addEntity(directionalLight); auto action = std::make_unique(directionalLight); ActionManager::get().recordAction(std::move(action)); } - if (ImGui::MenuItem("Point")) - { + if (ImGui::MenuItem("Point")) { const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); sceneManager.getScene(sceneId).addEntity(pointLight); auto action = std::make_unique(pointLight); ActionManager::get().recordAction(std::move(action)); } - if (ImGui::MenuItem("Spot")) - { + if (ImGui::MenuItem("Spot")) { const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); @@ -87,20 +77,19 @@ namespace nexo::editor } // --- Camera item --- - if (ImGui::MenuItem("Camera")) - { - m_popupManager.openPopupWithCallback("Popup camera inspector", [this, sceneId]() - { - const auto& editorScenes = m_windowRegistry.getWindows(); - for (const auto& scene : editorScenes) - { - if (scene->getSceneId() == sceneId) - { - ImNexo::CameraInspector(sceneId); - break; + if (ImGui::MenuItem("Camera")) { + m_popupManager.openPopupWithCallback( + "Popup camera inspector", + [this, sceneId]() { + const auto& editorScenes = m_windowRegistry.getWindows(); + for (const auto& scene : editorScenes) { + if (scene->getSceneId() == sceneId) { + ImNexo::CameraInspector(sceneId); + break; + } } - } - }, ImVec2(1440, 900)); + }, + ImVec2(1440, 900)); } ImGui::EndMenu(); @@ -109,21 +98,17 @@ namespace nexo::editor void SceneTreeWindow::sceneContextMenu() { - if (m_popupManager.showPopup("Scene Tree Context Menu")) - { - if (ImGui::MenuItem("Create Scene")) - m_popupManager.openPopup("Create New Scene"); + if (m_popupManager.showPopup("Scene Tree Context Menu")) { + if (ImGui::MenuItem("Create Scene")) m_popupManager.openPopup("Create New Scene"); PopupManager::endPopup(); } - if (m_popupManager.showPopup("Scene selection context menu")) - { + if (m_popupManager.showPopup("Scene selection context menu")) { m_popupManager.runPopupCallback("Scene selection context menu"); PopupManager::endPopup(); } - if (m_popupManager.showPopupModal("Popup camera inspector")) - { + if (m_popupManager.showPopupModal("Popup camera inspector")) { m_popupManager.runPopupCallback("Popup camera inspector"); PopupManager::endPopup(); } @@ -131,53 +116,45 @@ namespace nexo::editor void SceneTreeWindow::sceneCreationMenu() { - if (!m_popupManager.showPopupModal("Create New Scene")) - return; + if (!m_popupManager.showPopupModal("Create New Scene")) return; static char sceneNameBuffer[256] = ""; ImGui::Text("Enter Scene Name:"); ImGui::InputText("##SceneName", sceneNameBuffer, sizeof(sceneNameBuffer)); - if (ImNexo::Button("Create") && handleSceneCreation(sceneNameBuffer)) - { + if (ImNexo::Button("Create") && handleSceneCreation(sceneNameBuffer)) { memset(sceneNameBuffer, 0, sizeof(sceneNameBuffer)); PopupManager::closePopup(); } ImGui::SameLine(); - if (ImNexo::Button("Cancel")) - PopupManager::closePopup(); + if (ImNexo::Button("Cancel")) PopupManager::closePopup(); PopupManager::endPopup(); } void SceneTreeWindow::showNode(SceneObject& object) { - ImGuiTreeNodeFlags baseFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | - ImGuiTreeNodeFlags_SpanAvailWidth; + ImGuiTreeNodeFlags baseFlags = + ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; // Checks if the object is at the end of a tree const bool leaf = object.children.empty(); - if (leaf) - baseFlags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + if (leaf) baseFlags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // Check if this object is selected - const auto& selector = Selector::get(); + const auto& selector = Selector::get(); const bool isSelected = selector.isEntitySelected(static_cast(object.data.entity)); - if (isSelected) - baseFlags |= ImGuiTreeNodeFlags_Selected; + if (isSelected) baseFlags |= ImGuiTreeNodeFlags_Selected; - bool nodeOpen = false; + bool nodeOpen = false; const std::string uniqueLabel = object.uiName; - if (m_forceExpandAll && !leaf) - { + if (m_forceExpandAll && !leaf) { ImGui::SetNextItemOpen(true); m_resetExpandState = true; - } - else if (m_forceCollapseAll) - { + } else if (m_forceCollapseAll) { ImGui::SetNextItemOpen(false); m_resetExpandState = true; } @@ -194,33 +171,30 @@ namespace nexo::editor handleDropTarget(object); // Handles the right click on each different type of object - if (object.type != SelectionType::NONE && ImGui::BeginPopupContextItem(uniqueLabel.c_str())) - { + using enum SelectionType; + if (object.type != NONE && ImGui::BeginPopupContextItem(uniqueLabel.c_str())) { // Only show rename option for the primary selected entity or for non-selected entities if ((!isSelected || selector.getPrimaryEntity() == static_cast(object.data.entity)) && - ImGui::MenuItem("Rename")) - { + ImGui::MenuItem("Rename")) { m_renameTarget = {object.type, object.uuid}; m_renameBuffer = object.uiName; } - if (object.type == SelectionType::SCENE) + if (object.type == SCENE) sceneSelected(object); - else if (object.type == SelectionType::DIR_LIGHT || object.type == SelectionType::POINT_LIGHT || object.type - == SelectionType::SPOT_LIGHT) + else if (object.type == DIR_LIGHT || object.type == POINT_LIGHT || + object.type == SPOT_LIGHT) lightSelected(object); - else if (object.type == SelectionType::CAMERA) + else if (object.type == CAMERA) cameraSelected(object); - else if (object.type == SelectionType::ENTITY) + else if (object.type == ENTITY) entitySelected(object); ImGui::EndPopup(); } // Go further into the tree - if (nodeOpen && !leaf) - { - for (auto& child : object.children) - { + if (nodeOpen && !leaf) { + for (auto& child : object.children) { showNode(child); } ImGui::TreePop(); @@ -233,45 +207,38 @@ namespace nexo::editor ImGui::SetNextWindowSize(ImVec2(300, ImGui::GetIO().DisplaySize.y - 40), ImGuiCond_FirstUseEver); if (ImGui::Begin(ICON_FA_SITEMAP " Scene Tree" NEXO_WND_USTRID_SCENE_TREE, &m_opened, - ImGuiWindowFlags_NoCollapse)) - { + ImGuiWindowFlags_NoCollapse)) { beginRender(NEXO_WND_USTRID_SCENE_TREE); const auto& selector = Selector::get(); // Opens the right click popup when no items are hovered - if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered( - ImGuiHoveredFlags_AllowWhenBlockedByPopup) && !ImGui::IsAnyItemHovered()) - { + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) && !ImGui::IsAnyItemHovered()) { m_popupManager.openPopup("Scene Tree Context Menu"); } // Display multi-selection count at top of window if applicable - if (const auto& selectedEntities = selector.getSelectedEntities(); selectedEntities.size() > 1) - { + if (const auto& selectedEntities = selector.getSelectedEntities(); selectedEntities.size() > 1) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.0f, 1.0f)); ImGui::Text("%zu entities selected", selectedEntities.size()); ImGui::PopStyleColor(); ImGui::Separator(); } - if (!root_.children.empty()) - { - for (auto& node : root_.children) - showNode(node); + if (!root_.children.empty()) { + for (auto& node : root_.children) showNode(node); } sceneContextMenu(); sceneCreationMenu(); // Show the primitive creation popup - if (m_popupManager.showPopup("Sphere creation popup")) - { + if (m_popupManager.showPopup("Sphere creation popup")) { const int sceneId = selector.getSelectedScene(); ImNexo::PrimitiveCustomizationMenu(sceneId, SPHERE); } - if (m_popupManager.showPopup("Cylinder creation popup")) - { + if (m_popupManager.showPopup("Cylinder creation popup")) { const int sceneId = selector.getSelectedScene(); ImNexo::PrimitiveCustomizationMenu(sceneId, CYLINDER); } @@ -282,8 +249,7 @@ namespace nexo::editor void SceneTreeWindow::physicsTypeSelectionPopup() { - if (!m_popupManager.showPopupModal("Physics Type Selection")) - return; + if (!m_popupManager.showPopupModal("Physics Type Selection")) return; ImGui::Text("Choose physics body type:"); ImGui::Spacing(); @@ -305,18 +271,16 @@ namespace nexo::editor ImGui::Spacing(); - if (ImGui::Button("Add Physics Component")) - { + if (ImGui::Button("Add Physics Component")) { PhysicsBodyProperty::addPhysicsComponentToEntity(m_pendingPhysicsEntity, physicsType == 1); PopupManager::closePopup(); } ImGui::SameLine(); - if (ImGui::Button("Cancel")) - { + if (ImGui::Button("Cancel")) { PopupManager::closePopup(); } PopupManager::closePopup(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp index 0387ef769..2ff11a758 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp @@ -18,7 +18,7 @@ namespace nexo::editor { void SceneTreeWindow::shutdown() { - // Nothing to shutdown + // Nothing to shut down } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp index c2e77a86b..27c5c952f 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp @@ -13,19 +13,18 @@ /////////////////////////////////////////////////////////////////////////////// #include "SceneTreeWindow.hpp" -#include "components/StaticMesh.hpp" #include "components/Name.hpp" +#include "components/StaticMesh.hpp" #include "components/Uuid.hpp" namespace nexo::editor { - void SceneTreeWindow::generateHierarchicalNodes(std::map &scenes) + void SceneTreeWindow::generateHierarchicalNodes(std::map& scenes) { // Find all root entities - const std::vector rootEntities = Application::m_coordinator->getAllEntitiesWith< - components::RootComponent, - components::TransformComponent, - components::SceneTag>(); + const std::vector rootEntities = + Application::m_coordinator + ->getAllEntitiesWith(); // Set to track entities that have been processed std::unordered_set processedEntities; @@ -35,8 +34,7 @@ namespace nexo::editor { const auto& sceneTag = Application::m_coordinator->getComponent(rootEntity); if (auto it = scenes.find(sceneTag.id); it != scenes.end()) { SceneObject rootNode = createEntityNode(it->second.data.sceneProperties.sceneId, - it->second.data.sceneProperties.windowId, - rootEntity); + it->second.data.sceneProperties.windowId, rootEntity); processedEntities.insert(rootEntity); buildChildNodesForEntity(rootEntity, rootNode, processedEntities); it->second.children.push_back(rootNode); @@ -45,21 +43,16 @@ namespace nexo::editor { // Find standalone entities (those with no parent but without RootComponent) const std::vector standaloneEntities = Application::m_coordinator->getAllEntitiesWith< - components::StaticMeshComponent, - components::TransformComponent, - components::SceneTag, - ecs::Exclude, - ecs::Exclude>(); + components::StaticMeshComponent, components::TransformComponent, components::SceneTag, + ecs::Exclude, ecs::Exclude>(); for (const ecs::Entity entity : standaloneEntities) { - if (processedEntities.contains(entity)) - continue; // Skip if already processed + if (processedEntities.contains(entity)) continue; // Skip if already processed const auto& sceneTag = Application::m_coordinator->getComponent(entity); if (auto it = scenes.find(sceneTag.id); it != scenes.end()) { SceneObject entityNode = createEntityNode(it->second.data.sceneProperties.sceneId, - it->second.data.sceneProperties.windowId, - entity); + it->second.data.sceneProperties.windowId, entity); processedEntities.insert(entity); buildChildNodesForEntity(entity, entityNode, processedEntities); it->second.children.push_back(entityNode); @@ -67,24 +60,18 @@ namespace nexo::editor { } } - void SceneTreeWindow::buildChildNodesForEntity( - const ecs::Entity parentEntity, - SceneObject& parentNode, - std::unordered_set& processedEntities) + void SceneTreeWindow::buildChildNodesForEntity(const ecs::Entity parentEntity, SceneObject& parentNode, + std::unordered_set& processedEntities) { const auto& transform = Application::m_coordinator->getComponent(parentEntity); for (const auto childEntity : transform.children) { - if (childEntity == ecs::INVALID_ENTITY) - continue; + if (childEntity == ecs::INVALID_ENTITY) continue; // Skip if already processed - if (processedEntities.contains(childEntity)) - continue; + if (processedEntities.contains(childEntity)) continue; - SceneObject childNode = createEntityNode( - parentNode.data.sceneProperties.sceneId, - parentNode.data.sceneProperties.windowId, - childEntity); + SceneObject childNode = createEntityNode(parentNode.data.sceneProperties.sceneId, + parentNode.data.sceneProperties.windowId, childEntity); processedEntities.insert(childEntity); buildChildNodesForEntity(childEntity, childNode, processedEntities); @@ -92,7 +79,8 @@ namespace nexo::editor { } } - SceneObject SceneTreeWindow::createEntityNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) + SceneObject SceneTreeWindow::createEntityNode(const scene::SceneId sceneId, const WindowId uiId, + const ecs::Entity entity) { const SceneProperties scene{sceneId, uiId}; const EntityProperties data{scene, entity}; @@ -123,7 +111,7 @@ namespace nexo::editor { std::string uuid; if (Application::m_coordinator->entityHasComponent(entity)) { const auto& uuidComponent = Application::m_coordinator->getComponent(entity); - uuid = uuidComponent.uuid; + uuid = uuidComponent.uuid; } // Create UI name with appropriate icon @@ -142,63 +130,55 @@ namespace nexo::editor { return node; } - void SceneTreeWindow::update() { - root_.uiName = "Scene Tree"; + root_.uiName = "Scene Tree"; root_.data.entity = ecs::INVALID_ENTITY; - root_.type = SelectionType::NONE; + root_.type = SelectionType::NONE; root_.children.clear(); m_nbPointLights = 0; - m_nbDirLights = 0; - m_nbSpotLights = 0; + m_nbDirLights = 0; + m_nbSpotLights = 0; if (m_resetExpandState) { - m_forceExpandAll = false; + m_forceExpandAll = false; m_forceCollapseAll = false; m_resetExpandState = false; } // Retrieves the scenes that are displayed on the GUI - const auto &scenes = m_windowRegistry.getWindows(); + const auto& scenes = m_windowRegistry.getWindows(); std::map sceneNodes; - for (const auto &scene : scenes) - { + for (const auto& scene : scenes) { sceneNodes[scene->getSceneId()] = newSceneNode(scene->getWindowName(), scene->getSceneId(), windowId); } generateNodes( - sceneNodes, - [](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + sceneNodes, [](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return newAmbientLightNode(sceneId, uiId, entity); }); generateNodes( - sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return newDirectionalLightNode(sceneId, uiId, entity); }); generateNodes( - sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return newPointLightNode(sceneId, uiId, entity); }); generateNodes( - sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return newSpotLightNode(sceneId, uiId, entity); }); generateNodes>( - sceneNodes, - [](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + sceneNodes, [](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return newCameraNode(sceneId, uiId, entity); }); generateHierarchicalNodes(sceneNodes); - for (const auto &sceneNode: sceneNodes | std::views::values) - { + for (const auto& sceneNode : sceneNodes | std::views::values) { root_.children.push_back(sceneNode); } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/TestWindow/Init.cpp b/editor/src/DocumentWindows/TestWindow/Init.cpp index a6e0f0a17..1f729f7bc 100644 --- a/editor/src/DocumentWindows/TestWindow/Init.cpp +++ b/editor/src/DocumentWindows/TestWindow/Init.cpp @@ -21,4 +21,4 @@ namespace nexo::editor { parseTestFolder(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/TestWindow/Parser.cpp b/editor/src/DocumentWindows/TestWindow/Parser.cpp index 2b6fe8ac1..a862ed9a9 100644 --- a/editor/src/DocumentWindows/TestWindow/Parser.cpp +++ b/editor/src/DocumentWindows/TestWindow/Parser.cpp @@ -14,11 +14,11 @@ #include "Error.hpp" #include "Exception.hpp" -#include "TestWindow.hpp" -#include "utils/String.hpp" -#include "Path.hpp" #include "Logger.hpp" +#include "Path.hpp" +#include "TestWindow.hpp" #include "exceptions/Exceptions.hpp" +#include "utils/String.hpp" #include #include @@ -26,21 +26,30 @@ namespace nexo::editor { + /** + * @brief Parses a bullet point line to extract the test case name. + * + * This function checks if the provided line starts with a bullet point + * indicator (a hyphen followed by a space) and extracts the text following + * it as the test case name. If the line does not conform to this format, + * an empty string is returned. + * + * @param line The line of text to parse. + * @return The extracted test case name, or an empty string if the format is invalid. + */ static std::string parseBullet(const std::string &line) { - if (line.size() >= 2 && line[0] == '-' && std::isspace(line[1])) - return line.substr(2); + if (line.size() >= 2 && line[0] == '-' && std::isspace(line[1])) return line.substr(2); return {}; } void TestWindow::parseFile(const std::filesystem::directory_entry &entry) { std::ifstream in(entry.path()); - if (!in) - THROW_EXCEPTION(FileReadException, entry.path().string(), nexo::strerror(errno)); + if (!in) THROW_EXCEPTION(FileReadException, entry.path().string(), nexo::strerror(errno)); - TestSection* currentSection = nullptr; - TestSection* currentSubSection = nullptr; + TestSection *currentSection = nullptr; + TestSection *currentSubSection = nullptr; std::string line; while (std::getline(in, line)) { @@ -48,21 +57,23 @@ namespace nexo::editor { // Top-level section if (line.rfind("# ", 0) == 0) { m_testSections.emplace_back(); - currentSection = &m_testSections.back(); + currentSection = &m_testSections.back(); currentSection->name = line.substr(2); - currentSubSection = nullptr; - // Sub-section + currentSubSection = nullptr; + // Subsection } else if (line.rfind("## ", 0) == 0) { if (!currentSection) - THROW_EXCEPTION(InvalidTestFileFormat, entry.path().string(), "Subsection found without main section"); + THROW_EXCEPTION(InvalidTestFileFormat, entry.path().string(), + "Subsection found without main section"); currentSection->subSections.emplace_back(); - currentSubSection = ¤tSection->subSections.back(); + currentSubSection = ¤tSection->subSections.back(); currentSubSection->name = line.substr(3); - // Test case + // Test case } else if (line.rfind('-', 0) == 0) { std::string testName = parseBullet(line); if (testName.empty()) - THROW_EXCEPTION(InvalidTestFileFormat, entry.path().string(), "Test case format is invalid : \"- Test case name \""); + THROW_EXCEPTION(InvalidTestFileFormat, entry.path().string(), + "Test case format is invalid : \"- Test case name \""); TestCase testCase; testCase.name = std::move(testName); @@ -77,8 +88,7 @@ namespace nexo::editor { void TestWindow::parseTestFolder() { - const std::filesystem::path testDir = Path::resolvePathRelativeToExe( - "../tests/editor"); + const std::filesystem::path testDir = Path::resolvePathRelativeToExe("../tests/editor"); for (const auto &entry : std::filesystem::directory_iterator(testDir)) { if (!entry.is_regular_file()) { @@ -97,4 +107,4 @@ namespace nexo::editor { } } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/TestWindow/Show.cpp b/editor/src/DocumentWindows/TestWindow/Show.cpp index 82a1eddef..e021733b6 100644 --- a/editor/src/DocumentWindows/TestWindow/Show.cpp +++ b/editor/src/DocumentWindows/TestWindow/Show.cpp @@ -17,6 +17,16 @@ namespace nexo::editor { + /** + * @brief Renders radio buttons for selecting a test result. + * + * This function displays three radio buttons labeled "Passed", "Failed", + * and "Skipped". The currently selected test result is highlighted, and + * clicking on a button updates the provided TestResult reference to the + * corresponding value. + * + * @param result A reference to the TestResult variable to be updated based on user selection. + */ static void renderRadioButtons(TestResult &result) { if (ImGui::RadioButton("Passed", result == TestResult::PASSED)) @@ -29,6 +39,15 @@ namespace nexo::editor { result = TestResult::SKIPPED; } + /** + * @brief Renders the test cases within a given test section. + * + * This function creates a table layout to display each test case's name + * and its associated result. For test cases marked as "Skipped", an input + * field is provided to enter the reason for skipping the test. + * + * @param section The TestSection containing the test cases to be rendered. + */ static void renderTestCases(TestSection §ion) { if (ImGui::BeginTable("TestCasesTable", 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_BordersInnerV)) @@ -71,6 +90,15 @@ namespace nexo::editor { } } + /** + * @brief Renders the subsections within a test section. + * + * This function iterates through each subsection, creating a toggleable + * header for each. If a subsection is expanded, its test cases are rendered + * using the renderTestCases function. + * + * @param subSections A vector of TestSection objects representing the subsections to be rendered. + */ static void renderSubSections(std::vector &subSections) { for (unsigned int i = 0; i < subSections.size(); ++i) { @@ -98,7 +126,7 @@ namespace nexo::editor { // Any test cases directly in this section renderTestCases(section); - // Now sub-sections + // Now subsections renderSubSections(section.subSections); ImGui::TreePop(); } diff --git a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp index 4710107ab..7e1e260b5 100644 --- a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp +++ b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp @@ -12,21 +12,21 @@ // /////////////////////////////////////////////////////////////////////////////// +#include "Error.hpp" #include "Exception.hpp" -#include "TestWindow.hpp" #include "Path.hpp" -#include "Error.hpp" +#include "TestWindow.hpp" #include "exceptions/Exceptions.hpp" #include #include #ifdef __linux__ - #include + #include #endif #ifdef NX_GRAPHICS_API_OPENGL - #include + #include #endif namespace nexo::editor { @@ -56,8 +56,7 @@ namespace nexo::editor { return "macOS"; #elif defined(__linux__) utsname info{}; - if (uname(&info) == 0) - return std::string(info.sysname) + " " + info.release; + if (uname(&info) == 0) return std::string(info.sysname) + " " + info.release; return "Linux"; #else return "Unknown OS"; @@ -76,10 +75,8 @@ namespace nexo::editor { if (pos != std::string::npos) { std::string model = line.substr(pos + 1); // trim leading spaces - model.erase( - model.begin(), - std::ranges::find_if(model, [](const unsigned char ch) { return !std::isspace(ch); }) - ); + model.erase(model.begin(), + std::ranges::find_if(model, [](const unsigned char ch) { return !std::isspace(ch); })); return model; } } @@ -87,18 +84,18 @@ namespace nexo::editor { return "Unknown CPU"; #elif defined(_WIN32) && defined(_M_X64) // Using __cpuid to get CPU brand string - int cpuInfo[4] = {0}; - char brand[0x40] = { 0 }; + int cpuInfo[4] = {0}; + char brand[0x40] = {0}; __cpuid(cpuInfo, 0x80000000); unsigned int maxId = cpuInfo[0]; if (maxId >= 0x80000004) { - __cpuid((int*)cpuInfo, 0x80000002); + __cpuid((int *)cpuInfo, 0x80000002); memcpy(brand, cpuInfo, sizeof(cpuInfo)); - __cpuid((int*)cpuInfo, 0x80000003); + __cpuid((int *)cpuInfo, 0x80000003); memcpy(brand + 16, cpuInfo, sizeof(cpuInfo)); - __cpuid((int*)cpuInfo, 0x80000004); + __cpuid((int *)cpuInfo, 0x80000004); memcpy(brand + 32, cpuInfo, sizeof(cpuInfo)); - return std::string(brand); + return {brand}; } return "Unknown CPU"; #else @@ -110,9 +107,9 @@ namespace nexo::editor { static std::string getGraphicsInfo() { #ifdef NX_GRAPHICS_API_OPENGL - const auto vendor = reinterpret_cast(glGetString(GL_VENDOR)); - const auto renderer = reinterpret_cast(glGetString(GL_RENDERER)); - const auto version = reinterpret_cast(glGetString(GL_VERSION)); + const auto vendor = reinterpret_cast(glGetString(GL_VENDOR)); + const auto renderer = reinterpret_cast(glGetString(GL_RENDERER)); + const auto version = reinterpret_cast(glGetString(GL_VERSION)); return std::string("OpenGL: ") + vendor + " - " + renderer + " (" + version + ")"; #else return "Graphics info not available"; @@ -131,48 +128,44 @@ namespace nexo::editor { static constexpr std::string testResultToString(const TestResult r) { - switch (r) - { - case TestResult::PASSED: return "PASSED"; - case TestResult::FAILED: return "FAILED"; - case TestResult::SKIPPED: return "SKIPPED"; - default: return "NOT_TESTED"; + switch (r) { + case TestResult::PASSED: + return "PASSED"; + case TestResult::FAILED: + return "FAILED"; + case TestResult::SKIPPED: + return "SKIPPED"; + default: + return "NOT_TESTED"; } } - static void writeTestCaseReport(std::ofstream& out, const TestCase& tc) + static void writeTestCaseReport(std::ofstream &out, const TestCase &tc) { out << std::format("- {} : {}\n", tc.name, testResultToString(tc.result)); - if (tc.result == TestResult::SKIPPED) - out << std::format(" Reason: {}\n", tc.skippedMessage); + if (tc.result == TestResult::SKIPPED) out << std::format(" Reason: {}\n", tc.skippedMessage); } static std::filesystem::path getTestReportFilePath() { const auto now_tp = floor(std::chrono::system_clock::now()); std::chrono::zoned_time local_zoned{std::chrono::current_zone(), now_tp}; - std::string ts = std::format("{:%Y%m%d}", local_zoned); + std::string ts = std::format("{:%Y%m%d}", local_zoned); const std::string filename = std::format("EditorTestResults_{}", ts); - // Tiny file dialog to get path - // patterns - const char *patterns[] = {"*.report"}; - const char *chosenPathStr = tinyfd_saveFileDialog( - "Save Test Report", - filename.c_str(), - 1, - patterns, - "Text files (*.report)" - ); + // Tiny file dialog to get path patterns + constexpr std::array patterns = {"*.report"}; + const char *chosenPathStr = + tinyfd_saveFileDialog("Save Test Report", filename.c_str(), 1, patterns.data(), "Text files (*.report)"); if (!chosenPathStr) { - return std::filesystem::path(); // User cancelled + return {}; // User cancelled } const std::filesystem::path chosenPath = chosenPathStr; std::filesystem::create_directories(chosenPath.parent_path()); return chosenPath; } - void TestWindow::writeTestReport() + void TestWindow::writeTestReport() const { const auto filePath = getTestReportFilePath(); @@ -186,17 +179,14 @@ namespace nexo::editor { THROW_EXCEPTION(FileWriteException, filePath.string(), nexo::strerror(errno)); } - writeEnvironmentReport(out); for (const auto §ion : m_testSections) { out << std::format("# {}\n", section.name); - for (const auto &tc : section.testCases) - writeTestCaseReport(out, tc); + for (const auto &tc : section.testCases) writeTestCaseReport(out, tc); for (const auto &sub : section.subSections) { out << std::format("## {}\n", sub.name); - for (auto &tc : sub.testCases) - writeTestCaseReport(out, tc); + for (auto &tc : sub.testCases) writeTestCaseReport(out, tc); } } } @@ -206,4 +196,4 @@ namespace nexo::editor { writeTestReport(); } -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/TestWindow/TestWindow.hpp b/editor/src/DocumentWindows/TestWindow/TestWindow.hpp index 2299a54c5..862367711 100644 --- a/editor/src/DocumentWindows/TestWindow/TestWindow.hpp +++ b/editor/src/DocumentWindows/TestWindow/TestWindow.hpp @@ -15,17 +15,12 @@ #include "ADocumentWindow.hpp" -#include #include +#include namespace nexo::editor { - enum class TestResult { - NOT_TESTED, - PASSED, - FAILED, - SKIPPED - }; + enum class TestResult { NOT_TESTED, PASSED, FAILED, SKIPPED }; struct TestCase { std::string name; @@ -41,19 +36,96 @@ namespace nexo::editor { }; class TestWindow final : public ADocumentWindow { - public: - using ADocumentWindow::ADocumentWindow; - - void setup() override; - void shutdown() override; - void show() override; - void update() override; - private: - std::vector m_testSections; - - void parseTestFolder(); - void parseFile(const std::filesystem::directory_entry &entry); - void resetTestCases(); - void writeTestReport(); + public: + using ADocumentWindow::ADocumentWindow; + + /** + * @brief Initializes the TestWindow. + * + * This method is called when the TestWindow is first created or opened. + * It is responsible for setting up any necessary resources, loading test + * files, and preparing the UI elements required for displaying and managing + * the test cases. + */ + void setup() override; + + /** + * @brief Shuts down the TestWindow. + * + * This method is called when the TestWindow is being closed or destroyed. + * It is responsible for cleaning up any resources, saving state, or performing + * any necessary finalization tasks related to the TestWindow. + */ + void shutdown() override; + + /** + * @brief Renders the TestWindow UI. + * + * This method is responsible for rendering the entire TestWindow interface, + * including sections, subsections, test cases, and action buttons. It handles + * user interactions such as marking test cases as passed, failed, or skipped, + * and provides options to confirm or cancel the test results. + */ + void show() override; + + /** + * @brief Updates the state of the TestWindow. + * + * This method is called periodically to refresh or update the internal + * state of the TestWindow. It can be used to handle dynamic content, + * respond to user interactions, or perform any necessary background + * processing related to the test cases and sections. + */ + void update() override; + + private: + std::vector m_testSections; + + /** + * @brief Parses all test files in the designated test folder. + * + * This function iterates over all files in the predefined test folder, + * invoking parseFile for each file to extract test sections and cases. + * It populates the m_testSections member variable with the aggregated data. + * + * @throws FileReadException if any file cannot be opened for reading. + * @throws InvalidTestFileFormat if any file format is incorrect. + */ + void parseTestFolder(); + + /** + * @brief Parses a single test file to extract sections and test cases. + * + * This function reads the specified file line by line, identifying + * sections, subsections, and test cases based on markdown-like syntax. + * It populates the m_testSections member variable with the parsed data. + * + * @param entry The directory entry representing the test file to parse. + * @throws FileReadException if the file cannot be opened for reading. + * @throws InvalidTestFileFormat if the file format is incorrect. + */ + void parseFile(const std::filesystem::directory_entry &entry); + + /** + * @brief Resets all test cases to their initial state. + * + * This function iterates through all test sections and their respective + * test cases, resetting each test case's result to NOT_TESTED and + * clearing any skipped messages. It prepares the TestWindow for a fresh + * testing session. + */ + void resetTestCases(); + + /** + * @brief Generates and writes a test report to a user-specified file. + * + * This function compiles the results of all test cases, including their + * statuses (passed, failed, skipped) and any relevant messages. It prompts + * the user to select a file location and writes the formatted report to + * that file. + * + * @throws FileWriteException if the report file cannot be created or written to. + */ + void writeTestReport() const; }; -} +} // namespace nexo::editor diff --git a/editor/src/DocumentWindows/TestWindow/Update.cpp b/editor/src/DocumentWindows/TestWindow/Update.cpp index 276bed1b7..e95b05673 100644 --- a/editor/src/DocumentWindows/TestWindow/Update.cpp +++ b/editor/src/DocumentWindows/TestWindow/Update.cpp @@ -21,4 +21,4 @@ namespace nexo::editor { // Nothing to do in the update } -} +} // namespace nexo::editor diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 9a186178b..9e3b883ff 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -15,21 +15,21 @@ #define IMGUI_DEFINE_MATH_OPERATORS #include "ADocumentWindow.hpp" -#include "utils/Config.hpp" -#include "Nexo.hpp" +#include "DocumentWindows/TestWindow/TestWindow.hpp" #include "Editor.hpp" +#include "IconsFontAwesome.h" +#include "ImNexo/Elements.hpp" #include "Logger.hpp" +#include "Nexo.hpp" #include "Path.hpp" #include "backends/ImGuiBackend.hpp" -#include "IconsFontAwesome.h" -#include "ImNexo/Elements.hpp" #include "context/ActionManager.hpp" -#include "DocumentWindows/TestWindow/TestWindow.hpp" +#include "utils/Config.hpp" -#include -#include "imgui.h" #include #include +#include +#include "imgui.h" #include "DocumentWindows/EditorScene/EditorScene.hpp" #include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" @@ -38,7 +38,7 @@ namespace nexo::editor { void Editor::shutdown() const { - const Application& app = Application::getInstance(); + const Application &app = Application::getInstance(); app.shutdownScripting(); LOG(NEXO_INFO, "Closing editor"); @@ -47,10 +47,10 @@ namespace nexo::editor { ImGuiBackend::shutdown(); } - void Editor::setupEngine() const + void Editor::setupEngine() { auto const &app = Application::getInstance(); - auto &window = app.getWindow(); + auto &window = app.getWindow(); #ifdef __linux__ #ifndef WAYLAND_APP_ID @@ -69,17 +69,16 @@ namespace nexo::editor { ImGui::CreateContext(); ImGuiBackend::init(window); - ImGuiIO &io = ImGui::GetIO(); - static const std::string iniFilePath = Path::resolvePathRelativeToExe( - "../config/default-layout.ini").string(); - io.IniFilename = iniFilePath.c_str(); + ImGuiIO &io = ImGui::GetIO(); + static const std::string iniFilePath = Path::resolvePathRelativeToExe("../config/default-layout.ini").string(); + io.IniFilename = iniFilePath.c_str(); ImGui::StyleColorsDark(); ImGuizmo::SetImGuiContext(ImGui::GetCurrentContext()); ImGuizmo::Enable(true); } - void Editor::setupStyle() const + void Editor::setupStyle() { ImGui::Spectrum::StyleColorsSpectrum(); @@ -88,10 +87,8 @@ namespace nexo::editor { float scaleFactorX = 0.0f; float scaleFactorY = 0.0f; getApp().getWindow()->getDpiScale(&scaleFactorX, &scaleFactorY); - getApp().getWindow()->setWindowIcon(Path::resolvePathRelativeToExe( - "../resources/nexo.png")); - if (scaleFactorX > 1.0f || scaleFactorY > 1.0f) - { + getApp().getWindow()->setWindowIcon(Path::resolvePathRelativeToExe("../resources/nexo.png")); + if (scaleFactorX > 1.0f || scaleFactorY > 1.0f) { LOG(NEXO_WARN, "Scale factor is greater than 1.0, if you have any issue try adjusting the system's scale factor"); LOG(NEXO_INFO, "DPI scale: x: {}, y: {}", scaleFactorX, scaleFactorY); @@ -99,25 +96,25 @@ namespace nexo::editor { LOG(NEXO_INFO, "ImGui version: {}", IMGUI_VERSION); - ImGuiIO &io = ImGui::GetIO(); - io.DisplaySize = ImVec2(static_cast(getApp().getWindow()->getWidth()), - static_cast(getApp().getWindow()->getHeight())); + ImGuiIO &io = ImGui::GetIO(); + io.DisplaySize = ImVec2(static_cast(getApp().getWindow()->getWidth()), + static_cast(getApp().getWindow()->getHeight())); io.DisplayFramebufferScale = ImVec2(scaleFactorX, scaleFactorY); // Apply the DPI scale to ImGui rendering io.ConfigWindowsMoveFromTitleBarOnly = true; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - ImGuiStyle *style = &ImGui::GetStyle(); + ImGuiStyle *style = &ImGui::GetStyle(); style->CircleTessellationMaxError = 0.10f; - style->WindowRounding = 10.0f; - style->ChildRounding = 6.0f; - style->PopupRounding = 4.0f; - style->WindowMenuButtonPosition = ImGuiDir_Right; + style->WindowRounding = 10.0f; + style->ChildRounding = 6.0f; + style->PopupRounding = 4.0f; + style->WindowMenuButtonPosition = ImGuiDir_Right; style->ScaleAllSizes(std::max(scaleFactorX, scaleFactorY)); // Setup NEXO Color Scheme - ImVec4* colors = style->Colors; - constexpr auto colWindowBg = ImVec4(0.02f, 0.02f, 0.04f, 0.59f); // Every color above it will depend on it because of the alpha + ImVec4 *colors = style->Colors; + constexpr auto colWindowBg = + ImVec4(0.02f, 0.02f, 0.04f, 0.59f); // Every color above it will depend on it because of the alpha constexpr auto colTitleBg = ImVec4(0.00f, 0.00f, 0.00f, 0.28f); constexpr auto colTitleBgActive = ImVec4(0.00f, 0.00f, 0.00f, 0.31f); constexpr auto colTabSelectedOverline = ImVec4(0.30f, 0.12f, 0.45f, 0.85f); @@ -132,46 +129,46 @@ namespace nexo::editor { constexpr auto colTabHovered = ImVec4(0.33f, 0.25f, 0.40f, colWindowBg.w - colTitleBg.w); // Depending definitions - colors[ImGuiCol_WindowBg] = colWindowBg; - colors[ImGuiCol_TitleBg] = colTitleBg; - colors[ImGuiCol_TitleBgActive] = colTitleBgActive; - colors[ImGuiCol_TitleBgCollapsed] = colTitleBg; - colors[ImGuiCol_Tab] = colTab; - colors[ImGuiCol_TabSelected] = colTabSelected; - colors[ImGuiCol_TabDimmed] = colTabDimmed; - colors[ImGuiCol_TabDimmedSelected] = colTabDimmedSelected; - colors[ImGuiCol_TabSelectedOverline] = colTabSelectedOverline; - colors[ImGuiCol_TabDimmedSelectedOverline] = colTabDimmedSelectedOverline; - colors[ImGuiCol_TabHovered] = colTabHovered; + colors[ImGuiCol_WindowBg] = colWindowBg; + colors[ImGuiCol_TitleBg] = colTitleBg; + colors[ImGuiCol_TitleBgActive] = colTitleBgActive; + colors[ImGuiCol_TitleBgCollapsed] = colTitleBg; + colors[ImGuiCol_Tab] = colTab; + colors[ImGuiCol_TabSelected] = colTabSelected; + colors[ImGuiCol_TabDimmed] = colTabDimmed; + colors[ImGuiCol_TabDimmedSelected] = colTabDimmedSelected; + colors[ImGuiCol_TabSelectedOverline] = colTabSelectedOverline; + colors[ImGuiCol_TabDimmedSelectedOverline] = colTabDimmedSelectedOverline; + colors[ImGuiCol_TabHovered] = colTabHovered; // Static definitions const ImVec4 whiteText = colors[ImGuiCol_Text]; - colors[ImGuiCol_Border] = ImVec4(0.08f, 0.08f, 0.25f, 0.19f); - colors[ImGuiCol_TableRowBg] = ImVec4(0.49f, 0.63f, 0.71f, 0.15f); - colors[ImGuiCol_FrameBg] = ImVec4(0.49f, 0.63f, 0.71f, 0.15f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.59f, 0.73f, 0.81f, 0.15f); - colors[ImGuiCol_MenuBarBg] = ImVec4(0.58f, 0.14f, 0.14f, 0.10f); - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.34f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 0.69f); - colors[ImGuiCol_TextTab] = whiteText; - colors[ImGuiCol_TextTabDimmed] = whiteText; - colors[ImGuiCol_TextTabHovered] = whiteText; - colors[ImGuiCol_TextTabSelected] = whiteText; - colors[ImGuiCol_TextTabDimmedSelected] = whiteText; - colors[ImGuiCol_Header] = ImVec4(0.49f, 0.63f, 0.71f, 0.15f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.49f, 0.63f, 0.71f, 0.30f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.49f, 0.63f, 0.71f, 0.45f); - colors[ImGuiCol_Button] = ImVec4(0.49f, 0.63f, 0.71f, 0.15f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.49f, 0.63f, 0.71f, 0.30f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.49f, 0.63f, 0.71f, 0.45f); - colors[ImGuiCol_PopupBg] = ImVec4(0.05f * 1.5f, 0.09f * 1.15f, 0.13f * 1.25, 1.0f); + colors[ImGuiCol_Border] = ImVec4(0.08f, 0.08f, 0.25f, 0.19f); + colors[ImGuiCol_TableRowBg] = ImVec4(0.49f, 0.63f, 0.71f, 0.15f); + colors[ImGuiCol_FrameBg] = ImVec4(0.49f, 0.63f, 0.71f, 0.15f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.59f, 0.73f, 0.81f, 0.15f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.58f, 0.14f, 0.14f, 0.10f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.34f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 0.69f); + colors[ImGuiCol_TextTab] = whiteText; + colors[ImGuiCol_TextTabDimmed] = whiteText; + colors[ImGuiCol_TextTabHovered] = whiteText; + colors[ImGuiCol_TextTabSelected] = whiteText; + colors[ImGuiCol_TextTabDimmedSelected] = whiteText; + colors[ImGuiCol_Header] = ImVec4(0.49f, 0.63f, 0.71f, 0.15f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.49f, 0.63f, 0.71f, 0.30f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.49f, 0.63f, 0.71f, 0.45f); + colors[ImGuiCol_Button] = ImVec4(0.49f, 0.63f, 0.71f, 0.15f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.49f, 0.63f, 0.71f, 0.30f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.49f, 0.63f, 0.71f, 0.45f); + colors[ImGuiCol_PopupBg] = ImVec4(0.05f * 1.5f, 0.09f * 1.15f, 0.13f * 1.25, 1.0f); // Optionally, you might want to adjust the text color if needed: setupFonts(scaleFactorX, scaleFactorY); } - void Editor::setupFonts(const float scaleFactorX, const float scaleFactorY) const + void Editor::setupFonts(const float scaleFactorX, const float scaleFactorY) { ImFontConfig fontConfig; fontConfig.OversampleH = 3; // Horizontal oversampling @@ -182,17 +179,15 @@ namespace nexo::editor { io.Fonts->AddFontDefault(); float fontSize = 18.0f; - if (scaleFactorX > 1.0f || scaleFactorY > 1.0f) - { + if (scaleFactorX > 1.0f || scaleFactorY > 1.0f) { fontSize = std::ceil(fontSize * std::max(scaleFactorX, scaleFactorY)); LOG(NEXO_WARN, "Font size adjusted to {}", fontSize); } const float iconFontSize = fontSize * 2.0f / 3.0f; - static const std::string sourceSansPath = Path::resolvePathRelativeToExe( - "../resources/fonts/SourceSans3-Regular.ttf").string(); - ImFont *font = io.Fonts->AddFontFromFileTTF(sourceSansPath.c_str(), fontSize, - &fontConfig); + static const std::string sourceSansPath = + Path::resolvePathRelativeToExe("../resources/fonts/SourceSans3-Regular.ttf").string(); + ImFont *font = io.Fonts->AddFontFromFileTTF(sourceSansPath.c_str(), fontSize, &fontConfig); LOG(NEXO_DEBUG, "Font path: {}", sourceSansPath); IM_ASSERT(font != nullptr); io.FontDefault = font; @@ -200,22 +195,20 @@ namespace nexo::editor { ImGuiBackend::initFontAtlas(); ImFontConfig fontawesome_config; - fontawesome_config.MergeMode = true; // Merge fontawesome with the default font - fontawesome_config.OversampleH = 3; // Horizontal oversampling - fontawesome_config.OversampleV = 3; // Vertical oversampling - //fontawesome_config.GlyphMinAdvanceX = 7.0f; // Use if you want to make the icon monospaced - //fontawesome_config.GlyphMaxAdvanceX = 7.0f; // Use if you want to make the icon monospaced + fontawesome_config.MergeMode = true; // Merge fontawesome with the default font + fontawesome_config.OversampleH = 3; // Horizontal oversampling + fontawesome_config.OversampleV = 3; // Vertical oversampling + // fontawesome_config.GlyphMinAdvanceX = 7.0f; // Use if you want to make the icon monospaced + // fontawesome_config.GlyphMaxAdvanceX = 7.0f; // Use if you want to make the icon monospaced fontawesome_config.PixelSnapH = true; // Snap to pixel grid, useful for pixel-perfect rendering - //fontawesome_config.GlyphExtraSpacing = ImVec2(0.0f, 0.0f); // Adds space between icon and text + // fontawesome_config.GlyphExtraSpacing = ImVec2(0.0f, 0.0f); // Adds space between icon and text fontawesome_config.GlyphMinAdvanceX = iconFontSize; // Make the icons monospaced and aligned fontawesome_config.GlyphMaxAdvanceX = iconFontSize; // Make the icons monospaced and aligned - - - static constexpr ImWchar icon_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; - static const std::string fontawesomePath = Path::resolvePathRelativeToExe( - "../resources/fonts/fontawesome4.ttf").string(); + static constexpr ImWchar icon_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0}; + static const std::string fontawesomePath = + Path::resolvePathRelativeToExe("../resources/fonts/fontawesome4.ttf").string(); io.Fonts->AddFontFromFileTTF(fontawesomePath.c_str(), iconFontSize, &fontawesome_config, icon_ranges); LOG(NEXO_DEBUG, "Fonts initialized"); @@ -223,14 +216,16 @@ namespace nexo::editor { void Editor::init() const { - setupEngine(); - setupStyle(); - m_windowRegistry.setup(); + setupEngine(); + setupStyle(); + m_windowRegistry.setup(); - const Application& app = Application::getInstance(); - app.initScripting(); // TODO: scripting is init here since it requires a scene, later scenes shouldn't be created in the editor window + const Application &app = Application::getInstance(); + app.initScripting(); // TODO: scripting is init here since it requires a scene, later scenes shouldn't be + // created in the editor window for (const auto inspectorWindow : m_windowRegistry.getWindows()) - inspectorWindow->registerTypeErasedProperties(); // TODO: this should be done in the InspectorWindow constructor, but we need the scripting to init + inspectorWindow->registerTypeErasedProperties(); // TODO: this should be done in the InspectorWindow + // constructor, but we need the scripting to init } bool Editor::isOpen() const @@ -240,12 +235,9 @@ namespace nexo::editor { void Editor::drawMenuBar() { - if (ImGui::BeginMainMenuBar()) - { - if (ImGui::BeginMenu("File")) - { - if (ImGui::MenuItem("Exit")) - m_quit = true; + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Exit")) m_quit = true; ImGui::EndMenu(); } @@ -255,8 +247,8 @@ namespace nexo::editor { void Editor::buildDockspace() { - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - const ImGuiID dockspaceID = viewport->ID; + const ImGuiViewport *viewport = ImGui::GetMainViewport(); + const ImGuiID dockspaceID = viewport->ID; static bool dockingRegistryFilled = false; // If the dockspace node doesn't exist yet, create it @@ -298,7 +290,8 @@ namespace nexo::editor { // ───────────────────────────────────────────── // Dock the windows into their corresponding nodes. - const std::string defaultSceneUniqueStrId = std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, 0); // for the default scene + const std::string defaultSceneUniqueStrId = + std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, 0); // for the default scene ImGui::DockBuilderDockWindow(defaultSceneUniqueStrId.c_str(), mainSceneTop); ImGui::DockBuilderDockWindow(NEXO_WND_USTRID_CONSOLE, consoleNode); ImGui::DockBuilderDockWindow(NEXO_WND_USTRID_SCENE_TREE, sceneTreeNode); @@ -316,10 +309,9 @@ namespace nexo::editor { // Finish building the dock layout. ImGui::DockBuilderFinish(dockspaceID); - } - else if (!dockingRegistryFilled) { + } else if (!dockingRegistryFilled) { setAllWindowDockIDsFromConfig(m_windowRegistry); - dockingRegistryFilled = true; + dockingRegistryFilled = true; } // Render the dockspace @@ -328,19 +320,15 @@ namespace nexo::editor { void Editor::handleGlobalCommands() { - if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyPressed(ImGuiKey_Z)) - { - if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) - { + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyPressed(ImGuiKey_Z)) { + if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { ActionManager::get().redo(); - } - else - { + } else { ActionManager::get().undo(); } } - if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyDown(ImGuiKey_LeftShift) && ImGui::IsKeyPressed(ImGuiKey_T)) - { + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyDown(ImGuiKey_LeftShift) && + ImGui::IsKeyPressed(ImGuiKey_T)) { if (const auto testWindow = getWindow(NEXO_WND_USTRID_TEST).lock()) { testWindow->setOpened(true); } else { @@ -350,41 +338,33 @@ namespace nexo::editor { } } - std::vector Editor::handleFocusedWindowCommands() + std::vector Editor::handleFocusedWindowCommands() const { std::vector possibleCommands; static std::vector lastValidCommands; // Store the last valid set of commands static float commandDisplayTimer = 0.0f; // Track how long to show last commands auto focusedWindow = m_windowRegistry.getFocusedWindow(); - if (focusedWindow) - { + if (focusedWindow) { const WindowState currentState = m_windowRegistry.getFocusedWindow()->getWindowState(); - m_inputManager.processInputs(currentState); - possibleCommands = m_inputManager.getPossibleCommands(currentState); + nexo::editor::InputManager::processInputs(currentState); + possibleCommands = nexo::editor::InputManager::getPossibleCommands(currentState); // Update the last valid commands if we have any - if (!possibleCommands.empty()) - { + if (!possibleCommands.empty()) { constexpr float commandPersistTime = 2.0f; - lastValidCommands = possibleCommands; - commandDisplayTimer = commandPersistTime; // Reset timer - } - else if (commandDisplayTimer > 0.0f) - { + lastValidCommands = possibleCommands; + commandDisplayTimer = commandPersistTime; // Reset timer + } else if (commandDisplayTimer > 0.0f) { // Use the last valid commands if timer is still active possibleCommands = lastValidCommands; commandDisplayTimer -= ImGui::GetIO().DeltaTime; - } - else if (lastValidCommands.empty()) - { + } else if (lastValidCommands.empty()) { // Fallback: If we've never had commands, grab all possible commands from the window // This is a more complex operation but ensures we always have something to show - possibleCommands = m_inputManager.getAllPossibleCommands(currentState); + possibleCommands = nexo::editor::InputManager::getAllPossibleCommands(currentState); lastValidCommands = possibleCommands; - } - else - { + } else { // Use the last valid set of commands possibleCommands = lastValidCommands; } @@ -392,7 +372,7 @@ namespace nexo::editor { return possibleCommands; } - void Editor::drawShortcutBar(const std::vector &possibleCommands) const + void Editor::drawShortcutBar(const std::vector &possibleCommands) { constexpr float bottomBarHeight = 38.0f; ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.05f, 0.06f, 0.12f, 0.85f)); // Matches your dark blue theme @@ -404,24 +384,17 @@ namespace nexo::editor { ImGui::SetNextWindowViewport(viewport->ID); ImGuiWindowFlags bottomBarFlags = - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoDocking | - ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_NoBackground; - - if (ImGui::Begin(NEXO_WND_USTRID_BOTTOM_BAR, nullptr, bottomBarFlags)) - { + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground; + + if (ImGui::Begin(NEXO_WND_USTRID_BOTTOM_BAR, nullptr, bottomBarFlags)) { constexpr float textScaleFactor = 0.90f; // 15% larger text ImGui::SetWindowFontScale(textScaleFactor); // Vertically center the content const float windowHeight = ImGui::GetWindowHeight(); - const float textHeight = ImGui::GetTextLineHeight(); - const float paddingY = (windowHeight - textHeight) * 0.5f; + const float textHeight = ImGui::GetTextLineHeight(); + const float paddingY = (windowHeight - textHeight) * 0.5f; // Apply the vertical padding ImGui::SetCursorPosY(paddingY); @@ -429,51 +402,42 @@ namespace nexo::editor { // Start with a small horizontal padding ImGui::SetCursorPosX(10.0f); - if (!possibleCommands.empty()) - { - ImDrawList* drawList = ImGui::GetWindowDrawList(); + if (!possibleCommands.empty()) { + ImDrawList *drawList = ImGui::GetWindowDrawList(); // Use horizontal layout for commands, left-aligned - for (const auto& cmd : possibleCommands) - { + for (const auto &cmd : possibleCommands) { // Calculate text sizes for proper positioning and border sizing - ImVec2 keySize = ImGui::CalcTextSize(cmd.key.c_str()); + ImVec2 keySize = ImGui::CalcTextSize(cmd.key.c_str()); ImVec2 colonSize = ImGui::CalcTextSize(":"); - ImVec2 descSize = ImGui::CalcTextSize(cmd.description.c_str()); + ImVec2 descSize = ImGui::CalcTextSize(cmd.description.c_str()); // Position of the start of this command const ImVec2 commandStart = ImGui::GetCursorScreenPos(); // Total size of command group with padding - const float commandWidth = keySize.x + colonSize.x + 5.0f + descSize.x; + const float commandWidth = keySize.x + colonSize.x + 5.0f + descSize.x; const float commandHeight = std::max(keySize.y, std::max(colonSize.y, descSize.y)); // Add padding around the entire command - constexpr float borderPadding = 6.0f; + constexpr float borderPadding = 6.0f; constexpr float borderCornerRadius = 3.0f; // Draw the gradient border rectangle const auto rectMin = ImVec2(commandStart.x - borderPadding, commandStart.y - borderPadding); const auto rectMax = ImVec2(commandStart.x + commandWidth + borderPadding, - commandStart.y + commandHeight + borderPadding); + commandStart.y + commandHeight + borderPadding); // Draw gradient border rectangle - drawList->AddRect( - rectMin, - rectMax, - IM_COL32(58, 124, 161, 200), // Gradient start color - borderCornerRadius, - 0, - 1.5f // Border thickness + drawList->AddRect(rectMin, rectMax, IM_COL32(58, 124, 161, 200), // Gradient start color + borderCornerRadius, 0, + 1.5f // Border thickness ); // Dark inner background - drawList->AddRectFilled( - ImVec2(rectMin.x + 1, rectMin.y + 1), - ImVec2(rectMax.x - 1, rectMax.y - 1), - IM_COL32(10, 11, 25, 200), // Dark inner background - borderCornerRadius - 0.5f - ); + drawList->AddRectFilled(ImVec2(rectMin.x + 1, rectMin.y + 1), ImVec2(rectMax.x - 1, rectMax.y - 1), + IM_COL32(10, 11, 25, 200), // Dark inner background + borderCornerRadius - 0.5f); // Draw the command components const std::string &key = cmd.key + ":"; @@ -493,41 +457,37 @@ namespace nexo::editor { ImGui::PopStyleColor(2); // Pop both text and bg colors } - void Editor::drawBackground() const + void Editor::drawBackground() { const auto viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->Pos); ImGui::SetNextWindowSize(viewport->Size); ImGui::SetNextWindowViewport(viewport->ID); ImGui::Begin("Background", nullptr, - ImGuiWindowFlags_NoDecoration | - ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoBackground); + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoBackground); const std::vector stops = { - { 0.06f, IM_COL32(58, 124, 161, 255) }, - {0.26f, IM_COL32(88, 87, 154, 255) }, - { 0.50f, IM_COL32(88, 87, 154, 255) }, - {0.73f, IM_COL32(58, 124, 161, 255) }, + {0.06f, IM_COL32(58, 124, 161, 255)}, + {0.26f, IM_COL32(88, 87, 154, 255)}, + {0.50f, IM_COL32(88, 87, 154, 255)}, + {0.73f, IM_COL32(58, 124, 161, 255)}, }; constexpr float angle = 148; ImNexo::RectFilledLinearGradient(viewport->Pos, - ImVec2(viewport->Pos.x + viewport->Size.x, viewport->Pos.y + viewport->Size.y), angle, stops); + ImVec2(viewport->Pos.x + viewport->Size.x, viewport->Pos.y + viewport->Size.y), + angle, stops); ImGui::End(); } void Editor::render() { - getApp().beginFrame(); + getApp().beginFrame(); ImGuiBackend::begin(); @@ -552,7 +512,7 @@ namespace nexo::editor { void Editor::update() const { - m_windowRegistry.update(); + m_windowRegistry.update(); getApp().endFrame(); } -} +} // namespace nexo::editor diff --git a/editor/src/Editor.hpp b/editor/src/Editor.hpp index cad5bb768..f224e9cb7 100644 --- a/editor/src/Editor.hpp +++ b/editor/src/Editor.hpp @@ -24,153 +24,214 @@ namespace nexo::editor { class Editor { - private: - // Singleton: private constructor and destructor - Editor() = default; - ~Editor() = default; - - public: - // Singleton: Meyers' Singleton Pattern - static Editor& getInstance() - { - static Editor s_instance; - return s_instance; - } - - // Singleton: delete copy constructor and assignment operator - Editor(Editor const&) = delete; - void operator=(Editor const&) = delete; - - /** - * @brief Initializes the editor. - * - * Configures the application engine, sets up the UI style, and initializes the window registry. - */ - void init() const; - - /** - * @brief Checks if the editor is currently open. - * - * The editor is considered open when it has not been signaled to quit, the application window is open, and the application is still running. - * - * @return true if the editor is open, false otherwise. - */ - [[nodiscard]] bool isOpen() const; - - /** - * @brief Updates the editor's state for the current frame. - * - * This function updates the window registry to process pending window changes and then signals the application to conclude the current frame. - */ - void update() const; - - /** - * @brief Renders the editor's user interface. - * - * This function handles the complete rendering cycle for the editor. It initiates a new frame, configures the ImGui and ImGuizmo contexts, and builds the UI layout including the dockspace and main menu bar. Registered windows are rendered afterwards, and a full-viewport gradient background is drawn before finalizing the frame. - */ - void render(); - - /** - * @brief Shuts down the editor. - * - * This method finalizes the editor shutdown process by logging the closing event, - * destroying all active windows via the window registry, and terminating the ImGui backend. - */ - void shutdown() const; - - /** - * @brief Creates and registers a new window of type T. - * - * This function instantiates a new window of type T using the provided name and the editor's window registry. - * The new window is then registered with the registry so that it can be retrieved later. The type T must be - * derived from IDocumentWindow. - * - * @tparam T The type of the window to create and register. Must inherit from IDocumentWindow. - * @param name The name to assign to the new window. - */ - template + private: + // Singleton: private constructor and destructor + Editor() = default; + ~Editor() = default; + + public: + // Singleton: Meyers' Singleton Pattern + /** + * @brief Retrieves the singleton instance of the Editor class. + * + * This method ensures that only one instance of the Editor class exists + * throughout the application lifecycle. It provides global access to the + * editor instance. + * + * @return Editor& A reference to the singleton Editor instance. + */ + static Editor &getInstance() + { + static Editor s_instance; + return s_instance; + } + + // Singleton: delete copy constructor and assignment operator + /** + * @brief Deleted copy constructor to prevent copying of the Editor instance. + * + * This prevents the creation of multiple instances of the Editor class + * by disallowing copy construction. + */ + Editor(Editor const &) = delete; + void operator=(Editor const &) = delete; + + /** + * @brief Initializes the editor. + * + * Configures the application engine, sets up the UI style, and initializes the window registry. + */ + void init() const; + + /** + * @brief Checks if the editor is currently open. + * + * The editor is considered open when it has not been signaled to quit, the application window is open, and the + * application is still running. + * + * @return true if the editor is open, false otherwise. + */ + [[nodiscard]] bool isOpen() const; + + /** + * @brief Updates the editor's state for the current frame. + * + * This function updates the window registry to process pending window changes and then signals the application + * to conclude the current frame. + */ + void update() const; + + /** + * @brief Renders the editor's user interface. + * + * This function handles the complete rendering cycle for the editor. It initiates a new frame, configures the + * ImGui and ImGuizmo contexts, and builds the UI layout including the docks pace and main menu bar. Registered + * windows are rendered afterward, and a full-viewport gradient background is drawn before finalizing the + * frame. + */ + void render(); + + /** + * @brief Shuts down the editor. + * + * This method finalizes the editor shutdown process by logging the closing event, + * destroying all active windows via the window registry, and terminating the ImGui backend. + */ + void shutdown() const; + + /** + * @brief Creates and registers a new window of type T. + * + * This function instantiates a new window of type T using the provided name and the editor's window registry. + * The new window is then registered with the registry so that it can be retrieved later. The type T must be + * derived from IDocumentWindow. + * + * @tparam T The type of the window to create and register. Must inherit from IDocumentWindow. + * @param name The name to assign to the new window. + */ + template requires std::derived_from - void registerWindow(const std::string &name) - { - if (m_windowRegistry.hasWindow(name)) { - LOG(NEXO_WARN, "A window with the name '{}' already exists. Please choose a different name.", name); - return; - } - auto window = std::make_shared(name, m_windowRegistry); - m_windowRegistry.registerWindow(window); + void registerWindow(const std::string &name) + { + if (m_windowRegistry.hasWindow(name)) { + LOG(NEXO_WARN, "A window with the name '{}' already exists. Please choose a different name.", name); + return; } - - /** - * @brief Retrieves a registered window of type T by its name. - * - * This function returns a weak pointer to a window instance of type T from the editor's window registry. - * The type T must be derived from IDocumentWindow. If no window with the specified name is registered, an empty - * weak pointer is returned. - * - * @tparam T The type of the window to retrieve. Must inherit from IDocumentWindow. - * @param windowName The name of the window to retrieve. - * @return std::weak_ptr A weak pointer to the registered window of type T, or an empty pointer if no such window exists. - */ - template + auto window = std::make_shared(name, m_windowRegistry); + m_windowRegistry.registerWindow(window); + } + + /** + * @brief Retrieves a registered window of type T by its name. + * + * This function returns a weak pointer to a window instance of type T from the editor's window registry. + * The type T must be derived from IDocumentWindow. If no window with the specified name is registered, an empty + * weak pointer is returned. + * + * @tparam T The type of the window to retrieve. Must inherit from IDocumentWindow. + * @param windowName The name of the window to retrieve. + * @return std::weak_ptr A weak pointer to the registered window of type T, or an empty pointer if no such + * window exists. + */ + template requires std::derived_from - std::weak_ptr getWindow(const std::string &windowName) - { - return m_windowRegistry.getWindow(windowName); - } - private: - /** - * @brief Initializes the core engine and configures ImGui components. - * - * This function sets up essential engine features, including: - * - On Linux, configuring the window's Wayland app ID and window manager class if WAYLAND_APP_ID is defined. A warning is issued if the macro is undefined. - * - Initializing the engine core via nexo::init(). - * - Creating and initializing the ImGui context along with its backend error callback. - * - Setting the path for ImGui's default layout configuration. - * - Applying a dark color style and configuring ImGuizmo with the current ImGui context. - */ - void setupEngine() const; - void setupStyle() const; - - /** - * @brief Configures and loads fonts for the ImGui interface. - * - * Initializes the default, SourceSans, and FontAwesome fonts, adjusting the font size based on the - * provided horizontal and vertical DPI scaling factors. Merges FontAwesome icons with the primary font - * and initializes the backend font atlas. - * - * @param scaleFactorX Horizontal DPI scaling factor. - * @param scaleFactorY Vertical DPI scaling factor. - */ - void setupFonts(float scaleFactorX, float scaleFactorY) const; - - /** - * @brief Constructs and configures the editor's dockspace layout. - * - * This method initializes the dockspace on the main viewport using ImGui's DockBuilder API. - * It subdivides the viewport into designated regions for key UI components such as the main scene, - * console, scene tree, inspector, and material inspector. The dock nodes are registered with the - * window registry to maintain a consistent layout. If a dockspace already exists but the registry - * is not yet populated, the dock IDs are retrieved from the configuration. - */ - void buildDockspace(); - - /** - * @brief Draws the main menu bar for the editor. - * - * Constructs the main menu bar UI component which features the "File" menu. Selecting the "Exit" option in the menu - * sets a flag to signal that the editor should quit. - */ - void drawMenuBar(); - void drawShortcutBar(const std::vector &possibleCommands) const; - void drawBackground() const; - - void handleGlobalCommands(); - std::vector handleFocusedWindowCommands(); - - bool m_quit = false; - bool m_showDemoWindow = false; - WindowRegistry m_windowRegistry; - InputManager m_inputManager; + std::weak_ptr getWindow(const std::string &windowName) + { + return m_windowRegistry.getWindow(windowName); + } + + private: + /** + * @brief Initializes the core engine and configures ImGui components. + * + * This function sets up essential engine features, including: + * - On Linux, configuring the window's Wayland app ID and window manager class if WAYLAND_APP_ID is defined. A + * warning is issued if the macro is undefined. + * - Initializing the engine core via nexo::init(). + * - Creating and initializing the ImGui context along with its backend error callback. + * - Setting the path for ImGui's default layout configuration. + * - Applying a dark color style and configuring ImGuizmo with the current ImGui context. + */ + static void setupEngine(); + + /** + * @brief Configures the visual style for the ImGui interface. + * + * This method applies a dark color theme to the ImGui interface and customizes various style parameters + * such as window rounding, frame padding, item spacing, and tab appearance to enhance the overall look + * and feel of the editor. + */ + static void setupStyle(); + + /** + * @brief Configures and loads fonts for the ImGui interface. + * + * Initializes the default, SourceSans, and FontAwesome fonts, adjusting the font size based on the + * provided horizontal and vertical DPI scaling factors. Merges FontAwesome icons with the primary font + * and initializes the backend font atlas. + * + * @param scaleFactorX Horizontal DPI scaling factor. + * @param scaleFactorY Vertical DPI scaling factor. + */ + static void setupFonts(float scaleFactorX, float scaleFactorY) ; + + /** + * @brief Constructs and configures the editor's dockspace layout. + * + * This method initializes the dockspace on the main viewport using ImGui's DockBuilder API. + * It subdivides the viewport into designated regions for key UI components such as the main scene, + * console, scene tree, inspector, and material inspector. The dock nodes are registered with the + * window registry to maintain a consistent layout. If a dockspace already exists but the registry + * is not yet populated, the dock IDs are retrieved from the configuration. + */ + void buildDockspace(); + + /** + * @brief Draws the main menu bar for the editor. + * + * Constructs the main menu bar UI component which features the "File" menu. Selecting the "Exit" option in the + * menu sets a flag to signal that the editor should quit. + */ + void drawMenuBar(); + + /** + * @brief Renders a full-viewport gradient background. + * + * This method draws a visually appealing gradient background that spans the entire viewport of the editor. + * The gradient transitions smoothly between two specified colors, enhancing the overall aesthetic of the UI. + */ + static void drawShortcutBar(const std::vector &possibleCommands) ; + + /** + * @brief Renders a full-viewport gradient background. + * + * This method draws a visually appealing gradient background that spans the entire viewport of the editor. + * The gradient transitions smoothly between two specified colors, enhancing the overall aesthetic of the UI. + */ + static void drawBackground(); + + /** + * @brief Processes global commands and executes associated actions. + * + * This method checks for any global commands that have been triggered, such as quitting the application + * or toggling the demo window. If a command is detected, the corresponding action is executed. + */ + void handleGlobalCommands(); + + /** + * @brief Processes commands for the currently focused window and retrieves relevant command information. + * + * This method checks which window is currently focused in the editor. If a window is focused, it retrieves + * any commands associated with that window and executes them. The function returns a list of command + * information that can be used to display shortcuts or other command-related UI elements. + * + * @return std::vector A vector containing command information for the focused window. + */ + [[nodiscard]] std::vector handleFocusedWindowCommands() const; + + bool m_quit = false; + bool m_showDemoWindow = false; + WindowRegistry m_windowRegistry; + InputManager m_inputManager; }; -} +} // namespace nexo::editor diff --git a/editor/src/IDocumentWindow.hpp b/editor/src/IDocumentWindow.hpp index e88dcec35..b51c9d30f 100644 --- a/editor/src/IDocumentWindow.hpp +++ b/editor/src/IDocumentWindow.hpp @@ -24,20 +24,78 @@ namespace nexo::editor { inline WindowId nextWindowId = 0; class IDocumentWindow { - public: + public: virtual ~IDocumentWindow() = default; + + /** + * @brief Initializes the window, setting up necessary resources and state. + */ virtual void setup() = 0; + + /** + * @brief Shuts down the window, releasing resources and cleaning up state. + */ virtual void shutdown() = 0; + + /** + * @brief Renders the window's content. + * + * This method is responsible for drawing the window's UI elements and handling user interactions. + */ virtual void show() = 0; + + /** + * @brief Updates the window's state. + * + * This method is called to refresh the window's content and handle any necessary updates. + */ virtual void update() = 0; + /** + * @brief Checks if the window is currently focused. + * @return true if the window is focused, false otherwise. + */ [[nodiscard]] virtual bool isFocused() const = 0; + /** + * @brief Checks if the window is currently opened. + * @return true if the window is opened, false otherwise. + */ [[nodiscard]] virtual bool isOpened() const = 0; + + /** + * @brief Sets the opened state of the window. + * @param opened true to open the window, false to close it. + */ virtual void setOpened(bool opened) = 0; + + /** + * @brief Checks if the window is currently hovered by the mouse. + * @return true if the window is hovered, false otherwise. + */ [[nodiscard]] virtual bool isHovered() const = 0; + + /** + * @brief Gets the size of the window's content area. + * @return Reference to the ImVec2 representing the content size. + */ [[nodiscard]] virtual const ImVec2 &getContentSize() const = 0; + + /** + * @brief Gets a reference to the opened state of the window. + * @return Reference to the boolean indicating if the window is opened. + */ [[nodiscard]] virtual bool &getOpened() = 0; + + /** + * @brief Gets the name of the window. + * @return Reference to the string containing the window's name. + */ [[nodiscard]] virtual const std::string &getWindowName() const = 0; + + /** + * @brief Gets the current state of the window. + * @return Reference to the WindowState object representing the window's state. + */ [[nodiscard]] virtual const WindowState &getWindowState() const = 0; }; -} +} // namespace nexo::editor diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp index 6d6fddcab..d7f4de8af 100644 --- a/editor/src/ImNexo/Components.cpp +++ b/editor/src/ImNexo/Components.cpp @@ -8,138 +8,98 @@ // // Author: Mehdy MORVAN // Date: 17/02/2025 -// Description: Source file for the utilitary ImGui functions +// Description: Source file for the utility ImGui functions // /////////////////////////////////////////////////////////////////////////////// #include "Components.hpp" #include "Elements.hpp" #include "Guard.hpp" +#include "ImNexo.hpp" #include "Utils.hpp" #include "tinyfiledialogs.h" -#include "ImNexo.hpp" #include -#include namespace ImNexo { - bool ButtonWithIconAndText( - const std::string &uniqueId, - const std::string &icon, - const std::string &label, - const ImVec2 &itemSize - ) + bool ButtonWithIconAndText(const std::string &uniqueId, const std::string &icon, const std::string &label, + const ImVec2 &itemSize) { IdGuard idGuard(uniqueId); - const std::string invisButtonLabel = "##" + uniqueId; + const std::string invisibleButtonLabel = "##" + uniqueId; - if (ImGui::InvisibleButton(invisButtonLabel.c_str(), itemSize)) - return true; + if (ImGui::InvisibleButton(invisibleButtonLabel.c_str(), itemSize)) return true; // Draw the background auto [p0, p1] = utils::getItemRect(); ImGui::GetWindowDrawList()->AddRectFilled( - p0, p1, - ImGui::GetColorU32(ImGui::IsItemHovered() ? ImGuiCol_ButtonHovered : ImGuiCol_Button), - ImGui::GetStyle().FrameRounding - ); + p0, p1, ImGui::GetColorU32(ImGui::IsItemHovered() ? ImGuiCol_ButtonHovered : ImGuiCol_Button), + ImGui::GetStyle().FrameRounding); // Draw the icon at 25% from top - CenteredIcon( - icon, - p0, p1, - ImGui::GetColorU32(ImGuiCol_Text), - 1.5f, - 0.25f, - 0.5f - ); + CenteredIcon(icon, p0, p1, ImGui::GetColorU32(ImGuiCol_Text), 1.5f, 0.25f, 0.5f); // Draw the label with wrapping if needed - WrappedCenteredText( - label, - p0, p1, - ImGui::GetColorU32(ImGuiCol_Text), - 0.6f // Position at 60% from top + WrappedCenteredText(label, p0, p1, ImGui::GetColorU32(ImGuiCol_Text), + 0.6f // Position at 60% from top ); // Use drawButtonBorder instead of direct drawing - ButtonBorder( - 0, // Use default color - ImGui::GetColorU32(ImGuiCol_ButtonHovered), - ImGui::GetColorU32(ImGuiCol_ButtonActive), - ImGui::GetStyle().FrameRounding - ); + ButtonBorder(0, // Use default color + ImGui::GetColorU32(ImGuiCol_ButtonHovered), ImGui::GetColorU32(ImGuiCol_ButtonActive), + ImGui::GetStyle().FrameRounding); return false; } - void ColorButton(const std::string &label, const ImVec2 size, const ImVec4 color, bool *clicked, ImGuiColorEditFlags flags) + void ColorButton(const std::string &label, const ImVec2 size, const ImVec4 color, bool *clicked, + ImGuiColorEditFlags flags) { flags |= ImGuiColorEditFlags_NoTooltip; constexpr float borderThickness = 3.0f; - const float defaultSize = ImGui::GetFrameHeight() + borderThickness; - const auto calculatedSize = ImVec2( - size.x == 0 ? defaultSize : size.x - borderThickness * 2, - size.y == 0 ? defaultSize : size.y - borderThickness * 2 - ); + const float defaultSize = ImGui::GetFrameHeight() + borderThickness; + const auto calculatedSize = ImVec2(size.x == 0 ? defaultSize : size.x - borderThickness * 2, + size.y == 0 ? defaultSize : size.y - borderThickness * 2); - if (ImGui::ColorButton(label.c_str(), color, flags, calculatedSize) && clicked) - { + if (ImGui::ColorButton(label.c_str(), color, flags, calculatedSize) && clicked) { *clicked = !*clicked; } - ButtonBorder( - ImGui::GetColorU32(ImGuiCol_Button), - ImGui::GetColorU32(ImGuiCol_ButtonHovered), - ImGui::GetColorU32(ImGuiCol_ButtonActive), - borderThickness - ); + ButtonBorder(ImGui::GetColorU32(ImGuiCol_Button), ImGui::GetColorU32(ImGuiCol_ButtonHovered), + ImGui::GetColorU32(ImGuiCol_ButtonActive), borderThickness); } - - bool TextureButton(const std::string &label, const std::shared_ptr& texture, std::filesystem::path& outPath) - { - bool modified = false; - constexpr ImVec2 previewSize(32, 32); + bool TextureButton(const std::string &label, const std::shared_ptr &texture, + std::filesystem::path &outPath) + { + bool modified = false; + constexpr ImVec2 previewSize(32, 32); ImGui::PushID(label.c_str()); const ImTextureID textureId = texture ? static_cast(static_cast(texture->getId())) : 0; const std::string textureButton = std::string("##TextureButton") + label; if (ImageButton(textureButton.c_str(), textureId, previewSize)) { - const char* filePath = tinyfd_openFileDialog( - "Open Texture", - "", - 0, - nullptr, - nullptr, - 0 - ); + const char *filePath = tinyfd_openFileDialog("Open Texture", "", 0, nullptr, nullptr, 0); if (filePath) { - outPath = filePath; + outPath = filePath; modified = true; } } - ButtonBorder(IM_COL32(255,255,255,0), IM_COL32(255,255,255,255), IM_COL32(255,255,255,0), 0.0f, 0, 2.0f); - ImGui::PopID(); - ImGui::SameLine(); - ImGui::Text("%s", label.c_str()); - return modified; - } - - bool IconGradientButton( - const std::string& uniqueId, - const std::string& icon, - const ImVec2& size, - const std::vector& gradientStops, - const float gradientAngle, - const ImU32 borderColor, - const ImU32 borderColorHovered, - const ImU32 borderColorActive, - const ImU32 iconColor - ) + ButtonBorder(IM_COL32(255, 255, 255, 0), IM_COL32(255, 255, 255, 255), IM_COL32(255, 255, 255, 0), 0.0f, 0, + 2.0f); + ImGui::PopID(); + ImGui::SameLine(); + ImGui::Text("%s", label.c_str()); + return modified; + } + + bool IconGradientButton(const std::string &uniqueId, const std::string &icon, const ImVec2 &size, + const std::vector &gradientStops, const float gradientAngle, + const ImU32 borderColor, const ImU32 borderColorHovered, const ImU32 borderColorActive, + const ImU32 iconColor) { IdGuard idGuard(uniqueId); @@ -150,20 +110,17 @@ namespace ImNexo { auto [p_min, p_max] = utils::getItemRect(); // Draw the gradient background - ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImDrawList *drawList = ImGui::GetWindowDrawList(); RectFilledLinearGradient(p_min, p_max, gradientAngle, gradientStops, drawList); // Draw the icon centered using our helper function CenteredIcon(icon, p_min, p_max, iconColor); // Use our drawButtonBorder helper instead of direct drawing - ButtonBorder( - borderColor, - borderColorHovered, - borderColorActive, - 3.0f, // rounding - 0, // no flags - 1.5f // thickness + ButtonBorder(borderColor, borderColorHovered, borderColorActive, + 3.0f, // rounding + 0, // no flags + 1.5f // thickness ); return clicked; @@ -173,53 +130,33 @@ namespace ImNexo { { bool modified = false; - for (unsigned int i = 0; i < channels.count; ++i) - { + for (unsigned int i = 0; i < channels.count; ++i) { ImGui::TableNextColumn(); // Draw the badge (if provided) - if (!channels.badges[i].label.empty()) - { + if (!channels.badges[i].label.empty()) { const auto &badge = channels.badges[i]; - Button(badge.label, badge.size, badge.bg, badge.bgHovered, - badge.bgActive, badge.txtColor); + Button(badge.label, badge.size, badge.bg, badge.bgHovered, badge.bgActive, badge.txtColor); ImGui::SameLine(0, 2); } // Draw the drag float control const auto &slider = channels.sliders[i]; - const bool changed = DragFloat( - slider.label, - slider.value, - slider.speed, - slider.min, - slider.max, - slider.format, - slider.bg, - slider.bgHovered, - slider.bgActive, - slider.textColor); + const bool changed = + DragFloat(slider.label, slider.value, slider.speed, slider.min, slider.max, slider.format, slider.bg, + slider.bgHovered, slider.bgActive, slider.textColor); modified |= changed; - if (ImGui::IsItemActive()) - setItemActive(); - if (ImGui::IsItemActivated()) - setItemActivated(); - if (ImGui::IsItemDeactivated()) - setItemDeactivated(); + if (ImGui::IsItemActive()) setItemActive(); + if (ImGui::IsItemActivated()) setItemActivated(); + if (ImGui::IsItemDeactivated()) setItemDeactivated(); } return modified; } - bool RowDragFloat1( - const char *uniqueLabel, - const std::string &badgeLabel, - float *value, - float minValue, - float maxValue, - float speed - ) + bool RowDragFloat1(const char *uniqueLabel, const std::string &badgeLabel, float *value, float minValue, + float maxValue, float speed) { ImGui::TableNextRow(); @@ -234,38 +171,21 @@ namespace ImNexo { // Setup single badge and control Channels channels; channels.count = 1; - channels.badges.push_back({ - badgeId, - {0, 0}, - IM_COL32(80, 0, 0, 255), - IM_COL32(80, 0, 0, 255), - IM_COL32(80, 0, 0, 255), - IM_COL32(255, 180, 180, 255) - }); - - channels.sliders.emplace_back(labelId, - value, - speed, - minValue, - maxValue, - 0, 0, 0, 0, - "%.2f"); + channels.badges.push_back({badgeId, + {0, 0}, + IM_COL32(80, 0, 0, 255), + IM_COL32(80, 0, 0, 255), + IM_COL32(80, 0, 0, 255), + IM_COL32(255, 180, 180, 255)}); + + channels.sliders.emplace_back(labelId, value, speed, minValue, maxValue, 0, 0, 0, 0, "%.2f"); return RowDragFloat(channels); } - bool RowDragFloat2( - const char *uniqueLabel, - const std::string &badLabelX, - const std::string &badLabelY, - float *values, - float minValue, - float maxValue, - float speed, - std::vector badgeColor, - std::vector textBadgeColor, - const bool disabled - ) + bool RowDragFloat2(const char *uniqueLabel, const std::string &badLabelX, const std::string &badLabelY, + float *values, const float minValue, const float maxValue, float speed, + std::vector badgeColor, std::vector textBadgeColor, const bool disabled) { ImGui::TableNextRow(); @@ -292,32 +212,32 @@ namespace ImNexo { // Badge labels channels.badges = { {badLabelX + "##" + baseId, {0, 0}, badgeColor[0], badgeColor[0], badgeColor[0], textBadgeColor[0]}, - {badLabelY + "##" + baseId, {0, 0}, badgeColor[1], badgeColor[1], badgeColor[1], textBadgeColor[1]} - }; + {badLabelY + "##" + baseId, {0, 0}, badgeColor[1], badgeColor[1], badgeColor[1], textBadgeColor[1]}}; // Slider colors - ImU32 textColor = disabled ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : ImGui::GetColorU32(ImGuiCol_Text); - ImU32 bgColor = ImGui::GetColorU32(ImGuiCol_FrameBg); - ImU32 bgHoveredColor = ImGui::GetColorU32(ImGuiCol_FrameBgHovered); - ImU32 bgActiveColor = ImGui::GetColorU32(ImGuiCol_FrameBgActive); + const ImU32 textColor = + disabled ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : ImGui::GetColorU32(ImGuiCol_Text); + const ImU32 bgColor = ImGui::GetColorU32(ImGuiCol_FrameBg); + const ImU32 bgHoveredColor = ImGui::GetColorU32(ImGuiCol_FrameBgHovered); + const ImU32 bgActiveColor = ImGui::GetColorU32(ImGuiCol_FrameBgActive); // Slider controls - channels.sliders = { - {"##X" + baseId, &values[0], speed, minValue, maxValue, bgColor, bgHoveredColor, bgActiveColor, textColor, "%.2f"}, - {"##Y" + baseId, &values[1], speed, minValue, maxValue, bgColor, bgHoveredColor, bgActiveColor, textColor, "%.2f"} - }; + channels.sliders = {{"##X" + baseId, &values[0], speed, minValue, maxValue, bgColor, bgHoveredColor, + bgActiveColor, textColor, "%.2f"}, + {"##Y" + baseId, &values[1], speed, minValue, maxValue, bgColor, bgHoveredColor, + bgActiveColor, textColor, "%.2f"}}; return RowDragFloat(channels); } // Creates standard badge colors for X/Y/Z axes if not provided - static void setupAxisBadgeColors(std::vector& badgeColors, std::vector& textBadgeColors) + static void setupAxisBadgeColors(std::vector &badgeColors, std::vector &textBadgeColors) { if (badgeColors.empty()) { badgeColors = { - IM_COL32(102, 28, 28, 255), // X - Red - IM_COL32(0, 80, 0, 255), // Y - Green - IM_COL32(38, 49, 121, 255) // Z - Blue + IM_COL32(102, 28, 28, 255), // X - Red + IM_COL32(0, 80, 0, 255), // Y - Green + IM_COL32(38, 49, 121, 255) // Z - Blue }; } @@ -330,22 +250,13 @@ namespace ImNexo { } } - bool RowDragFloat3( - const char *uniqueLabel, - const std::string &badLabelX, - const std::string &badLabelY, - const std::string &badLabelZ, - float *values, - float minValue, - float maxValue, - float speed, - std::vector badgeColors, - std::vector textBadgeColors - ) + bool RowDragFloat3(const char *uniqueLabel, const std::string &badLabelX, const std::string &badLabelY, + const std::string &badLabelZ, float *values, const float minValue, const float maxValue, + float speed, std::vector badgeColors, std::vector textBadgeColors) { ImGui::TableNextRow(); - // Setup the label for the row + // Set up the label for the row ChannelLabel chanLabel; chanLabel.label = uniqueLabel; @@ -353,45 +264,54 @@ namespace ImNexo { setupAxisBadgeColors(badgeColors, textBadgeColors); // Base ID for controls - std::string baseId = uniqueLabel; - float badgeSize = ImGui::GetFrameHeight(); + const std::string baseId = uniqueLabel; + float badgeSize = ImGui::GetFrameHeight(); // Set up channels structure Channels channels; channels.count = 3; // Badge labels - channels.badges = { - {badLabelX + "##" + baseId, {badgeSize, badgeSize}, badgeColors[0], badgeColors[0], badgeColors[0], textBadgeColors[0]}, - {badLabelY + "##" + baseId, {badgeSize, badgeSize}, badgeColors[1], badgeColors[1], badgeColors[1], textBadgeColors[1]}, - {badLabelZ + "##" + baseId, {badgeSize, badgeSize}, badgeColors[2], badgeColors[2], badgeColors[2], textBadgeColors[2]} - }; - - ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text); + channels.badges = {{badLabelX + "##" + baseId, + {badgeSize, badgeSize}, + badgeColors[0], + badgeColors[0], + badgeColors[0], + textBadgeColors[0]}, + {badLabelY + "##" + baseId, + {badgeSize, badgeSize}, + badgeColors[1], + badgeColors[1], + badgeColors[1], + textBadgeColors[1]}, + {badLabelZ + "##" + baseId, + {badgeSize, badgeSize}, + badgeColors[2], + badgeColors[2], + badgeColors[2], + textBadgeColors[2]}}; + + const ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text); // Slider controls - channels.sliders = { - {"##X" + baseId, &values[0], speed, minValue, maxValue, 0, 0, 0, textColor, "%.2f"}, - {"##Y" + baseId, &values[1], speed, minValue, maxValue, 0, 0, 0, textColor, "%.2f"}, - {"##Z" + baseId, &values[2], speed, minValue, maxValue, 0, 0, 0, textColor, "%.2f"} - }; + channels.sliders = {{"##X" + baseId, &values[0], speed, minValue, maxValue, 0, 0, 0, textColor, "%.2f"}, + {"##Y" + baseId, &values[1], speed, minValue, maxValue, 0, 0, 0, textColor, "%.2f"}, + {"##Z" + baseId, &values[2], speed, minValue, maxValue, 0, 0, 0, textColor, "%.2f"}}; - if (!chanLabel.label.empty()) - RowLabel(chanLabel); + if (!chanLabel.label.empty()) RowLabel(chanLabel); return RowDragFloat(channels); } - bool ToggleButtonWithSeparator(const std::string &label, bool* toggled) + bool ToggleButtonWithSeparator(const std::string &label, bool *toggled) { IdGuard idGuard(label); bool clicked = false; // Create the toggle button constexpr ImVec2 buttonSize(24, 24); - if (ImGui::InvisibleButton("##arrow", buttonSize)) - { - clicked = true; + if (ImGui::InvisibleButton("##arrow", buttonSize)) { + clicked = true; *toggled = !(*toggled); } @@ -400,34 +320,23 @@ namespace ImNexo { const ImVec2 center((p_min.x + p_max.x) * 0.5f, (p_min.y + p_max.y) * 0.5f); constexpr float arrowSize = 5.0f; - const ImU32 arrowColor = ImGui::GetColorU32(ImGuiCol_TextTab); + const ImU32 arrowColor = ImGui::GetColorU32(ImGuiCol_TextTab); Arrow(center, *toggled, arrowColor, arrowSize); ImGui::SameLine(); // Draw separator line - const ImVec2 separatorPos = ImGui::GetCursorScreenPos(); + const ImVec2 separatorPos = ImGui::GetCursorScreenPos(); constexpr float separatorHeight = 24.0f; // match button height - ImGui::GetWindowDrawList()->AddLine( - separatorPos, - ImVec2(separatorPos.x, separatorPos.y + separatorHeight), - ImGui::GetColorU32(ImGuiCol_Separator), - 1.0f - ); + ImGui::GetWindowDrawList()->AddLine(separatorPos, ImVec2(separatorPos.x, separatorPos.y + separatorHeight), + ImGui::GetColorU32(ImGuiCol_Separator), 1.0f); ImGui::Dummy(ImVec2(4, buttonSize.y)); ImGui::SameLine(); // Use the existing custom separator text component - CustomSeparatorText( - label, - 10.0f, - 0.1f, - 0.5f, - IM_COL32(255, 255, 255, 255), - IM_COL32(255, 255, 255, 255) - ); + CustomSeparatorText(label, 10.0f, 0.1f, 0.5f, IM_COL32(255, 255, 255, 255), IM_COL32(255, 255, 255, 255)); return clicked; } -} +} // namespace ImNexo diff --git a/editor/src/ImNexo/Components.hpp b/editor/src/ImNexo/Components.hpp index 0447c4a59..497598d0b 100644 --- a/editor/src/ImNexo/Components.hpp +++ b/editor/src/ImNexo/Components.hpp @@ -8,22 +8,22 @@ // // Author: Mehdy MORVAN // Date: 17/02/2025 -// Description: Header file for the utilitary ImGui functions +// Description: Header file for the utility ImGui functions // /////////////////////////////////////////////////////////////////////////////// #pragma once #include +#include #include #include #include -#include -#include "ecs/Coordinator.hpp" -#include "renderer/Texture.hpp" #include "Elements.hpp" #include "Guard.hpp" #include "ImNexo.hpp" +#include "ecs/Coordinator.hpp" +#include "renderer/Texture.hpp" namespace ImNexo { @@ -39,33 +39,36 @@ namespace ImNexo { * @param itemSize The size dimensions of the button * @return true if the button was clicked, false otherwise */ - bool ButtonWithIconAndText(const std::string &uniqueId, const std::string &icon, const std::string &label, const ImVec2 &itemSize); + bool ButtonWithIconAndText(const std::string &uniqueId, const std::string &icon, const std::string &label, + const ImVec2 &itemSize); /** - * @brief Draws a color button with a border. - * - * Displays a color button with the provided label and size. Optionally toggles a clicked state. - * - * @param label The label for the color button. - * @param size The size of the button. - * @param color The color to display. - * @param clicked Optional pointer to a boolean that is toggled when the button is clicked. - * @param flags Additional color edit flags. - */ - void ColorButton(const std::string &label, ImVec2 size, ImVec4 color, bool *clicked = nullptr, ImGuiColorEditFlags flags = ImGuiColorEditFlags_None); + * @brief Draws a color button with a border. + * + * Displays a color button with the provided label and size. Optionally toggles a clicked state. + * + * @param label The label for the color button. + * @param size The size of the button. + * @param color The color to display. + * @param clicked Optional pointer to a boolean that is toggled when the button is clicked. + * @param flags Additional color edit flags. + */ + void ColorButton(const std::string &label, ImVec2 size, ImVec4 color, bool *clicked = nullptr, + ImGuiColorEditFlags flags = ImGuiColorEditFlags_None); /** - * @brief Draws a texture button that displays a texture preview. - * - * When pressed, opens a file dialog to select a new texture. If a path was - * selected, it is set in `outPath` and the function returns true. - * - * @param[in] label A unique label identifier for the button. - * @param[in] texture A shared pointer to the renderer::NxTexture2D that holds the texture. - * @param[out] outPath The path to the selected texture. - * @return true if a texture path was set; false otherwise. - */ - bool TextureButton(const std::string &label, const std::shared_ptr& texture, std::filesystem::path& outPath); + * @brief Draws a texture button that displays a texture preview. + * + * When pressed, opens a file dialog to select a new texture. If a path was + * selected, it is set in `outPath` and the function returns true. + * + * @param[in] label A unique label identifier for the button. + * @param[in] texture A shared pointer to the renderer::NxTexture2D that holds the texture. + * @param[out] outPath The path to the selected texture. + * @return true if a texture path was set; false otherwise. + */ + bool TextureButton(const std::string &label, const std::shared_ptr &texture, + std::filesystem::path &outPath); /** * @brief Creates a customizable gradient button with a centered icon. @@ -84,18 +87,13 @@ namespace ImNexo { * @param iconColor Color of the icon * @return true if the button was clicked, false otherwise */ - bool IconGradientButton(const std::string& uniqueId, const std::string& icon, - const ImVec2& size = ImVec2(40, 40), - const std::vector& gradientStops = { - {0.0f, IM_COL32(60, 60, 80, 255)}, - {1.0f, IM_COL32(30, 30, 40, 255)} - }, - float gradientAngle = 45.0f, - ImU32 borderColor = IM_COL32(100, 100, 120, 255), - ImU32 borderColorHovered = IM_COL32(150, 150, 200, 255), - ImU32 borderColorActive = IM_COL32(200, 200, 255, 255), - ImU32 iconColor = IM_COL32(255, 255, 255, 255) - ); + bool IconGradientButton(const std::string &uniqueId, const std::string &icon, const ImVec2 &size = ImVec2(40, 40), + const std::vector &gradientStops = {{0.0f, IM_COL32(60, 60, 80, 255)}, + {1.0f, IM_COL32(30, 30, 40, 255)}}, + float gradientAngle = 45.0f, ImU32 borderColor = IM_COL32(100, 100, 120, 255), + ImU32 borderColorHovered = IM_COL32(150, 150, 200, 255), + ImU32 borderColorActive = IM_COL32(200, 200, 255, 255), + ImU32 iconColor = IM_COL32(255, 255, 255, 255)); /** * @brief Displays a dropdown to select an entity from a list. @@ -109,192 +107,164 @@ namespace ImNexo { * @param getNameFunc Function that converts an entity ID to a displayable name string * @return true if an entity was selected (value changed), false otherwise */ - template - bool RowEntityDropdown( - const std::string& label, - nexo::ecs::Entity& targetEntity, - const std::vector& entities, - GetNameFunc&& getNameFunc - ) - { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(label.c_str()); - - ImGui::TableNextColumn(); - IdGuard idGuard(label); + template + bool RowEntityDropdown(const std::string &label, nexo::ecs::Entity &targetEntity, + const std::vector &entities, GetNameFunc &&getNameFunc) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(label.c_str()); - bool changed = false; + ImGui::TableNextColumn(); + IdGuard idGuard(label); - // Build entity-name mapping - static std::vector> entityNamePairs; - static nexo::ecs::Entity lastTargetEntity = 0; - static std::vector lastEntities; + bool changed = false; - // Only rebuild the mapping if entities list changed or target entity changed - bool needRebuild = lastTargetEntity != targetEntity || lastEntities.size() != entities.size(); + // Build entity-name mapping + static std::vector> entityNamePairs; + static nexo::ecs::Entity lastTargetEntity = 0; + static std::vector lastEntities; - if (!needRebuild) { - for (size_t i = 0; i < entities.size() && !needRebuild; i++) { - needRebuild = lastEntities[i] != entities[i]; - } - } + // Only rebuild the mapping if entities list changed or target entity changed + bool needRebuild = lastTargetEntity != targetEntity || lastEntities.size() != entities.size(); - if (needRebuild) { - entityNamePairs.clear(); - entityNamePairs.reserve(entities.size()); - lastEntities = entities; - lastTargetEntity = targetEntity; + // if (!needRebuild) { + // for (size_t i = 0; i < entities.size() && !needRebuild; i++) { + // needRebuild = lastEntities[i] != entities[i]; + // } + for (size_t i = 0; i < entities.size() && !needRebuild; ++i) { + if (lastEntities[i] != entities[i]) { + needRebuild = true; + break; + } + } + // } - for (nexo::ecs::Entity entity : entities) { - std::string name = getNameFunc(entity); - entityNamePairs.emplace_back(entity, name); - } - } + if (needRebuild) { + entityNamePairs.clear(); + entityNamePairs.reserve(entities.size()); + lastEntities = entities; + lastTargetEntity = targetEntity; - // Find current index - int currentIndex = -1; - for (size_t i = 0; i < entityNamePairs.size(); i++) { - if (entityNamePairs[i].first == targetEntity) { - currentIndex = static_cast(i); - break; - } - } + for (nexo::ecs::Entity entity : entities) { + std::string name = getNameFunc(entity); + entityNamePairs.emplace_back(entity, name); + } + } - // Add a "None" option if we want to allow null selection - const std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; + // Find current index + int currentIndex = -1; + for (size_t i = 0; i < entityNamePairs.size(); i++) { + if (entityNamePairs[i].first == targetEntity) { + currentIndex = static_cast(i); + break; + } + } - // Draw the combo box - ImGui::SetNextItemWidth(-FLT_MIN); // Use all available width - if (ImGui::BeginCombo("##entity_dropdown", currentItemName.c_str())) - { - // Optional: Add a "None" option for clearing the target - if (ImGui::Selectable("None", targetEntity == nexo::ecs::MAX_ENTITIES)) { - targetEntity = nexo::ecs::MAX_ENTITIES; - changed = true; - } + // Add a "None" option if we want to allow null selection + const std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; - for (size_t i = 0; i < entityNamePairs.size(); i++) - { - const bool isSelected = (currentIndex == static_cast(i)); - if (ImGui::Selectable(entityNamePairs[i].second.c_str(), isSelected)) - { - targetEntity = entityNamePairs[i].first; - changed = true; - } + // Draw the combo box + ImGui::SetNextItemWidth(-FLT_MIN); // Use all available width + if (ImGui::BeginCombo("##entity_dropdown", currentItemName.c_str())) { + // Optional: Add a "None" option for clearing the target + if (ImGui::Selectable("None", targetEntity == nexo::ecs::MAX_ENTITIES)) { + targetEntity = nexo::ecs::MAX_ENTITIES; + changed = true; + } - if (isSelected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - if (ImGui::IsItemActive()) - setItemActive(); - if (ImGui::IsItemActivated()) - setItemActivated(); - if (ImGui::IsItemDeactivated()) - setItemDeactivated(); + for (size_t i = 0; i < entityNamePairs.size(); i++) { + const bool isSelected = (currentIndex == static_cast(i)); + if (ImGui::Selectable(entityNamePairs[i].second.c_str(), isSelected)) { + targetEntity = entityNamePairs[i].first; + changed = true; + } - return changed; - } + if (isSelected) ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + if (ImGui::IsItemActive()) setItemActive(); + if (ImGui::IsItemActivated()) setItemActivated(); + if (ImGui::IsItemDeactivated()) setItemDeactivated(); - /** - * @brief Draws a row with multiple channels (badge + slider pairs) - * - * This is a lower-level function used by the other drawRowDragFloatX functions. - * - * @param[in] channels The channel configuration to draw - * @return true if any value was changed, false otherwise - */ - bool RowDragFloat(const Channels &channels); + return changed; + } /** - * @brief Draws a row with a single float value slider - * - * @param[in] uniqueLabel Unique label/ID for the component - * @param[in] badgeLabel Text for the badge (empty for no badge) - * @param[in,out] value Pointer to the float value to edit - * @param[in] minValue Minimum allowed value (default: -FLT_MAX) - * @param[in] maxValue Maximum allowed value (default: FLT_MAX) - * @param[in] speed Speed of value change during dragging (default: 0.3f) - * @return true if the value was changed, false otherwise - */ - bool RowDragFloat1( - const char *uniqueLabel, - const std::string &badgeLabel, - float *value, - float minValue = -FLT_MAX, - float maxValue = FLT_MAX, - float speed = 0.3f - ); + * @brief Draws a row with multiple channels (badge + slider pairs) + * + * This is a lower-level function used by the other drawRowDragFloatX functions. + * + * @param[in] channels The channel configuration to draw + * @return true if any value was changed, false otherwise + */ + bool RowDragFloat(const Channels &channels); + /** + * @brief Draws a row with a single float value slider + * + * @param[in] uniqueLabel Unique label/ID for the component + * @param[in] badgeLabel Text for the badge (empty for no badge) + * @param[in,out] value Pointer to the float value to edit + * @param[in] minValue Minimum allowed value (default: -FLT_MAX) + * @param[in] maxValue Maximum allowed value (default: FLT_MAX) + * @param[in] speed Speed of value change during dragging (default: 0.3f) + * @return true if the value was changed, false otherwise + */ + bool RowDragFloat1(const char *uniqueLabel, const std::string &badgeLabel, float *value, float minValue = -FLT_MAX, + float maxValue = FLT_MAX, float speed = 0.3f); /** - * @brief Draws a row with two float value sliders (X and Y components) - * - * @param[in] uniqueLabel Unique label/ID for the component - * @param[in] badLabelX Text for the X component badge - * @param[in] badLabelY Text for the Y component badge - * @param[in,out] values Pointer to array of two float values to edit - * @param[in] minValue Minimum allowed value (default: -FLT_MAX) - * @param[in] maxValue Maximum allowed value (default: FLT_MAX) - * @param[in] speed Speed of value change during dragging (default: 0.3f) - * @param[in] badgeColor Optional custom colors for badges - * @param[in] textBadgeColor Optional custom text colors for badges - * @param[in] disabled If true, renders in an inactive/disabled state (default: false) - * @return true if any value was changed, false otherwise - */ - bool RowDragFloat2( - const char *uniqueLabel, - const std::string &badLabelX, - const std::string &badLabelY, - float *values, - float minValue = -FLT_MAX, - float maxValue = FLT_MAX, - float speed = 0.3f, - std::vector badgeColor = {}, - std::vector textBadgeColor = {}, - bool disabled = false - ); + * @brief Draws a row with two float value sliders (X and Y components) + * + * @param[in] uniqueLabel Unique label/ID for the component + * @param[in] badLabelX Text for the X component badge + * @param[in] badLabelY Text for the Y component badge + * @param[in,out] values Pointer to array of two float values to edit + * @param[in] minValue Minimum allowed value (default: -FLT_MAX) + * @param[in] maxValue Maximum allowed value (default: FLT_MAX) + * @param[in] speed Speed of value change during dragging (default: 0.3f) + * @param[in] badgeColor Optional custom colors for badges + * @param[in] textBadgeColor Optional custom text colors for badges + * @param[in] disabled If true, renders in an inactive/disabled state (default: false) + * @return true if any value was changed, false otherwise + */ + bool RowDragFloat2(const char *uniqueLabel, const std::string &badLabelX, const std::string &badLabelY, + float *values, float minValue = -FLT_MAX, float maxValue = FLT_MAX, float speed = 0.3f, + std::vector badgeColor = {}, std::vector textBadgeColor = {}, + bool disabled = false); /** - * @brief Draws a row with three float value sliders (X, Y, and Z components) - * - * @param[in] uniqueLabel Unique label/ID for the component - * @param[in] badLabelX Text for the X component badge - * @param[in] badLabelY Text for the Y component badge - * @param[in] badLabelZ Text for the Z component badge - * @param[in,out] values Pointer to array of three float values to edit - * @param[in] minValue Minimum allowed value (default: -FLT_MAX) - * @param[in] maxValue Maximum allowed value (default: FLT_MAX) - * @param[in] speed Speed of value change during dragging (default: 0.3f) - * @param[in] badgeColors Optional custom colors for badges - * @param[in] textBadgeColors Optional custom text colors for badges - * @return true if any value was changed, false otherwise - */ - bool RowDragFloat3( - const char *uniqueLabel, - const std::string &badLabelX, - const std::string &badLabelY, - const std::string &badLabelZ, - float *values, - float minValue = -FLT_MAX, - float maxValue = FLT_MAX, - float speed = 0.3f, - std::vector badgeColors = {}, - std::vector textBadgeColors = {} - ); + * @brief Draws a row with three float value sliders (X, Y, and Z components) + * + * @param[in] uniqueLabel Unique label/ID for the component + * @param[in] badLabelX Text for the X component badge + * @param[in] badLabelY Text for the Y component badge + * @param[in] badLabelZ Text for the Z component badge + * @param[in,out] values Pointer to array of three float values to edit + * @param[in] minValue Minimum allowed value (default: -FLT_MAX) + * @param[in] maxValue Maximum allowed value (default: FLT_MAX) + * @param[in] speed Speed of value change during dragging (default: 0.3f) + * @param[in] badgeColors Optional custom colors for badges + * @param[in] textBadgeColors Optional custom text colors for badges + * @return true if any value was changed, false otherwise + */ + bool RowDragFloat3(const char *uniqueLabel, const std::string &badLabelX, const std::string &badLabelY, + const std::string &badLabelZ, float *values, float minValue = -FLT_MAX, float maxValue = FLT_MAX, + float speed = 0.3f, std::vector badgeColors = {}, + std::vector textBadgeColors = {}); /** - * @brief Draws a toggle button with a separator and label - * - * Creates a collapsible section control with an arrow that toggles - * between expanded and collapsed states. - * - * @param[in] label The label to display - * @param[in,out] toggled Pointer to bool that tracks the toggle state - * @return true if the toggle state changed, false otherwise - */ - bool ToggleButtonWithSeparator(const std::string &label, bool* toggled); -} + * @brief Draws a toggle button with a separator and label + * + * Creates a collapsible section control with an arrow that toggles + * between expanded and collapsed states. + * + * @param[in] label The label to display + * @param[in,out] toggled Pointer to bool that tracks the toggle state + * @return true if the toggle state changed, false otherwise + */ + bool ToggleButtonWithSeparator(const std::string &label, bool *toggled); +} // namespace ImNexo diff --git a/editor/src/ImNexo/Elements.cpp b/editor/src/ImNexo/Elements.cpp index 287461a3d..14834835a 100644 --- a/editor/src/ImNexo/Elements.cpp +++ b/editor/src/ImNexo/Elements.cpp @@ -18,7 +18,6 @@ #include #include -#include #include namespace ImNexo { @@ -114,6 +113,20 @@ namespace ImNexo { } } + /** + * @brief Draws a button with custom style colors. + * + * Pushes custom style colors for the button and its states, draws the button, + * and then pops the style colors. + * + * @param label The button label. + * @param size The size of the button. + * @param bg The background color. + * @param bgHovered The background color when hovered. + * @param bgActive The background color when active. + * @param txtColor The text color. + * @return true if the button was clicked; false otherwise. + */ bool Button(const std::string& label, const ImVec2& size, const ImU32 bg, const ImU32 bgHovered, const ImU32 bgActive, const ImU32 txtColor) { @@ -125,15 +138,30 @@ namespace ImNexo { return ImGui::Button(label.c_str(), size); } + /** + * @brief Draws a validation button with custom style colors. + * + * Pushes custom style colors for the button and its states, draws the button, + * and then pops the style colors. + * + * @param label The button label. + * @param type The type of the button (validation, cancel, or default). + * @param size The size of the button. + * @param bg The background color. + * @param bgHovered The background color when hovered. + * @param bgActive The background color when active. + * @param txtColor The text color. + * @return true if the button was clicked; false otherwise. + */ bool Button(const std::string& label, const ButtonTypes type, const ImVec2& size, const ImU32 bg, const ImU32 bgHovered, const ImU32 bgActive, const ImU32 txtColor) { const bool clicked = Button(label, size, bg, bgHovered, bgActive, txtColor); switch (type) { - case (VALIDATION): { + case ButtonTypes::VALIDATION: { return clicked || ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter); } - case (CANCEL): { + case ButtonTypes::CANCEL: { return clicked || ImGui::IsKeyPressed(ImGuiKey_Escape); } default: { @@ -142,6 +170,19 @@ namespace ImNexo { } } + /** + * @brief Draws a border around the last item. + * + * Uses the current item's rectangle and draws a border with specified colors + * for normal, hovered, and active states. + * + * @param borderColor The border color for normal state. + * @param borderColorHovered The border color when hovered. + * @param borderColorActive The border color when active. + * @param rounding The rounding of the border corners. + * @param flags Additional draw flags. + * @param thickness The thickness of the border. + */ void ButtonBorder(const ImU32 borderColor, const ImU32 borderColorHovered, const ImU32 borderColorActive, const float rounding, const ImDrawFlags flags, const float thickness) { @@ -156,6 +197,23 @@ namespace ImNexo { ImGui::GetWindowDrawList()->AddRect(p_min, p_max, color, rounding, flags, thickness); } + /** + * @brief Draws a draggable float widget with custom styling. + * + * Pushes custom style colors for the drag float widget, draws it, and then pops the styles. + * + * @param label The label for the drag float. + * @param values Pointer to the float value. + * @param speed The speed of value change. + * @param min The minimum allowable value. + * @param max The maximum allowable value. + * @param format The display format. + * @param bg The background color. + * @param bgHovered The background color when hovered. + * @param bgActive The background color when active. + * @param textColor The text color. + * @return true if the value was changed; false otherwise. + */ bool DragFloat(const std::string& label, float* values, const float speed, const float min, const float max, const std::string& format, const ImU32 bg, const ImU32 bgHovered, const ImU32 bgActive, const ImU32 textColor) @@ -168,8 +226,14 @@ namespace ImNexo { return ImGui::DragFloat(label.c_str(), values, speed, min, max, format.c_str()); } + /** - * @brief Sanitizes gradient stops to ensure proper ordering and range + * @brief Sanitize and prepare gradient stops for rendering + * @param[in,out] stops Vector of gradient stops to sanitize + * + * This function ensures that the gradient stops are sorted by position, + * clamped to the range [0.0f, 1.0f], and that there are stops at both + * 0.0f and 1.0f if needed. */ static void sanitizeGradientStops(std::vector& stops) { @@ -199,6 +263,14 @@ namespace ImNexo { } } + /** + * @brief Draws a rectangle filled with a linear gradient defined by color stops and angle + * @param[in] pMin Minimum bounds of the rectangle + * @param[in] pMax Maximum bounds of the rectangle + * @param[in] angle Angle of the gradient in degrees (0 = left to right, 90 = bottom to top) + * @param[in] stops Array of gradient color stops defining the gradient + * @param[in] drawList Optional ImDrawList to draw into (nullptr for current window's draw list) + */ void RectFilledLinearGradient(const ImVec2& pMin, const ImVec2& pMax, float angle, std::vector stops, ImDrawList* drawList) { @@ -269,6 +341,13 @@ namespace ImNexo { } } + /** + * @brief Draws a collapsible header with centered text + * + * @param[in] label Unique label/ID for the header + * @param[in] headerText Text to display in the header + * @return true if the header is open/expanded, false otherwise + */ bool Header(const std::string& label, const std::string_view headerText) { StyleVarGuard styleGuard(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, 3.0f)); @@ -287,6 +366,10 @@ namespace ImNexo { return open; } + /** + * @brief Draws a row label in the current table column + * @param[in] rowLabel The label configuration to draw + */ void RowLabel(const ChannelLabel& rowLabel) { ImGui::TableNextColumn(); @@ -294,7 +377,13 @@ namespace ImNexo { ImGui::TextUnformatted(rowLabel.label.c_str()); } - // Helper method to draw arrow indicators + /** + * @brief Draws an arrow (right or down) at the specified position + * @param center Center position of the arrow + * @param isExpanded True for down arrow (expanded), false for right arrow (collapsed) + * @param color Color of the arrow + * @param size Size of the arrow + */ void Arrow(const ImVec2& center, const bool isExpanded, const ImU32 color, const float size) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -312,6 +401,15 @@ namespace ImNexo { } } + /** + * @brief Draws a horizontal separator with centered text + * @param text The text to display in the center of the separator + * @param textPadding Padding between the text and the separator lines + * @param leftSpacing Fraction of space allocated to the left line (0.0 to 1.0) + * @param thickness Thickness of the separator lines + * @param lineColor Color of the separator lines + * @param textColor Color of the text + */ void CustomSeparatorText(const std::string& text, const float textPadding, const float leftSpacing, const float thickness, const ImU32 lineColor, const ImU32 textColor) { @@ -342,12 +440,32 @@ namespace ImNexo { ImGui::Dummy(ImVec2(0, ImGui::GetTextLineHeight())); } + /** + * @brief Wrapper around ImGui::Image to provide a consistent interface + * @param user_texture_id The texture ID to display + * @param image_size Size of the image + * @param uv0 UV coordinate for the top-left corner + * @param uv1 UV coordinate for the bottom-right corner + * @param tint_col Tint color to apply to the image + * @param border_col Border color to apply around the image + */ void Image(const ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) { ImGui::Image(user_texture_id, image_size, uv0, uv1, tint_col, border_col); } + /** + * @brief Wrapper around ImGui::ImageButton to provide a consistent interface + * @param str_id Unique string ID for the button + * @param user_texture_id The texture ID to display + * @param image_size Size of the image + * @param uv0 UV coordinate for the top-left corner + * @param uv1 UV coordinate for the bottom-right corner + * @param bg_col Background color of the button + * @param tint_col Tint color to apply to the image + * @return true if the button was clicked, false otherwise + */ bool ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { diff --git a/editor/src/ImNexo/Elements.hpp b/editor/src/ImNexo/Elements.hpp index 8af8a43e5..f8d826dfc 100644 --- a/editor/src/ImNexo/Elements.hpp +++ b/editor/src/ImNexo/Elements.hpp @@ -20,241 +20,193 @@ namespace ImNexo { /** - * @struct ChannelLabel - * @brief Represents a label for a channel in the entity properties editor - * - * Labels can have optional fixed width for precise layout control. - */ - struct ChannelLabel { - std::string label; - float fixedWidth = -1.0f; - }; + * @struct ChannelLabel + * @brief Represents a label for a channel in the entity properties editor + * + * Labels can have optional fixed width for precise layout control. + */ + struct ChannelLabel { + std::string label; + float fixedWidth = -1.0f; + }; /** - * @struct Badge - * @brief A styled badge component with customizable appearance - * - * Used as visual indicators or labels in the UI, typically alongside sliders. - */ - struct Badge { - std::string label; ///< The displayed text - ImVec2 size; ///< Size of the badge in pixels - ImU32 bg; ///< Background color - ImU32 bgHovered; ///< Background color when hovered - ImU32 bgActive; ///< Background color when active - ImU32 txtColor; ///< Text color - }; + * @struct Badge + * @brief A styled badge component with customizable appearance + * + * Used as visual indicators or labels in the UI, typically alongside sliders. + */ + struct Badge { + std::string label; ///< The displayed text + ImVec2 size; ///< Size of the badge in pixels + ImU32 bg; ///< Background color + ImU32 bgHovered; ///< Background color when hovered + ImU32 bgActive; ///< Background color when active + ImU32 txtColor; ///< Text color + }; /** - * @struct DragFloat - * @brief A drag float slider component with customizable appearance - * - * Used for editing float values with adjustable range and visual styling. - */ - struct DragFloat { - std::string label; ///< Unique label/ID for the component - float *value; ///< Pointer to the value being edited - float speed; ///< Speed of value change during dragging - float min; ///< Minimum value - float max; ///< Maximum value - ImU32 bg; ///< Background color - ImU32 bgHovered; ///< Background color when hovered - ImU32 bgActive; ///< Background color when active - ImU32 textColor; ///< Text color - std::string format; ///< Format string for displaying the value - }; + * @struct DragFloat + * @brief A drag float slider component with customizable appearance + * + * Used for editing float values with adjustable range and visual styling. + */ + struct DragFloat { + std::string label; ///< Unique label/ID for the component + float* value; ///< Pointer to the value being edited + float speed; ///< Speed of value change during dragging + float min; ///< Minimum value + float max; ///< Maximum value + ImU32 bg; ///< Background color + ImU32 bgHovered; ///< Background color when hovered + ImU32 bgActive; ///< Background color when active + ImU32 textColor; ///< Text color + std::string format; ///< Format string for displaying the value + }; /** - * @struct Channels - * @brief A collection of badges and sliders forming a multi-channel editing row - * - * Used to create rows with multiple editable values (like X, Y, Z components). - */ - struct Channels { - unsigned int count; ///< Number of channels - std::vector badges; ///< Badge component for each channel - std::vector sliders; ///< Slider component for each channel - }; + * @struct Channels + * @brief A collection of badges and sliders forming a multi-channel editing row + * + * Used to create rows with multiple editable values (like X, Y, Z components). + */ + struct Channels { + unsigned int count; ///< Number of channels + std::vector badges; ///< Badge component for each channel + std::vector sliders; ///< Slider component for each channel + }; - enum ButtonTypes { - VALIDATION, ///< Button type for validation actions - CANCEL, ///< Button type for cancel actions - DEFAULT ///< Default button type for general actions - }; + enum class ButtonTypes { + VALIDATION, ///< Button type for validation actions + CANCEL, ///< Button type for cancel actions + DEFAULT ///< Default button type for general actions + }; /** - * @brief Draw an icon centered within a rectangle with optional vertical positioning - * @param[in] icon Text of the icon to draw - * @param[in] p_min Minimum bounds of the rectangle - * @param[in] p_max Maximum bounds of the rectangle - * @param[in] color Color of the icon - * @param[in] scale Scale factor for the icon font - * @param[in] verticalPosition Vertical position factor (0-1), 0.5 for centered - * @param[in] horizontalPosition Horizontal position factor (0-1), 0.5 for centered - * @param[in] font Font to use (nullptr for current font) - */ - void CenteredIcon( - const std::string& icon, - const ImVec2& p_min, - const ImVec2& p_max, - ImU32 color, - float scale = 1.0f, - float verticalPosition = 0.5f, - float horizontalPosition = 0.5f, - ImFont* font = nullptr - ); - + * @brief Draw an icon centered within a rectangle with optional vertical positioning + * @param[in] icon Text of the icon to draw + * @param[in] p_min Minimum bounds of the rectangle + * @param[in] p_max Maximum bounds of the rectangle + * @param[in] color Color of the icon + * @param[in] scale Scale factor for the icon font + * @param[in] verticalPosition Vertical position factor (0-1), 0.5 for centered + * @param[in] horizontalPosition Horizontal position factor (0-1), 0.5 for centered + * @param[in] font Font to use (nullptr for current font) + */ + void CenteredIcon(const std::string& icon, const ImVec2& p_min, const ImVec2& p_max, ImU32 color, + float scale = 1.0f, float verticalPosition = 0.5f, float horizontalPosition = 0.5f, + ImFont* font = nullptr); /** - * @brief Draw wrapped text within bounds, attempts to split on spaces for better appearance - * @param[in] text Text to draw - * @param[in] p_min Minimum bounds - * @param[in] p_max Maximum bounds - * @param[in] color Text color - * @param[in] verticalPosition Vertical position (0-1), 0.5 for centered - */ - void WrappedCenteredText( - const std::string& text, - const ImVec2& p_min, - const ImVec2& p_max, - ImU32 color, - float verticalPosition = 0.5f - ); + * @brief Draw wrapped text within bounds, attempts to split on spaces for better appearance + * @param[in] text Text to draw + * @param[in] p_min Minimum bounds + * @param[in] p_max Maximum bounds + * @param[in] color Text color + * @param[in] verticalPosition Vertical position (0-1), 0.5 for centered + */ + void WrappedCenteredText(const std::string& text, const ImVec2& p_min, const ImVec2& p_max, ImU32 color, + float verticalPosition = 0.5f); - /** - * @brief Draws a validation button with custom style colors. - * - * Pushes custom style colors for the button and its states, draws the button, - * and then pops the style colors. - * - * @param label The button label. - * @param type The type of the button (validation, cancel, or default). - * @param size The size of the button. - * @param bg The background color. - * @param bgHovered The background color when hovered. - * @param bgActive The background color when active. - * @param txtColor The text color. - * @return true if the button was clicked; false otherwise. - */ - bool Button( - const std::string &label, - ButtonTypes type, - const ImVec2& size = ImVec2(0, 0), - ImU32 bg = 0, - ImU32 bgHovered = 0, - ImU32 bgActive = 0, - ImU32 txtColor = 0 - ); + /** + * @brief Draws a validation button with custom style colors. + * + * Pushes custom style colors for the button and its states, draws the button, + * and then pops the style colors. + * + * @param label The button label. + * @param type The type of the button (validation, cancel, or default). + * @param size The size of the button. + * @param bg The background color. + * @param bgHovered The background color when hovered. + * @param bgActive The background color when active. + * @param txtColor The text color. + * @return true if the button was clicked; false otherwise. + */ + bool Button(const std::string& label, ButtonTypes type, const ImVec2& size = ImVec2(0, 0), ImU32 bg = 0, + ImU32 bgHovered = 0, ImU32 bgActive = 0, ImU32 txtColor = 0); /** - * @brief Draws a button with custom style colors. - * - * Pushes custom style colors for the button and its states, draws the button, - * and then pops the style colors. - * - * @param label The button label. - * @param size The size of the button. - * @param bg The background color. - * @param bgHovered The background color when hovered. - * @param bgActive The background color when active. - * @param txtColor The text color. - * @return true if the button was clicked; false otherwise. - */ - bool Button( - const std::string &label, - const ImVec2& size = ImVec2(0, 0), - ImU32 bg = 0, - ImU32 bgHovered = 0, - ImU32 bgActive = 0, - ImU32 txtColor = 0 - ); + * @brief Draws a button with custom style colors. + * + * Pushes custom style colors for the button and its states, draws the button, + * and then pops the style colors. + * + * @param label The button label. + * @param size The size of the button. + * @param bg The background color. + * @param bgHovered The background color when hovered. + * @param bgActive The background color when active. + * @param txtColor The text color. + * @return true if the button was clicked; false otherwise. + */ + bool Button(const std::string& label, const ImVec2& size = ImVec2(0, 0), ImU32 bg = 0, ImU32 bgHovered = 0, + ImU32 bgActive = 0, ImU32 txtColor = 0); /** - * @brief Draws a border around the last item. - * - * Uses the current item's rectangle and draws a border with specified colors - * for normal, hovered, and active states. - * - * @param borderColor The border color for normal state. - * @param borderColorHovered The border color when hovered. - * @param borderColorActive The border color when active. - * @param rounding The rounding of the border corners. - * @param flags Additional draw flags. - * @param thickness The thickness of the border. - */ - void ButtonBorder( - ImU32 borderColor, - ImU32 borderColorHovered, - ImU32 borderColorActive, - float rounding = 2.0f, - ImDrawFlags flags = 0, - float thickness = 3.0f - ); + * @brief Draws a border around the last item. + * + * Uses the current item's rectangle and draws a border with specified colors + * for normal, hovered, and active states. + * + * @param borderColor The border color for normal state. + * @param borderColorHovered The border color when hovered. + * @param borderColorActive The border color when active. + * @param rounding The rounding of the border corners. + * @param flags Additional draw flags. + * @param thickness The thickness of the border. + */ + void ButtonBorder(ImU32 borderColor, ImU32 borderColorHovered, ImU32 borderColorActive, float rounding = 2.0f, + ImDrawFlags flags = 0, float thickness = 3.0f); /** - * @brief Draws a border inside the last item. - * - * Similar to drawButtonBorder, but draws a border inside the item rectangle instead of outside. - * - * @param borderColor The border color for normal state. - * @param borderColorHovered The border color when hovered. - * @param borderColorActive The border color when active. - * @param rounding The rounding of the border corners. - * @param flags Additional draw flags. - * @param thickness The thickness of the border. - */ - void ButtonInnerBorder( - ImU32 borderColor, - ImU32 borderColorHovered, - ImU32 borderColorActive, - float rounding = 2.0f, - ImDrawFlags flags = 0, - float thickness = 3.0f - ); + * @brief Draws a border inside the last item. + * + * Similar to drawButtonBorder, but draws a border inside the item rectangle instead of outside. + * + * @param borderColor The border color for normal state. + * @param borderColorHovered The border color when hovered. + * @param borderColorActive The border color when active. + * @param rounding The rounding of the border corners. + * @param flags Additional draw flags. + * @param thickness The thickness of the border. + */ + void ButtonInnerBorder(ImU32 borderColor, ImU32 borderColorHovered, ImU32 borderColorActive, float rounding = 2.0f, + ImDrawFlags flags = 0, float thickness = 3.0f); /** - * @brief Draws a draggable float widget with custom styling. - * - * Pushes custom style colors for the drag float widget, draws it, and then pops the styles. - * - * @param label The label for the drag float. - * @param values Pointer to the float value. - * @param speed The speed of value change. - * @param min The minimum allowable value. - * @param max The maximum allowable value. - * @param format The display format. - * @param bg The background color. - * @param bgHovered The background color when hovered. - * @param bgActive The background color when active. - * @param textColor The text color. - * @return true if the value was changed; false otherwise. - */ - bool DragFloat( - const std::string &label, - float *values, - float speed, - float min, - float max, - const std::string &format, - ImU32 bg = 0, - ImU32 bgHovered = 0, - ImU32 bgActive = 0, - ImU32 textColor = 0 - ); + * @brief Draws a draggable float widget with custom styling. + * + * Pushes custom style colors for the drag float widget, draws it, and then pops the styles. + * + * @param label The label for the drag float. + * @param values Pointer to the float value. + * @param speed The speed of value change. + * @param min The minimum allowable value. + * @param max The maximum allowable value. + * @param format The display format. + * @param bg The background color. + * @param bgHovered The background color when hovered. + * @param bgActive The background color when active. + * @param textColor The text color. + * @return true if the value was changed; false otherwise. + */ + bool DragFloat(const std::string& label, float* values, float speed, float min, float max, + const std::string& format, ImU32 bg = 0, ImU32 bgHovered = 0, ImU32 bgActive = 0, + ImU32 textColor = 0); /** - * @struct GradientStop - * @brief Defines a color position in a gradient - * - * Each gradient stop has a position (from 0.0 to 1.0) that represents - * where along the gradient the color appears, and a color value in ImGui's - * 32-bit color format. - */ - struct GradientStop - { - float pos; // percentage position along the gradient [0.0f, 1.0f] - ImU32 color; // color at this stop - }; + * @struct GradientStop + * @brief Defines a color position in a gradient + * + * Each gradient stop has a position (from 0.0 to 1.0) that represents + * where along the gradient the color appears, and a color value in ImGui's + * 32-bit color format. + */ + struct GradientStop { + float pos; // percentage position along the gradient [0.0f, 1.0f] + ImU32 color; // color at this stop + }; /** * @brief Draw filled rectangle with a linear gradient defined by an arbitrary angle and gradient stops. @@ -264,104 +216,90 @@ namespace ImNexo { * @param stops Vector of gradient stops, each defined by a position (0.0f to 1.0f) and a color * @param drawList The imgui draw list */ - void RectFilledLinearGradient( - const ImVec2& pMin, - const ImVec2& pMax, - float angle, - std::vector stops, - ImDrawList* drawList = nullptr - ); + void RectFilledLinearGradient(const ImVec2& pMin, const ImVec2& pMax, float angle, std::vector stops, + ImDrawList* drawList = nullptr); /** - * @brief Draws a collapsible header with centered text - * - * @param[in] label Unique label/ID for the header - * @param[in] headerText Text to display in the header - * @return true if the header is open/expanded, false otherwise - */ - bool Header(const std::string &label, std::string_view headerText); + * @brief Draws a collapsible header with centered text + * + * @param[in] label Unique label/ID for the header + * @param[in] headerText Text to display in the header + * @return true if the header is open/expanded, false otherwise + */ + bool Header(const std::string& label, std::string_view headerText); /** - * @brief Draws a row label in the current table column - * - * @param[in] rowLabel The label configuration to draw - */ - void RowLabel(const ChannelLabel &rowLabel); + * @brief Draws a row label in the current table column + * + * @param[in] rowLabel The label configuration to draw + */ + void RowLabel(const ChannelLabel& rowLabel); /** - * @brief Draws an arrow shape indicating expanded/collapsed state - * - * Creates a filled triangle pointing downward (expanded) or rightward (collapsed) - * that is commonly used to indicate a toggleable/expandable UI element. - * - * @param center Center point of the arrow - * @param isExpanded Whether the arrow should show expanded state (down arrow) or collapsed state (right arrow) - * @param color Color of the arrow - * @param size Size of the arrow from center to tip - */ - void Arrow(const ImVec2& center, bool isExpanded, ImU32 color, float size); + * @brief Draws an arrow shape indicating expanded/collapsed state + * + * Creates a filled triangle pointing downward (expanded) or rightward (collapsed) + * that is commonly used to indicate a toggleable/expandable UI element. + * + * @param center Center point of the arrow + * @param isExpanded Whether the arrow should show expanded state (down arrow) or collapsed state (right arrow) + * @param color Color of the arrow + * @param size Size of the arrow from center to tip + */ + void Arrow(const ImVec2& center, bool isExpanded, ImU32 color, float size); /** - * @brief Draws a custom separator with centered text. - * - * Renders a separator line with text in the middle, with customizable padding, spacing, - * thickness, and colors. - * - * @param text The text to display at the separator. - * @param textPadding Padding around the text. - * @param leftSpacing The spacing multiplier for the left separator line. - * @param thickness The thickness of the separator lines. - * @param lineColor The color of the separator lines. - * @param textColor The color of the text. - */ - void CustomSeparatorText(const std::string &text, float textPadding, float leftSpacing, float thickness, ImU32 lineColor, ImU32 textColor); + * @brief Draws a custom separator with centered text. + * + * Renders a separator line with text in the middle, with customizable padding, spacing, + * thickness, and colors. + * + * @param text The text to display at the separator. + * @param textPadding Padding around the text. + * @param leftSpacing The spacing multiplier for the left separator line. + * @param thickness The thickness of the separator lines. + * @param lineColor The color of the separator lines. + * @param textColor The color of the text. + */ + void CustomSeparatorText(const std::string& text, float textPadding, float leftSpacing, float thickness, + ImU32 lineColor, ImU32 textColor); /** - * @brief ImGui::Image wrapper with different default UV coordinates (to flip the Y-axis). - * - * This function behaves exactly like ImGui::Image, except that the default UV coordinates are - * flipped to invert the image vertically. If you provide custom UV coordinates, the flipping - * behavior is effectively disabled. - * - * @param[in] user_texture_id The texture identifier for ImGui to render. - * @param[in] image_size The size of the image on the screen (in pixels). - * @param[in] uv0 The normalized UV coordinate for the top-left corner of the texture. - * @param[in] uv1 The normalized UV coordinate for the bottom-right corner of the texture. - * @param[in] tint_col The tint color applied to the image. - * @param[in] border_col The border color drawn around the image, if any. - */ - void Image( - ImTextureID user_texture_id, - const ImVec2& image_size, - const ImVec2& uv0 = ImVec2(0, 1), // Flipped Y - const ImVec2& uv1 = ImVec2(1, 0), // Flipped Y - const ImVec4& tint_col = ImVec4(1, 1, 1, 1), - const ImVec4& border_col = ImVec4(0, 0, 0, 0) - ); + * @brief ImGui::Image wrapper with different default UV coordinates (to flip the Y-axis). + * + * This function behaves exactly like ImGui::Image, except that the default UV coordinates are + * flipped to invert the image vertically. If you provide custom UV coordinates, the flipping + * behavior is effectively disabled. + * + * @param[in] user_texture_id The texture identifier for ImGui to render. + * @param[in] image_size The size of the image on the screen (in pixels). + * @param[in] uv0 The normalized UV coordinate for the top-left corner of the texture. + * @param[in] uv1 The normalized UV coordinate for the bottom-right corner of the texture. + * @param[in] tint_col The tint color applied to the image. + * @param[in] border_col The border color drawn around the image, if any. + */ + void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 1), // Flipped Y + const ImVec2& uv1 = ImVec2(1, 0), // Flipped Y + const ImVec4& tint_col = ImVec4(1, 1, 1, 1), const ImVec4& border_col = ImVec4(0, 0, 0, 0)); /** - * @brief ImGui::ImageButton wrapper with different default UV coordinates (to flip the Y-axis). - * - * This function behaves exactly like ImGui::ImageButton, except that the default UV coordinates are - * flipped to invert the image vertically. If you provide custom UV coordinates, the flipping - * behavior is effectively disabled. - * - * @param[in] str_id The unique label for the image button. - * @param[in] user_texture_id The texture identifier for ImGui to render. - * @param[in] image_size The size of the image on the screen (in pixels). - * @param[in] uv0 The normalized UV coordinate for the top-left corner of the texture. - * @param[in] uv1 The normalized UV coordinate for the bottom-right corner of the texture. - * @param[in] bg_col The background color of the button. - * @param[in] tint_col The tint color applied to the image. - */ - bool ImageButton( - const char* str_id, - ImTextureID user_texture_id, - const ImVec2& image_size, - const ImVec2& uv0 = ImVec2(0, 1), // Flipped Y - const ImVec2& uv1 = ImVec2(1, 0), // Flipped Y - const ImVec4& bg_col = ImVec4(0, 0, 0, 0), - const ImVec4& tint_col = ImVec4(1, 1, 1, 1) - ); + * @brief ImGui::ImageButton wrapper with different default UV coordinates (to flip the Y-axis). + * + * This function behaves exactly like ImGui::ImageButton, except that the default UV coordinates are + * flipped to invert the image vertically. If you provide custom UV coordinates, the flipping + * behavior is effectively disabled. + * + * @param[in] str_id The unique label for the image button. + * @param[in] user_texture_id The texture identifier for ImGui to render. + * @param[in] image_size The size of the image on the screen (in pixels). + * @param[in] uv0 The normalized UV coordinate for the top-left corner of the texture. + * @param[in] uv1 The normalized UV coordinate for the bottom-right corner of the texture. + * @param[in] bg_col The background color of the button. + * @param[in] tint_col The tint color applied to the image. + */ + bool ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, + const ImVec2& uv0 = ImVec2(0, 1), // Flipped Y + const ImVec2& uv1 = ImVec2(1, 0), // Flipped Y + const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); -} +} // namespace ImNexo diff --git a/editor/src/ImNexo/EntityProperties.cpp b/editor/src/ImNexo/EntityProperties.cpp index e58a1e067..880685f30 100644 --- a/editor/src/ImNexo/EntityProperties.cpp +++ b/editor/src/ImNexo/EntityProperties.cpp @@ -12,19 +12,19 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "ImNexo/ImNexo.hpp" -#include "Widgets.hpp" -#include "Guard.hpp" #include "EntityProperties.hpp" +#include "Guard.hpp" #include "IconsFontAwesome.h" +#include "ImNexo/ImNexo.hpp" #include "Nexo.hpp" +#include "Widgets.hpp" #include "components/Camera.hpp" -#include "context/Selector.hpp" -#include "components/Uuid.hpp" #include "components/Light.hpp" #include "components/Transform.hpp" -#include "math/Vector.hpp" +#include "components/Uuid.hpp" +#include "context/Selector.hpp" #include "math/Light.hpp" +#include "math/Vector.hpp" namespace ImNexo { @@ -32,7 +32,7 @@ namespace ImNexo { { ImGui::Spacing(); static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; + static bool showColorPicker = false; ImGui::Text("Color"); ImGui::SameLine(); @@ -45,7 +45,7 @@ namespace ImNexo { { ImGui::Spacing(); static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; + static bool showColorPicker = false; ImGui::Text("Color"); ImGui::SameLine(); glm::vec4 color = {directionalComponent.color, 1.0f}; @@ -53,10 +53,9 @@ namespace ImNexo { directionalComponent.color = color; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("InspectorDirectionTable", 4, - ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + if (ImGui::BeginTable("InspectorDirectionTable", 4, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); @@ -68,13 +67,12 @@ namespace ImNexo { ImGui::PopStyleVar(); } - void PointLight( - nexo::components::PointLightComponent &pointComponent, - nexo::components::TransformComponent &pointTransform - ) { + void PointLight(nexo::components::PointLightComponent &pointComponent, + nexo::components::TransformComponent &pointTransform) + { ImGui::Spacing(); static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; + static bool showColorPicker = false; ImGui::Text("Color"); ImGui::SameLine(); glm::vec4 color = {pointComponent.color, 1.0f}; @@ -82,10 +80,9 @@ namespace ImNexo { pointComponent.color = color; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("InspectorPointTable", 4, - ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + if (ImGui::BeginTable("InspectorPointTable", 4, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); @@ -99,36 +96,33 @@ namespace ImNexo { ImGui::SameLine(); if (ImGui::DragFloat("##DistanceSlider", &pointComponent.maxDistance, 1.0f, 1.0f, 3250.0f)) { // Recompute the attenuation from the distance - auto [lin, quad] = nexo::math::computeAttenuationFromDistance(pointComponent.maxDistance); - pointComponent.constant = 1.0f; - pointComponent.linear = lin; + auto [lin, quad] = nexo::math::computeAttenuationFromDistance(pointComponent.maxDistance); + pointComponent.constant = 1.0f; + pointComponent.linear = lin; pointComponent.quadratic = quad; } - if (ImGui::IsItemActive()) - setItemActive(); - if (ImGui::IsItemActivated()) - setItemActivated(); - if (ImGui::IsItemDeactivated()) - setItemDeactivated(); + if (ImGui::IsItemActive()) setItemActive(); + if (ImGui::IsItemActivated()) setItemActivated(); + if (ImGui::IsItemDeactivated()) setItemDeactivated(); ImGui::PopStyleVar(); } - void SpotLight(nexo::components::SpotLightComponent &spotComponent, nexo::components::TransformComponent &spotTransform) + void SpotLight(nexo::components::SpotLightComponent &spotComponent, + nexo::components::TransformComponent &spotTransform) { ImGui::Spacing(); - static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; - ImGui::Text("Color"); - ImGui::SameLine(); - glm::vec4 color = {spotComponent.color, 1.0f}; - ColorEditor("##ColorEditor Spot light", &color, &colorPickerMode, &showColorPicker); - spotComponent.color = color; - - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("InspectorSpotTable", 4, - ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPicker = false; + ImGui::Text("Color"); + ImGui::SameLine(); + glm::vec4 color = {spotComponent.color, 1.0f}; + ColorEditor("##ColorEditor Spot light", &color, &colorPickerMode, &showColorPicker); + spotComponent.color = color; + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); + if (ImGui::BeginTable("InspectorSpotTable", 4, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); @@ -136,27 +130,25 @@ namespace ImNexo { RowDragFloat3("Direction", "X", "Y", "Z", &spotComponent.direction.x, -FLT_MAX, FLT_MAX, 0.1f); RowDragFloat3("Position", "X", "Y", "Z", &spotTransform.pos.x, -FLT_MAX, FLT_MAX, 0.1f); - ImGui::EndTable(); } - if (ImGui::BeginTable("InspectorCutOffSpotTable", 2, ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + if (ImGui::BeginTable("InspectorCutOffSpotTable", 2, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - if (RowDragFloat1("Distance", "", &spotComponent.maxDistance, 1.0f, 3250.0f, 1.0f)) - { - auto [lin, quad] = nexo::math::computeAttenuationFromDistance(spotComponent.maxDistance); - spotComponent.linear = lin; - spotComponent.quadratic = quad; + if (RowDragFloat1("Distance", "", &spotComponent.maxDistance, 1.0f, 3250.0f, 1.0f)) { + auto [lin, quad] = nexo::math::computeAttenuationFromDistance(spotComponent.maxDistance); + spotComponent.linear = lin; + spotComponent.quadratic = quad; } float innerCutOffDegrees = glm::degrees(glm::acos(spotComponent.cutOff)); float outerCutOffDegrees = glm::degrees(glm::acos(spotComponent.outerCutoff)); if (RowDragFloat1("Inner cut off", "", &innerCutOffDegrees, 0.0f, outerCutOffDegrees, 0.5f)) - spotComponent.cutOff = glm::cos(glm::radians(innerCutOffDegrees)); + spotComponent.cutOff = glm::cos(glm::radians(innerCutOffDegrees)); if (RowDragFloat1("Outer cut off", "", &outerCutOffDegrees, innerCutOffDegrees, 90.0f, 0.5f)) - spotComponent.outerCutoff = glm::cos(glm::radians(outerCutOffDegrees)); + spotComponent.outerCutoff = glm::cos(glm::radians(outerCutOffDegrees)); ImGui::EndTable(); } @@ -165,52 +157,50 @@ namespace ImNexo { } void Transform(nexo::components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler) - { - // Increase cell padding so rows have more space: - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - auto &pos = transformComponent.pos; - auto &size = transformComponent.size; - auto &quat = transformComponent.quat; - - if (ImGui::BeginTable("InspectorTransformTable", 4, - ImGuiTableFlags_SizingStretchProp)) - { - // Only the first column has a fixed width - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - RowDragFloat3("Position", "X", "Y", "Z", &pos.x); - - const glm::vec3 computedEuler = glm::degrees(glm::eulerAngles(quat)); - - lastDisplayedEuler = computedEuler; - glm::vec3 rotation = lastDisplayedEuler; - - // Draw the Rotation row. - // When the user edits the rotation, we compute the delta from the last displayed Euler, - // convert that delta into an incremental quaternion, and update the master quaternion. - if (RowDragFloat3("Rotation", "X", "Y", "Z", &rotation.x)) { - const glm::vec3 deltaEuler = rotation - lastDisplayedEuler; - const glm::quat deltaQuat = glm::quat(glm::radians(deltaEuler)); - quat = glm::normalize(deltaQuat * quat); - lastDisplayedEuler = glm::degrees(glm::eulerAngles(quat)); - } - RowDragFloat3("Scale", "X", "Y", "Z", &size.x); - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - } - - void Camera(nexo::components::CameraComponent &cameraComponent) - { + { + // Increase cell padding so rows have more space: ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("CameraInspectorViewPortParams", 4, - ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + auto &pos = transformComponent.pos; + auto &size = transformComponent.size; + auto &quat = transformComponent.quat; + + if (ImGui::BeginTable("InspectorTransformTable", 4, ImGuiTableFlags_SizingStretchProp)) { + // Only the first column has a fixed width + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + RowDragFloat3("Position", "X", "Y", "Z", &pos.x); + + const glm::vec3 computedEuler = glm::degrees(glm::eulerAngles(quat)); + + lastDisplayedEuler = computedEuler; + glm::vec3 rotation = lastDisplayedEuler; + + // Draw the Rotation row. + // When the user edits the rotation, we compute the delta from the last displayed Euler, + // convert that delta into an incremental quaternion, and update the master quaternion. + if (RowDragFloat3("Rotation", "X", "Y", "Z", &rotation.x)) { + const glm::vec3 deltaEuler = rotation - lastDisplayedEuler; + const auto deltaQuat = glm::quat(glm::radians(deltaEuler)); + quat = glm::normalize(deltaQuat * quat); + lastDisplayedEuler = glm::degrees(glm::eulerAngles(quat)); + } + RowDragFloat3("Scale", "X", "Y", "Z", &size.x); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + + void Camera(nexo::components::CameraComponent &cameraComponent) + { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); + if (ImGui::BeginTable("CameraInspectorViewPortParams", 4, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Lock", ImGuiTableColumnFlags_WidthStretch); @@ -219,27 +209,27 @@ namespace ImNexo { const std::vector textBadgeColors; const bool disabled = !cameraComponent.viewportLocked; - if (disabled) - ImGui::BeginDisabled(); - bool toResize = RowDragFloat2("Viewport size", "W", "H", &viewPort.x, -FLT_MAX, FLT_MAX, 1.0f, badgeColors, textBadgeColors, disabled); + if (disabled) ImGui::BeginDisabled(); + const bool toResize = RowDragFloat2("Viewport size", "W", "H", &viewPort.x, -FLT_MAX, FLT_MAX, 1.0f, badgeColors, + textBadgeColors, disabled); if (toResize && cameraComponent.viewportLocked) cameraComponent.resize(static_cast(viewPort.x), static_cast(viewPort.y)); - if (disabled) - ImGui::EndDisabled(); + if (disabled) ImGui::EndDisabled(); ImGui::TableSetColumnIndex(3); // Lock button - const std::string lockBtnLabel = !cameraComponent.viewportLocked ? ICON_FA_LOCK "##ViewPortSettings" : ICON_FA_UNLOCK "##ViewPortSettings"; + const std::string lockBtnLabel = !cameraComponent.viewportLocked ? ICON_FA_LOCK "##ViewPortSettings" : + ICON_FA_UNLOCK "##ViewPortSettings"; if (Button(lockBtnLabel)) { cameraComponent.viewportLocked = !cameraComponent.viewportLocked; } ImGui::EndTable(); } - if (ImGui::BeginTable("InspectorCameraVariables", 2, ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + if (ImGui::BeginTable("InspectorCameraVariables", 2, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); RowDragFloat1("FOV", "", &cameraComponent.fov, 30.0f, 120.0f, 0.3f); @@ -252,57 +242,50 @@ namespace ImNexo { ImGui::Spacing(); static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; + static bool showColorPicker = false; ImGui::AlignTextToFramePadding(); ImGui::Text("Clear Color"); ImGui::SameLine(); ColorEditor("##ColorEditor Spot light", &cameraComponent.clearColor, &colorPickerMode, &showColorPicker); - } + } - void CameraTarget(nexo::components::PerspectiveCameraTarget &cameraTargetComponent) - { + void CameraTarget(nexo::components::PerspectiveCameraTarget &cameraTargetComponent) + { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("InspectorControllerTable", 2, - ImGuiTableFlags_SizingStretchProp)) - { + if (ImGui::BeginTable("InspectorControllerTable", 2, ImGuiTableFlags_SizingStretchProp)) { auto &selector = nexo::editor::Selector::get(); - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); const std::vector &entities = nexo::Application::m_coordinator->getAllEntitiesWith< - nexo::components::TransformComponent, - nexo::ecs::Exclude, - nexo::ecs::Exclude, - nexo::ecs::Exclude, - nexo::ecs::Exclude, - nexo::ecs::Exclude>(); + nexo::components::TransformComponent, nexo::ecs::Exclude, + nexo::ecs::Exclude, + nexo::ecs::Exclude, + nexo::ecs::Exclude, + nexo::ecs::Exclude>(); RowDragFloat1("Mouse sensitivity", "", &cameraTargetComponent.mouseSensitivity, 0.1f); RowDragFloat1("Distance", "", &cameraTargetComponent.distance, 0.1f); RowEntityDropdown( - "Target Entity", - cameraTargetComponent.targetEntity, entities, - [&selector](const nexo::ecs::Entity e) { + "Target Entity", cameraTargetComponent.targetEntity, entities, [&selector](const nexo::ecs::Entity e) { return selector.getUiHandle( nexo::Application::m_coordinator->getComponent(e).uuid, - std::to_string(e) - ); - } - ); + std::to_string(e)); + }); ImGui::EndTable(); } ImGui::PopStyleVar(); - } + } - void CameraController(nexo::components::PerspectiveCameraController &cameraControllerComponent) - { + void CameraController(nexo::components::PerspectiveCameraController &cameraControllerComponent) + { ImGui::Spacing(); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("InspectorControllerTable", 2, - ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + if (ImGui::BeginTable("InspectorControllerTable", 2, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("##Label", + ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); float mouseSensitivity = cameraControllerComponent.mouseSensitivity; @@ -312,6 +295,6 @@ namespace ImNexo { ImGui::EndTable(); } ImGui::PopStyleVar(); - } + } -} +} // namespace ImNexo diff --git a/editor/src/ImNexo/EntityProperties.hpp b/editor/src/ImNexo/EntityProperties.hpp index 3296b65d2..104220afc 100644 --- a/editor/src/ImNexo/EntityProperties.hpp +++ b/editor/src/ImNexo/EntityProperties.hpp @@ -13,20 +13,63 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include "components/Camera.hpp" #include "components/Light.hpp" #include "components/Transform.hpp" -#include "components/Camera.hpp" namespace ImNexo { + /** + * @brief Renders and handles the ambient light component editor UI. + * + * Creates a color picker for adjusting the ambient light color. + * The color is represented as a vec3 in the component, but displayed + * as a vec4 in the UI for compatibility with ImGui's color picker. + * + * @param ambientComponent Reference to the ambient light component being edited + */ void Ambient(nexo::components::AmbientLightComponent &ambientComponent); + /** + * @brief Renders and handles the directional light component editor UI. + * + * Creates a color picker for adjusting the directional light color, + * and a table-based editor for the light's direction vector (x, y, z). + * The direction is represented as a vec3 in the component. + * + * @param directionalComponent Reference to the directional light component being edited + */ void DirectionalLight(nexo::components::DirectionalLightComponent &directionalComponent); - void PointLight(nexo::components::PointLightComponent &pointComponent, nexo::components::TransformComponent &pointTransform); - - void SpotLight(nexo::components::SpotLightComponent &spotComponent, nexo::components::TransformComponent &spotTransform); + /** + * @brief Renders and handles the point light component editor UI. + * + * Creates a color picker for adjusting the point light color, + * and a table-based editor for the light's position (x, y, z) + * and attenuation parameters (constant, linear, quadratic). + * The position is represented as a vec3 in the transform component, + * while attenuation parameters are floats in the point light component. + * + * @param pointComponent Reference to the point light component being edited + * @param pointTransform Reference to the transform component for position editing + */ + void PointLight(nexo::components::PointLightComponent &pointComponent, + nexo::components::TransformComponent &pointTransform); + /** + * @brief Renders and handles the spotlight component editor UI. + * + * Creates a color picker for adjusting the spotlight color, + * and a table-based editor for the light's position (x, y, z), + * direction (x, y, z), and cutoff angles (inner and outer). + * The position is represented as a vec3 in the transform component, + * while direction and cutoff angles are part of the spotlight component. + * + * @param spotComponent Reference to the spotlight component being edited + * @param spotTransform Reference to the transform component for position editing + */ + void SpotLight(nexo::components::SpotLightComponent &spotComponent, + nexo::components::TransformComponent &spotTransform); /** * @brief Renders and handles the transform component editor UI. @@ -41,7 +84,6 @@ namespace ImNexo { */ void Transform(nexo::components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler); - /** * @brief Renders and handles the camera component editor UI. * @@ -77,11 +119,11 @@ namespace ImNexo { * @brief Renders and handles the camera controller component editor UI. * * Creates a table-based editor for a free-moving camera controller component. - * Currently includes only mouse sensitivity adjustment, which controls how + * Currently, includes only mouse sensitivity adjustment, which controls how * quickly the camera rotates in response to mouse movement. * * @param cameraControllerComponent Reference to the camera controller component being edited */ void CameraController(nexo::components::PerspectiveCameraController &cameraControllerComponent); -} +} // namespace ImNexo diff --git a/editor/src/ImNexo/Guard.hpp b/editor/src/ImNexo/Guard.hpp index cba1db059..ba530f078 100644 --- a/editor/src/ImNexo/Guard.hpp +++ b/editor/src/ImNexo/Guard.hpp @@ -14,8 +14,8 @@ #pragma once #include -#include #include +#include namespace ImNexo { /** @@ -25,91 +25,89 @@ namespace ImNexo { * ensuring the style state is properly restored even when exceptions occur. Supports * chaining multiple color changes with the push() method. */ - class StyleGuard { - public: - /** - * @brief Constructs a StyleGuard and pushes the initial style color. - * - * @param col The ImGui color enumeration to modify - * @param color The new color value (0 to skip pushing) - */ - explicit StyleGuard(const ImGuiCol_ col, const ImU32 color) - { - if (color != 0) { - m_colorIndices.push_back(col); - ImGui::PushStyleColor(col, color); - } - } - - /** - * @brief Delete copy constructor since we can't precisely duplicate the pushed styles. - * - * We only track which color indices were pushed, but not their actual values. - * Without storing the color values, we can't properly recreate the pushed styles. - */ - StyleGuard(const StyleGuard&) = delete; - - /** - * @brief Delete copy assignment operator for the same reason as the copy constructor. - */ - StyleGuard& operator=(const StyleGuard&) = delete; - - /** - * @brief Move constructor - transfers ownership of pushed styles. - */ - StyleGuard(StyleGuard&& other) noexcept : m_colorIndices(std::move(other.m_colorIndices)) - { - // The moved-from object should no longer pop any style colors - other.m_colorIndices.clear(); - } - - /** - * @brief Move assignment operator - transfers ownership of pushed styles. - */ - StyleGuard& operator=(StyleGuard&& other) noexcept - { - if (this != &other) { - // Pop our current styles - if (!m_colorIndices.empty()) - ImGui::PopStyleColor(static_cast(m_colorIndices.size())); - - // Take ownership of other's styles - m_colorIndices = std::move(other.m_colorIndices); - - // Ensure other doesn't pop our newly acquired styles - other.m_colorIndices.clear(); - } - return *this; - } - - /** - * @brief Pushes an additional style color to the guard. - * - * @param col The ImGui color enumeration to modify - * @param color The new color value (0 to skip pushing) - * @return Reference to this guard for method chaining - */ - StyleGuard& push(const ImGuiCol_ col, const ImU32 color) - { - if (color != 0) { - m_colorIndices.push_back(col); - ImGui::PushStyleColor(col, color); - } - return *this; - } - - /** - * @brief Destructor that automatically pops all pushed style colors. - */ - ~StyleGuard() - { - if (!m_colorIndices.empty()) - ImGui::PopStyleColor(static_cast(m_colorIndices.size())); - } - - private: - std::vector m_colorIndices; ///< Tracks which color indices were pushed - }; + class StyleGuard { + public: + /** + * @brief Constructs a StyleGuard and pushes the initial style color. + * + * @param col The ImGui color enumeration to modify + * @param color The new color value (0 to skip pushing) + */ + explicit StyleGuard(const ImGuiCol_ col, const ImU32 color) + { + if (color != 0) { + m_colorIndices.push_back(col); + ImGui::PushStyleColor(col, color); + } + } + + /** + * @brief Delete copy constructor since we can't precisely duplicate the pushed styles. + * + * We only track which color indices were pushed, but not their actual values. + * Without storing the color values, we can't properly recreate the pushed styles. + */ + StyleGuard(const StyleGuard&) = delete; + + /** + * @brief Delete copy assignment operator for the same reason as the copy constructor. + */ + StyleGuard& operator=(const StyleGuard&) = delete; + + /** + * @brief Move constructor - transfers ownership of pushed styles. + */ + StyleGuard(StyleGuard&& other) noexcept : m_colorIndices(std::move(other.m_colorIndices)) + { + // The moved-from object should no longer pop any style colors + other.m_colorIndices.clear(); + } + + /** + * @brief Move assignment operator - transfers ownership of pushed styles. + */ + StyleGuard& operator=(StyleGuard&& other) noexcept + { + if (this != &other) { + // Pop our current styles + if (!m_colorIndices.empty()) ImGui::PopStyleColor(static_cast(m_colorIndices.size())); + + // Take ownership of other's styles + m_colorIndices = std::move(other.m_colorIndices); + + // Ensure other doesn't pop our newly acquired styles + other.m_colorIndices.clear(); + } + return *this; + } + + /** + * @brief Pushes an additional style color to the guard. + * + * @param col The ImGui color enumeration to modify + * @param color The new color value (0 to skip pushing) + * @return Reference to this guard for method chaining + */ + StyleGuard& push(const ImGuiCol_ col, const ImU32 color) + { + if (color != 0) { + m_colorIndices.push_back(col); + ImGui::PushStyleColor(col, color); + } + return *this; + } + + /** + * @brief Destructor that automatically pops all pushed style colors. + */ + ~StyleGuard() + { + if (!m_colorIndices.empty()) ImGui::PopStyleColor(static_cast(m_colorIndices.size())); + } + + private: + std::vector m_colorIndices; ///< Tracks which color indices were pushed + }; /** * @brief Guard class for managing ImGui style variables. @@ -118,186 +116,182 @@ namespace ImNexo { * ensuring the style state is properly restored even when exceptions occur. Supports * chaining multiple variable changes with the push() method. */ - class StyleVarGuard { - public: - /** - * @brief Constructs a StyleVarGuard and pushes an initial vector style variable. - * - * @param var The ImGui style variable enumeration to modify - * @param value The new vector value - */ - explicit StyleVarGuard(const ImGuiStyleVar_ var, const ImVec2 value) - { - m_varCount++; - ImGui::PushStyleVar(var, value); - } - - /** - * @brief Constructs a StyleVarGuard and pushes an initial scalar style variable. - * - * @param var The ImGui style variable enumeration to modify - * @param value The new scalar value - */ - explicit StyleVarGuard(const ImGuiStyleVar_ var, const float value) - { - m_varCount++; - ImGui::PushStyleVar(var, value); - } - - /** - * @brief Delete copy constructor since we can't recreate the exact pushed styles. - * - * The class only tracks how many styles were pushed, not which ones or their values. - */ - StyleVarGuard(const StyleVarGuard&) = delete; - - /** - * @brief Delete copy assignment operator for the same reason as the copy constructor. - */ - StyleVarGuard& operator=(const StyleVarGuard&) = delete; - - /** - * @brief Move constructor - transfers responsibility for popping style variables. - */ - StyleVarGuard(StyleVarGuard&& other) noexcept : m_varCount(other.m_varCount) - { - // Prevent the moved-from object from popping our styles - other.m_varCount = 0; - } - - /** - * @brief Move assignment operator - transfers responsibility for popping style variables. - */ - StyleVarGuard& operator=(StyleVarGuard&& other) noexcept - { - if (this != &other) { - // Pop our current styles - if (m_varCount > 0) - ImGui::PopStyleVar(m_varCount); - - // Take over responsibility for other's styles - m_varCount = other.m_varCount; - - // Prevent the moved-from object from popping our styles - other.m_varCount = 0; - } - return *this; - } - - /** - * @brief Pushes an additional vector style variable to the guard. - * - * @param var The ImGui style variable enumeration to modify - * @param value The new vector value - * @return Reference to this guard for method chaining - */ - StyleVarGuard& push(const ImGuiStyleVar_ var, const ImVec2 value) - { - m_varCount++; - ImGui::PushStyleVar(var, value); - return *this; - } - - /** - * @brief Pushes an additional scalar style variable to the guard. - * - * @param var The ImGui style variable enumeration to modify - * @param value The new scalar value - * @return Reference to this guard for method chaining - */ - StyleVarGuard& push(const ImGuiStyleVar_ var, const float value) - { - m_varCount++; - ImGui::PushStyleVar(var, value); - return *this; - } - - /** - * @brief Destructor that automatically pops all pushed style variables. - */ - ~StyleVarGuard() - { - if (m_varCount > 0) - ImGui::PopStyleVar(m_varCount); - } - - private: - int m_varCount = 0; ///< Counts how many style variables were pushed - }; - - /** - * @brief Guard class for managing ImGui ID stack. - * - * Automatically pushes an ID to the ImGui ID stack on construction and pops - * it on destruction, ensuring proper nesting and scoping of unique identifiers - * even when exceptions occur. - */ - class IdGuard { - public: - /** - * @brief Constructs an IdGuard and pushes the specified ID. - * - * @param id The string identifier to push onto the ImGui ID stack - */ - explicit IdGuard(const std::string& id) - { - ImGui::PushID(id.c_str()); - m_pushed = true; - } - - /** - * @brief Delete copy constructor since we can't duplicate the pushed ID. - * - * We don't store the ID string after construction, so we can't recreate - * the exact same state in a copy. - */ - IdGuard(const IdGuard&) = delete; - - /** - * @brief Delete copy assignment operator for the same reason as copy constructor. - */ - IdGuard& operator=(const IdGuard&) = delete; - - /** - * @brief Move constructor - transfers responsibility for popping the ID. - */ - IdGuard(IdGuard&& other) noexcept : m_pushed(other.m_pushed) - { - // Prevent the moved-from object from popping the ID - other.m_pushed = false; - } - - /** - * @brief Move assignment operator - transfers responsibility for popping the ID. - */ - IdGuard& operator=(IdGuard&& other) noexcept - { - if (this != &other) { - // Pop our current ID if we pushed one - if (m_pushed) - ImGui::PopID(); - - // Take over responsibility for other's ID - m_pushed = other.m_pushed; - - // Prevent the moved-from object from popping the ID - other.m_pushed = false; - } - return *this; - } - - /** - * @brief Destructor that automatically pops the pushed ID. - */ - ~IdGuard() - { - if (m_pushed) - ImGui::PopID(); - } - - private: - bool m_pushed = false; ///< Tracks whether an ID was pushed and needs to be popped - }; + class StyleVarGuard { + public: + /** + * @brief Constructs a StyleVarGuard and pushes an initial vector style variable. + * + * @param var The ImGui style variable enumeration to modify + * @param value The new vector value + */ + explicit StyleVarGuard(const ImGuiStyleVar_ var, const ImVec2 value) + { + m_varCount++; + ImGui::PushStyleVar(var, value); + } + + /** + * @brief Constructs a StyleVarGuard and pushes an initial scalar style variable. + * + * @param var The ImGui style variable enumeration to modify + * @param value The new scalar value + */ + explicit StyleVarGuard(const ImGuiStyleVar_ var, const float value) + { + m_varCount++; + ImGui::PushStyleVar(var, value); + } + + /** + * @brief Delete copy constructor since we can't recreate the exact pushed styles. + * + * The class only tracks how many styles were pushed, not which ones or their values. + */ + StyleVarGuard(const StyleVarGuard&) = delete; + + /** + * @brief Delete copy assignment operator for the same reason as the copy constructor. + */ + StyleVarGuard& operator=(const StyleVarGuard&) = delete; + + /** + * @brief Move constructor - transfers responsibility for popping style variables. + */ + StyleVarGuard(StyleVarGuard&& other) noexcept : m_varCount(other.m_varCount) + { + // Prevent the moved-from object from popping our styles + other.m_varCount = 0; + } + + /** + * @brief Move assignment operator - transfers responsibility for popping style variables. + */ + StyleVarGuard& operator=(StyleVarGuard&& other) noexcept + { + if (this != &other) { + // Pop our current styles + if (m_varCount > 0) ImGui::PopStyleVar(m_varCount); + + // Take over responsibility for other's styles + m_varCount = other.m_varCount; + + // Prevent the moved-from object from popping our styles + other.m_varCount = 0; + } + return *this; + } + + /** + * @brief Pushes an additional vector style variable to the guard. + * + * @param var The ImGui style variable enumeration to modify + * @param value The new vector value + * @return Reference to this guard for method chaining + */ + StyleVarGuard& push(const ImGuiStyleVar_ var, const ImVec2 value) + { + m_varCount++; + ImGui::PushStyleVar(var, value); + return *this; + } + + /** + * @brief Pushes an additional scalar style variable to the guard. + * + * @param var The ImGui style variable enumeration to modify + * @param value The new scalar value + * @return Reference to this guard for method chaining + */ + StyleVarGuard& push(const ImGuiStyleVar_ var, const float value) + { + m_varCount++; + ImGui::PushStyleVar(var, value); + return *this; + } + + /** + * @brief Destructor that automatically pops all pushed style variables. + */ + ~StyleVarGuard() + { + if (m_varCount > 0) ImGui::PopStyleVar(m_varCount); + } + + private: + int m_varCount = 0; ///< Counts how many style variables were pushed + }; + + /** + * @brief Guard class for managing ImGui ID stack. + * + * Automatically pushes an ID to the ImGui ID stack on construction and pops + * it on destruction, ensuring proper nesting and scoping of unique identifiers + * even when exceptions occur. + */ + class IdGuard { + public: + /** + * @brief Constructs an IdGuard and pushes the specified ID. + * + * @param id The string identifier to push onto the ImGui ID stack + */ + explicit IdGuard(const std::string& id) + { + ImGui::PushID(id.c_str()); + m_pushed = true; + } + + /** + * @brief Delete copy constructor since we can't duplicate the pushed ID. + * + * We don't store the ID string after construction, so we can't recreate + * the exact same state in a copy. + */ + IdGuard(const IdGuard&) = delete; + + /** + * @brief Delete copy assignment operator for the same reason as copy constructor. + */ + IdGuard& operator=(const IdGuard&) = delete; + + /** + * @brief Move constructor - transfers responsibility for popping the ID. + */ + IdGuard(IdGuard&& other) noexcept : m_pushed(other.m_pushed) + { + // Prevent the moved-from object from popping the ID + other.m_pushed = false; + } + + /** + * @brief Move assignment operator - transfers responsibility for popping the ID. + */ + IdGuard& operator=(IdGuard&& other) noexcept + { + if (this != &other) { + // Pop our current ID if we pushed one + if (m_pushed) ImGui::PopID(); + + // Take over responsibility for other's ID + m_pushed = other.m_pushed; + + // Prevent the moved-from object from popping the ID + other.m_pushed = false; + } + return *this; + } + + /** + * @brief Destructor that automatically pops the pushed ID. + */ + ~IdGuard() + { + if (m_pushed) ImGui::PopID(); + } + + private: + bool m_pushed = false; ///< Tracks whether an ID was pushed and needs to be popped + }; /** * @brief Guard class for managing ImGui font scaling. @@ -305,70 +299,67 @@ namespace ImNexo { * Temporarily changes the window font scale factor and restores it * to the default scale (1.0) when the guard goes out of scope. */ - class FontScaleGuard { - public: - /** - * @brief Constructs a FontScaleGuard and sets the font scale. - * - * @param scale The scaling factor to apply to the current window's font - */ - explicit FontScaleGuard(const float scale) - { - ImGui::SetWindowFontScale(scale); - } - - /** - * @brief Delete copy constructor since we can't properly duplicate this action. - * - * Copying this guard would require knowledge of the current window context - * and possibly interfere with other active guards. - */ - FontScaleGuard(const FontScaleGuard&) = delete; - - /** - * @brief Delete copy assignment operator for the same reasons as copy constructor. - */ - FontScaleGuard& operator=(const FontScaleGuard&) = delete; - - /** - * @brief Move constructor - transfers responsibility for resetting the font scale. - */ - FontScaleGuard(FontScaleGuard&& other) noexcept - : m_active(other.m_active) - { - // Prevent the moved-from object from resetting the font scale - other.m_active = false; - } - - /** - * @brief Move assignment operator - transfers responsibility for resetting the font scale. - */ - FontScaleGuard& operator=(FontScaleGuard&& other) noexcept - { - if (this != &other) { - // Reset our scale if we're currently active - if (m_active) - ImGui::SetWindowFontScale(1.0f); - - // Take over responsibility - m_active = other.m_active; - - // Prevent the moved-from object from resetting the scale - other.m_active = false; - } - return *this; - } - - /** - * @brief Destructor that automatically resets the font scale to default. - */ - ~FontScaleGuard() - { - if (m_active) - ImGui::SetWindowFontScale(1.0f); - } - - private: - bool m_active = true; ///< Tracks whether this guard is responsible for resetting the scale - }; -} + class FontScaleGuard { + public: + /** + * @brief Constructs a FontScaleGuard and sets the font scale. + * + * @param scale The scaling factor to apply to the current window's font + */ + explicit FontScaleGuard(const float scale) + { + ImGui::SetWindowFontScale(scale); + } + + /** + * @brief Delete copy constructor since we can't properly duplicate this action. + * + * Copying this guard would require knowledge of the current window context + * and possibly interfere with other active guards. + */ + FontScaleGuard(const FontScaleGuard&) = delete; + + /** + * @brief Delete copy assignment operator for the same reasons as copy constructor. + */ + FontScaleGuard& operator=(const FontScaleGuard&) = delete; + + /** + * @brief Move constructor - transfers responsibility for resetting the font scale. + */ + FontScaleGuard(FontScaleGuard&& other) noexcept : m_active(other.m_active) + { + // Prevent the moved-from object from resetting the font scale + other.m_active = false; + } + + /** + * @brief Move assignment operator - transfers responsibility for resetting the font scale. + */ + FontScaleGuard& operator=(FontScaleGuard&& other) noexcept + { + if (this != &other) { + // Reset our scale if we're currently active + if (m_active) ImGui::SetWindowFontScale(1.0f); + + // Take over responsibility + m_active = other.m_active; + + // Prevent the moved-from object from resetting the scale + other.m_active = false; + } + return *this; + } + + /** + * @brief Destructor that automatically resets the font scale to default. + */ + ~FontScaleGuard() + { + if (m_active) ImGui::SetWindowFontScale(1.0f); + } + + private: + bool m_active = true; ///< Tracks whether this guard is responsible for resetting the scale + }; +} // namespace ImNexo diff --git a/editor/src/ImNexo/ImNexo.cpp b/editor/src/ImNexo/ImNexo.cpp index a08300048..ad5278526 100644 --- a/editor/src/ImNexo/ImNexo.cpp +++ b/editor/src/ImNexo/ImNexo.cpp @@ -20,6 +20,12 @@ namespace ImNexo { return g_isItemActive; } + /** + * @brief Reset the active state of the last item. + * + * This function resets the internal state to indicate that the last item is no longer active. + * It should be called at the end of each frame to clear the active state for the next frame. + */ static void resetItemActiveState() { g_isItemActive = false; @@ -35,6 +41,13 @@ namespace ImNexo { return g_isItemActivated; } + /** + * @brief Reset the activated state of the last item. + * + * This function resets the internal state to indicate that the last item is no longer + * considered activated. It should be called at the end of each frame to clear the + * activated state for the next frame. + */ static void resetItemActivatedState() { g_isItemActivated = false; @@ -50,6 +63,13 @@ namespace ImNexo { return g_isItemDeactivated; } + /** + * @brief Reset the deactivated state of the last item. + * + * This function resets the internal state to indicate that the last item is no longer + * considered deactivated. It should be called at the end of each frame to clear the + * deactivated state for the next frame. + */ static void resetItemDeactivatedState() { g_isItemDeactivated = false; @@ -67,4 +87,4 @@ namespace ImNexo { resetItemDeactivatedState(); } -} +} // namespace ImNexo diff --git a/editor/src/ImNexo/ImNexo.hpp b/editor/src/ImNexo/ImNexo.hpp index 86ca20be9..e28a3b887 100644 --- a/editor/src/ImNexo/ImNexo.hpp +++ b/editor/src/ImNexo/ImNexo.hpp @@ -15,19 +15,73 @@ namespace ImNexo { - inline bool g_isItemActive = false; - inline bool g_isItemActivated = false; + inline bool g_isItemActive = false; + inline bool g_isItemActivated = false; inline bool g_isItemDeactivated = false; + /** + * @brief Check if the last item is currently active. + * + * This function returns true if the last item is currently active, meaning it is being interacted with. + * An item is considered active when it is clicked or focused. + * + * @return true if the last item is active, false otherwise. + */ bool isItemActive(); + + /** + * @brief Mark the last item as active. + * + * This function sets the internal state to indicate that the last item is currently active. + * An item is considered active when it is being interacted with, such as when it is clicked + * or focused. + */ void setItemActive(); + /** + * @brief Check if the last item was activated (e.g., clicked or focused). + * + * This function returns true if the last item was activated during the current frame. + * Activation typically occurs when an item is clicked or receives keyboard focus. + * + * @return true if the last item was activated, false otherwise. + */ bool isItemActivated(); + /** + * @brief Mark the last item as activated. + * + * This function sets the internal state to indicate that the last item was activated + * during the current frame. Activation typically occurs when an item is clicked or + * receives keyboard focus. + */ void setItemActivated(); + /** + * @brief Check if the last item was deactivated (e.g., lost focus or interaction). + * + * This function returns true if the last item was deactivated during the current frame. + * Deactivation typically occurs when an item loses focus or interaction. + * + * @return true if the last item was deactivated, false otherwise. + */ bool isItemDeactivated(); + + /** + * @brief Mark the last item as deactivated. + * + * This function sets the internal state to indicate that the last item was deactivated + * during the current frame. Deactivation typically occurs when an item loses focus or + * interaction. + */ void setItemDeactivated(); + /** + * @brief Reset all item states (active, activated, deactivated). + * + * This function resets the internal states for active, activated, and deactivated + * items. It should be called at the end of each frame to clear all item states + * for the next frame. + */ void resetItemStates(); -} +} // namespace ImNexo diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index 6ccd53684..d7c375bb8 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -12,160 +12,177 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "ImNexo/ImNexo.hpp" -#include "Nexo.hpp" #include "Panels.hpp" +#include "CameraFactory.hpp" #include "Elements.hpp" -#include "Widgets.hpp" #include "EntityProperties.hpp" -#include "CameraFactory.hpp" -#include "Path.hpp" #include "IconsFontAwesome.h" +#include "ImNexo/ImNexo.hpp" +#include "Nexo.hpp" +#include "Widgets.hpp" #include "assets/AssetCatalog.hpp" #include "components/Camera.hpp" #include "components/Transform.hpp" #include "components/Uuid.hpp" +#include "context/ActionManager.hpp" #include "context/Selector.hpp" #include "context/actions/EntityActions.hpp" #include "utils/EditorProps.hpp" -#include "context/ActionManager.hpp" namespace ImNexo { + /** + * @brief Renders the material inspector UI for editing material properties. + * + * Provides controls for selecting shaders, rendering modes, and editing + * textures and colors for albedo and specular properties. + * + * @param material Reference to the material component being edited + * @return true if any property was modified, false otherwise + */ bool MaterialInspector(nexo::components::Material &material) - { - bool modified = false; - // --- Shader Selection --- - ImGui::BeginGroup(); - { - ImGui::Text("Shader:"); - ImGui::SameLine(); - - static int currentShaderIndex = 0; - const char* shaderOptions[] = { "Standard", "Unlit", "CustomPBR" }; - const float availableWidth = ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(availableWidth); - - if (ImGui::Combo("##ShaderCombo", ¤tShaderIndex, shaderOptions, IM_ARRAYSIZE(shaderOptions))) - { - //TODO: implement shader selection - } - } - ImGui::EndGroup(); - ImGui::Spacing(); - - // --- Rendering mode selection --- - ImGui::Text("Rendering mode:"); - ImGui::SameLine(); - static int currentRenderingModeIndex = 0; - const char* renderingModeOptions[] = { "Opaque", "Transparent", "Refraction" }; - const float availableWidth = ImGui::GetContentRegionAvail().x; - - ImGui::SetNextItemWidth(availableWidth); - if (ImGui::Combo("##RenderingModeCombo", ¤tRenderingModeIndex, renderingModeOptions, IM_ARRAYSIZE(renderingModeOptions))) - { - //TODO: implement rendering mode - } - - auto& catalog = nexo::assets::AssetCatalog::getInstance(); - // --- Albedo texture --- + { + bool modified = false; + // --- Shader Selection --- + ImGui::BeginGroup(); + { + ImGui::Text("Shader:"); + ImGui::SameLine(); + + static int currentShaderIndex = 0; + const char *shaderOptions[] = {"Standard", "Unlit", "CustomPBR"}; + const float availableWidth = ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(availableWidth); + + if (ImGui::Combo("##ShaderCombo", ¤tShaderIndex, shaderOptions, IM_ARRAYSIZE(shaderOptions))) { + // TODO: implement shader selection + } + } + ImGui::EndGroup(); + ImGui::Spacing(); + + // --- Rendering mode selection --- + ImGui::Text("Rendering mode:"); + ImGui::SameLine(); + static int currentRenderingModeIndex = 0; + const char *renderingModeOptions[] = {"Opaque", "Transparent", "Refraction"}; + const float availableWidth = ImGui::GetContentRegionAvail().x; + + ImGui::SetNextItemWidth(availableWidth); + if (ImGui::Combo("##RenderingModeCombo", ¤tRenderingModeIndex, renderingModeOptions, + IM_ARRAYSIZE(renderingModeOptions))) { + // TODO: implement rendering mode + } + + auto &catalog = nexo::assets::AssetCatalog::getInstance(); + // --- Albedo texture --- { static ImGuiColorEditFlags colorPickerModeAlbedo = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPickerAlbedo = false; - const auto asset = material.albedoTexture.lock(); - const auto albedoTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; - - std::filesystem::path newTexturePath; - if (TextureButton("Albedo texture", albedoTexture, newTexturePath) - && !newTexturePath.empty()) { - // TODO: /!\ This is not futureproof, this would modify the texture for every asset that use this material - const auto newTexture = catalog.createAsset( - nexo::assets::AssetLocation(newTexturePath.filename().string()), - newTexturePath - ); - if (newTexture) - material.albedoTexture = newTexture; - } - ImGui::SameLine(); - modified = ColorEditor("##ColorEditor Albedo texture", &material.albedoColor, &colorPickerModeAlbedo, &showColorPickerAlbedo) || modified; + static bool showColorPickerAlbedo = false; + const auto asset = material.albedoTexture.lock(); + const auto albedoTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; + + std::filesystem::path newTexturePath; + if (TextureButton("Albedo texture", albedoTexture, newTexturePath) && !newTexturePath.empty()) { + // TODO: /!\ This is not future proof, this would modify the texture for every asset that use this + // material + const auto newTexture = catalog.createAsset( + nexo::assets::AssetLocation(newTexturePath.filename().string()), newTexturePath); + if (newTexture) material.albedoTexture = newTexture; + } + ImGui::SameLine(); + modified = ColorEditor("##ColorEditor Albedo texture", &material.albedoColor, &colorPickerModeAlbedo, + &showColorPickerAlbedo) || + modified; } - // --- Specular texture --- + // --- Specular texture --- { static ImGuiColorEditFlags colorPickerModeSpecular = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPickerSpecular = false; - const auto asset = material.metallicMap.lock(); - const auto metallicTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; - - std::filesystem::path newTexturePath; - if (TextureButton("Specular texture", metallicTexture, newTexturePath) - && !newTexturePath.empty()) { - // TODO: /!\ This is not futureproof, this would modify the texture for every asset that use this material - const auto newTexture = catalog.createAsset( - nexo::assets::AssetLocation(newTexturePath.filename().string()), - newTexturePath - ); - if (newTexture) - material.metallicMap = newTexture; - } - ImGui::SameLine(); - modified = ColorEditor("##ColorEditor Specular texture", &material.specularColor, &colorPickerModeSpecular, &showColorPickerSpecular) || modified; - } + static bool showColorPickerSpecular = false; + const auto asset = material.metallicMap.lock(); + const auto metallicTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; + + std::filesystem::path newTexturePath; + if (TextureButton("Specular texture", metallicTexture, newTexturePath) && !newTexturePath.empty()) { + // TODO: /!\ This is not future proof, this would modify the texture for every asset that use this + // material + const auto newTexture = catalog.createAsset( + nexo::assets::AssetLocation(newTexturePath.filename().string()), newTexturePath); + if (newTexture) material.metallicMap = newTexture; + } + ImGui::SameLine(); + modified = ColorEditor("##ColorEditor Specular texture", &material.specularColor, &colorPickerModeSpecular, + &showColorPickerSpecular) || + modified; + } return modified; - } + } /** - * @brief Creates a default perspective camera for the camera inspector preview. - * - * Sets up a perspective camera with a framebuffer for rendering the preview view. - * Also adds a billboard with a camera icon for visualization in the scene. - * - * @param sceneId The ID of the scene where the camera should be created - * @param sceneViewportSize The dimensions to use for the camera's framebuffer - * @return The entity ID of the created camera - */ - static nexo::ecs::Entity createDefaultPerspectiveCamera(const nexo::scene::SceneId sceneId, const ImVec2 sceneViewportSize) - { + * @brief Creates a default perspective camera for the camera inspector preview. + * + * Sets up a perspective camera with a framebuffer for rendering the preview view. + * Also adds a billboard with a camera icon for visualization in the scene. + * + * @param sceneId The ID of the scene where the camera should be created + * @param sceneViewportSize The dimensions to use for the camera's framebuffer + * @return The entity ID of the created camera + */ + static nexo::ecs::Entity createDefaultPerspectiveCamera(const nexo::scene::SceneId sceneId, + const ImVec2 sceneViewportSize) + { auto &app = nexo::getApp(); nexo::renderer::NxFramebufferSpecs framebufferSpecs; - framebufferSpecs.attachments = { - nexo::renderer::NxFrameBufferTextureFormats::RGBA8, nexo::renderer::NxFrameBufferTextureFormats::RED_INTEGER, nexo::renderer::NxFrameBufferTextureFormats::Depth - }; + framebufferSpecs.attachments = {nexo::renderer::NxFrameBufferTextureFormats::RGBA8, + nexo::renderer::NxFrameBufferTextureFormats::RED_INTEGER, + nexo::renderer::NxFrameBufferTextureFormats::Depth}; // Define layout: 60% for inspector, 40% for preview - framebufferSpecs.width = static_cast(sceneViewportSize.x); - framebufferSpecs.height = static_cast(sceneViewportSize.y); - const auto renderTarget = nexo::renderer::NxFramebuffer::create(framebufferSpecs); - const nexo::ecs::Entity defaultCamera = nexo::CameraFactory::createPerspectiveCamera({0.0f, 0.0f, -5.0f}, static_cast(sceneViewportSize.x), static_cast(sceneViewportSize.y), renderTarget); + framebufferSpecs.width = static_cast(sceneViewportSize.x); + framebufferSpecs.height = static_cast(sceneViewportSize.y); + const auto renderTarget = nexo::renderer::NxFramebuffer::create(framebufferSpecs); + const nexo::ecs::Entity defaultCamera = nexo::CameraFactory::createPerspectiveCamera( + {0.0f, 0.0f, -5.0f}, static_cast(sceneViewportSize.x), + static_cast(sceneViewportSize.y), renderTarget); app.getSceneManager().getScene(sceneId).addEntity(defaultCamera); nexo::editor::utils::addPropsTo(defaultCamera, nexo::editor::utils::PropsType::CAMERA); return defaultCamera; - } + } - bool CameraInspector(const nexo::scene::SceneId sceneId) - { - auto &app = nexo::getApp(); - static int undoStackSize = -1; - // We store the current undo stack size so that when finalizing the camera creation, - // we can remove the correct number of actions from the undo stack in order to only keep the camera creation action - if (undoStackSize == -1) - undoStackSize = static_cast(nexo::editor::ActionManager::get().getUndoStackSize()); + /** + * @brief Renders the camera inspector UI for creating and configuring a camera. + * + * Provides controls for setting the camera name, editing camera and transform components, + * and a preview of the camera's view. Handles validation and cancellation of camera creation. + * + * @param sceneId The ID of the scene where the camera is being created + * @return true if the camera creation process is complete (either validated or cancelled), false otherwise + */ + bool CameraInspector(const nexo::scene::SceneId sceneId) + { + auto &app = nexo::getApp(); + static int undoStackSize = -1; + // We store the current undo stack size so that when finalizing the camera creation, + // we can remove the correct number of actions from the undo stack in order to only keep the camera creation + // action + if (undoStackSize == -1) + undoStackSize = static_cast(nexo::editor::ActionManager::get().getUndoStackSize()); const ImVec2 availSize = ImGui::GetContentRegionAvail(); const float totalWidth = availSize.x; - float totalHeight = availSize.y - 40; // Reserve space for bottom buttons + float totalHeight = availSize.y - 40; // Reserve space for bottom buttons // Define layout: 60% for inspector, 40% for preview - const float inspectorWidth = totalWidth * 0.4f; - const float previewWidth = totalWidth - inspectorWidth - 8; // Subtract spacing between panels + const float inspectorWidth = totalWidth * 0.4f; + const float previewWidth = totalWidth - inspectorWidth - 8; // Subtract spacing between panels static nexo::ecs::Entity camera = nexo::ecs::MAX_ENTITIES; - if (camera == nexo::ecs::MAX_ENTITIES) - { + if (camera == nexo::ecs::MAX_ENTITIES) { camera = createDefaultPerspectiveCamera(sceneId, ImVec2(previewWidth, totalHeight)); } static char cameraName[128] = ""; - static bool nameIsEmpty = false; - static bool closingPopup = false; + static bool nameIsEmpty = false; + static bool closingPopup = false; // We do this since imgui seems to render once more the popup, so we need to wait one frame before deleting // the render target @@ -173,11 +190,11 @@ namespace ImNexo { // Now it's safe to delete the entity app.deleteEntity(camera); - camera = nexo::ecs::MAX_ENTITIES; + camera = nexo::ecs::MAX_ENTITIES; cameraName[0] = '\0'; - nameIsEmpty = false; + nameIsEmpty = false; undoStackSize = -1; - closingPopup = false; + closingPopup = false; ImGui::CloseCurrentPopup(); return true; } @@ -206,13 +223,12 @@ namespace ImNexo { } else { ImGui::Spacing(); } - if (nameIsEmpty && cameraName[0] != '\0') - nameIsEmpty = false; + if (nameIsEmpty && cameraName[0] != '\0') nameIsEmpty = false; ImGui::Spacing(); - if (Header("##CameraNode", "Camera")) - { - auto &cameraComponent = nexo::Application::m_coordinator->getComponent(camera); + if (Header("##CameraNode", "Camera")) { + auto &cameraComponent = + nexo::Application::m_coordinator->getComponent(camera); cameraComponent.render = true; static nexo::components::CameraComponent::Memento beforeState; auto cameraComponentCopy = cameraComponent; @@ -222,7 +238,9 @@ namespace ImNexo { beforeState = cameraComponentCopy.save(); } else if (isItemDeactivated()) { auto afterState = cameraComponent.save(); - auto action = std::make_unique>(camera, beforeState, afterState); + auto action = + std::make_unique>( + camera, beforeState, afterState); nexo::editor::ActionManager::get().recordAction(std::move(action)); } ImGui::TreePop(); @@ -232,10 +250,10 @@ namespace ImNexo { ImGui::Spacing(); ImGui::Spacing(); - if (Header("##TransformNode", "Transform Component")) - { + if (Header("##TransformNode", "Transform Component")) { static glm::vec3 lastDisplayedEuler(0.0f); - auto &transformComponent = nexo::Application::m_coordinator->getComponent(camera); + auto &transformComponent = + nexo::Application::m_coordinator->getComponent(camera); static nexo::components::TransformComponent::Memento beforeState; resetItemStates(); auto transformComponentCopy = transformComponent; @@ -244,16 +262,19 @@ namespace ImNexo { beforeState = transformComponentCopy.save(); } else if (isItemDeactivated()) { auto afterState = transformComponent.save(); - auto action = std::make_unique>(camera, beforeState, afterState); + auto action = + std::make_unique>( + camera, beforeState, afterState); nexo::editor::ActionManager::get().recordAction(std::move(action)); } ImGui::TreePop(); } - if (nexo::Application::m_coordinator->entityHasComponent(camera) && - Header("##PerspectiveCameraTarget", "Camera Target Component")) - { - auto &cameraTargetComponent = nexo::Application::m_coordinator->getComponent(camera); + if (nexo::Application::m_coordinator->entityHasComponent( + camera) && + Header("##PerspectiveCameraTarget", "Camera Target Component")) { + auto &cameraTargetComponent = + nexo::Application::m_coordinator->getComponent(camera); nexo::components::PerspectiveCameraTarget::Memento beforeState{}; resetItemStates(); auto cameraTargetComponentCopy = cameraTargetComponent; @@ -262,16 +283,20 @@ namespace ImNexo { beforeState = cameraTargetComponentCopy.save(); } else if (isItemDeactivated()) { auto afterState = cameraTargetComponent.save(); - auto action = std::make_unique>(camera, beforeState, afterState); + auto action = std::make_unique< + nexo::editor::ComponentChangeAction>( + camera, beforeState, afterState); nexo::editor::ActionManager::get().recordAction(std::move(action)); } ImGui::TreePop(); } - if (nexo::Application::m_coordinator->entityHasComponent(camera) && - Header("##PerspectiveCameraController", "Camera Controller Component")) - { - auto &cameraControllerComponent = nexo::Application::m_coordinator->getComponent(camera); + if (nexo::Application::m_coordinator->entityHasComponent( + camera) && + Header("##PerspectiveCameraController", "Camera Controller Component")) { + auto &cameraControllerComponent = + nexo::Application::m_coordinator->getComponent( + camera); nexo::components::PerspectiveCameraController::Memento beforeState{}; auto cameraControllerComponentCopy = cameraControllerComponent; resetItemStates(); @@ -280,7 +305,9 @@ namespace ImNexo { beforeState = cameraControllerComponentCopy.save(); } else if (isItemDeactivated()) { auto afterState = cameraControllerComponent.save(); - auto action = std::make_unique>(camera, beforeState, afterState); + auto action = std::make_unique< + nexo::editor::ComponentChangeAction>( + camera, beforeState, afterState); nexo::editor::ActionManager::get().recordAction(std::move(action)); } ImGui::TreePop(); @@ -298,63 +325,66 @@ namespace ImNexo { // Static variables for state tracking static bool showComponentSelector = false; - static float animProgress = 0.0f; - static double lastClickTime = 0.0f; + static float animProgress = 0.0f; + static double lastClickTime = 0.0f; // Button with arrow indicating state - const std::string buttonText = "Add Component " + std::string(showComponentSelector ? ICON_FA_CHEVRON_UP : ICON_FA_CHEVRON_DOWN); + const std::string buttonText = + "Add Component " + std::string(showComponentSelector ? ICON_FA_CHEVRON_UP : ICON_FA_CHEVRON_DOWN); - if (Button(buttonText, ImVec2(buttonWidth, 0))) - { + if (Button(buttonText, ImVec2(buttonWidth, 0))) { showComponentSelector = !showComponentSelector; if (showComponentSelector) { lastClickTime = ImGui::GetTime(); - animProgress = 0.0f; + animProgress = 0.0f; } } ImGui::PopStyleVar(); // Component selector with just two options - if (showComponentSelector) - { + if (showComponentSelector) { // Animation calculation constexpr float animDuration = 0.25f; - auto timeSinceClick = static_cast(ImGui::GetTime() - lastClickTime); - animProgress = std::min(timeSinceClick / animDuration, 1.0f); + auto timeSinceClick = static_cast(ImGui::GetTime() - lastClickTime); + animProgress = std::min(timeSinceClick / animDuration, 1.0f); // Simplified component grid with compact layout constexpr float maxGridHeight = 90.0f; - const float currentHeight = maxGridHeight * animProgress; + const float currentHeight = maxGridHeight * animProgress; // Create child window for components with animated height ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 3.0f); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4)); // Reduce spacing between items + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4)); // Reduce spacing between items ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 8)); // Better padding inside items - ImGui::BeginChild("ComponentSelector", ImVec2(buttonWidth, currentHeight), 0, ImGuiWindowFlags_NoScrollbar); - - if (animProgress > 0.5f) - { + ImGui::BeginChild("ComponentSelector", ImVec2(buttonWidth, currentHeight), 0, + ImGuiWindowFlags_NoScrollbar); + if (animProgress > 0.5f) { // Draw component buttons side-by-side with controlled spacing ImGui::BeginGroup(); - if (!nexo::Application::m_coordinator->entityHasComponent(camera) && - !nexo::Application::m_coordinator->entityHasComponent(camera) && - ButtonWithIconAndText("camera_target", ICON_FA_CAMERA, "Camera target", ImVec2(75.0f, 75.0f))) - { - auto action = std::make_unique>(camera); + if (!nexo::Application::m_coordinator + ->entityHasComponent(camera) && + !nexo::Application::m_coordinator + ->entityHasComponent(camera) && + ButtonWithIconAndText("camera_target", ICON_FA_CAMERA, "Camera target", ImVec2(75.0f, 75.0f))) { + auto action = std::make_unique< + nexo::editor::ComponentAddAction>(camera); nexo::editor::ActionManager::get().recordAction(std::move(action)); nexo::components::PerspectiveCameraTarget cameraTarget{}; nexo::Application::m_coordinator->addComponent(camera, cameraTarget); showComponentSelector = false; } ImGui::SameLine(); - if (!nexo::Application::m_coordinator->entityHasComponent(camera) && - !nexo::Application::m_coordinator->entityHasComponent(camera) && - ButtonWithIconAndText("camera_controller", ICON_FA_GAMEPAD, "Camera Controller", ImVec2(75.0f, 75.0f))) - { - auto action = std::make_unique>(camera); + if (!nexo::Application::m_coordinator + ->entityHasComponent(camera) && + !nexo::Application::m_coordinator + ->entityHasComponent(camera) && + ButtonWithIconAndText("camera_controller", ICON_FA_GAMEPAD, "Camera Controller", + ImVec2(75.0f, 75.0f))) { + auto action = std::make_unique< + nexo::editor::ComponentAddAction>(camera); nexo::editor::ActionManager::get().recordAction(std::move(action)); nexo::components::PerspectiveCameraController cameraController{}; nexo::Application::m_coordinator->addComponent(camera, cameraController); @@ -381,15 +411,15 @@ namespace ImNexo { nexo::Application::SceneInfo sceneInfo{sceneId, nexo::RenderingType::FRAMEBUFFER}; app.run(sceneInfo); - auto const &cameraComponent = nexo::Application::m_coordinator->getComponent(camera); + auto const &cameraComponent = + nexo::Application::m_coordinator->getComponent(camera); const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); const float displayHeight = totalHeight - 20; - const float displayWidth = displayHeight; + const float displayWidth = displayHeight; ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 4, ImGui::GetCursorPosY() + 4)); - Image(static_cast(static_cast(textureId)), - ImVec2(displayWidth, displayHeight)); + Image(static_cast(static_cast(textureId)), ImVec2(displayWidth, displayHeight)); ImGui::EndChild(); } @@ -400,36 +430,35 @@ namespace ImNexo { // Bottom buttons - centered constexpr float buttonWidth = 120.0f; - if (ImGui::Button("OK", ImVec2(buttonWidth, 0))) - { + if (ImGui::Button("OK", ImVec2(buttonWidth, 0))) { if (cameraName[0] == '\0') { nameIsEmpty = true; return false; } - nameIsEmpty = false; - auto &selector = nexo::editor::Selector::get(); + nameIsEmpty = false; + auto &selector = nexo::editor::Selector::get(); const auto &uuid = nexo::Application::m_coordinator->getComponent(camera); - auto &cameraComponent = nexo::Application::m_coordinator->getComponent(camera); + auto &cameraComponent = + nexo::Application::m_coordinator->getComponent(camera); cameraComponent.active = false; selector.setUiHandle(uuid.uuid, std::string(ICON_FA_CAMERA " ") + cameraName); unsigned int stackSize = nexo::editor::ActionManager::get().getUndoStackSize() - undoStackSize; nexo::editor::ActionManager::get().clearHistory(stackSize); auto action = std::make_unique(camera); nexo::editor::ActionManager::get().recordAction(std::move(action)); - camera = nexo::ecs::MAX_ENTITIES; + camera = nexo::ecs::MAX_ENTITIES; cameraName[0] = '\0'; undoStackSize = -1; ImGui::CloseCurrentPopup(); return true; } ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) - { + if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) { unsigned int stackSize = nexo::editor::ActionManager::get().getUndoStackSize() - undoStackSize; nexo::editor::ActionManager::get().clearHistory(stackSize); closingPopup = true; return false; } return false; - } -} + } +} // namespace ImNexo diff --git a/editor/src/ImNexo/Panels.hpp b/editor/src/ImNexo/Panels.hpp index a08d9233e..249603c0f 100644 --- a/editor/src/ImNexo/Panels.hpp +++ b/editor/src/ImNexo/Panels.hpp @@ -17,14 +17,14 @@ namespace ImNexo { /** - * @brief Draws a material inspector widget for editing material properties. - * - * This function displays controls for shader selection, rendering mode, and textures/colors - * for material properties such as albedo and specular components. - * - * @param material Reference to the components::Material to be inspected and modified. - * @return true if any material property was modified; false otherwise. - */ + * @brief Draws a material inspector widget for editing material properties. + * + * This function displays controls for shader selection, rendering mode, and textures/colors + * for material properties such as albedo and specular components. + * + * @param material Reference to the components::Material to be inspected and modified. + * @return true if any material property was modified; false otherwise. + */ bool MaterialInspector(nexo::components::Material &material); /** @@ -44,8 +44,7 @@ namespace ImNexo { * the dialog is otherwise closed, any temporary camera is deleted. * * @param sceneId The ID of the scene where the camera will be created - * @param sceneViewportSize The size of the scene viewport for proper camera aspect ratio * @return true if the dialog was closed (either by confirming or canceling), false if still open */ bool CameraInspector(nexo::scene::SceneId sceneId); -} +} // namespace ImNexo diff --git a/editor/src/ImNexo/Utils.cpp b/editor/src/ImNexo/Utils.cpp index c80f6df35..b1242a425 100644 --- a/editor/src/ImNexo/Utils.cpp +++ b/editor/src/ImNexo/Utils.cpp @@ -27,14 +27,15 @@ namespace ImNexo::utils { const unsigned char r1 = (colB >> 16) & 0xFF; const unsigned char g1 = (colB >> 8) & 0xFF; const unsigned char b1 = colB & 0xFF; - const auto a = static_cast(static_cast(a0) + t * static_cast(a1 - a0)); - const auto r = static_cast(static_cast(r0) + t * static_cast(r1 - r0)); - const auto g = static_cast(static_cast(g0) + t * static_cast(g1 - g0)); - const auto b = static_cast(static_cast(b0) + t * static_cast(b1 - b0)); + const auto a = static_cast(static_cast(a0) + t * static_cast(a1 - a0)); + const auto r = static_cast(static_cast(r0) + t * static_cast(r1 - r0)); + const auto g = static_cast(static_cast(g0) + t * static_cast(g1 - g0)); + const auto b = static_cast(static_cast(b0) + t * static_cast(b1 - b0)); return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); } - void clipPolygonWithLine(const std::vector& poly, const ImVec2& normal, const float offset, std::vector& outPoly) + void clipPolygonWithLine(const std::vector& poly, const ImVec2& normal, const float offset, + std::vector& outPoly) { outPoly.clear(); const auto count = poly.size(); @@ -42,10 +43,9 @@ namespace ImNexo::utils { for (size_t i = 0; i < count; i++) { const ImVec2& a = poly[i]; const ImVec2& b = poly[(i + 1) % count]; - const float da = ImDot(a, normal) - offset; - const float db = ImDot(b, normal) - offset; - if (da >= 0) - outPoly.push_back(a); + const float da = ImDot(a, normal) - offset; + const float db = ImDot(b, normal) - offset; + if (da >= 0) outPoly.push_back(a); // if the edge spans the boundary, compute intersection if ((da >= 0 && db < 0) || (da < 0 && db >= 0)) { const float t = da / (da - db); @@ -59,8 +59,7 @@ namespace ImNexo::utils { void fillConvexPolygon(ImDrawList* drawList, const std::vector& poly, const std::vector& polyColors) { - if (poly.size() < 3) - return; + if (poly.size() < 3) return; const auto count = static_cast(poly.size()); drawList->PrimReserve((count - 2) * 3, count); // Use the first vertex as pivot. @@ -92,14 +91,11 @@ namespace ImNexo::utils { } /** - * @brief Positions text centered within a rectangle - */ + * @brief Positions text centered within a rectangle + */ ImVec2 calculateCenteredTextPosition(const std::string& text, const ImVec2& p_min, const ImVec2& p_max) { const ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); - return { - p_min.x + (p_max.x - p_min.x - textSize.x) * 0.5f, - p_min.y + (p_max.y - p_min.y - textSize.y) * 0.5f - }; + return {p_min.x + (p_max.x - p_min.x - textSize.x) * 0.5f, p_min.y + (p_max.y - p_min.y - textSize.y) * 0.5f}; } -} +} // namespace ImNexo::utils diff --git a/editor/src/ImNexo/Utils.hpp b/editor/src/ImNexo/Utils.hpp index 68d469eae..2d2bb8e7b 100644 --- a/editor/src/ImNexo/Utils.hpp +++ b/editor/src/ImNexo/Utils.hpp @@ -14,46 +14,64 @@ #pragma once #include -#include #include +#include namespace ImNexo::utils { /** - * @brief Linearly interpolates between two colors (ImU32, ImGui 32-bits ARGB format). - * @param[in] colA The first color (ARGB format). - * @param[in] colB The second color (ARGB format). - * @param[in] t The interpolation factor (0.0 to 1.0). - * @return The interpolated color (ARGB format). - */ + * @brief Linearly interpolates between two colors (ImU32, ImGui 32-bits ARGB format). + * @param[in] colA The first color (ARGB format). + * @param[in] colB The second color (ARGB format). + * @param[in] t The interpolation factor (0.0 to 1.0). + * @return The interpolated color (ARGB format). + */ ImU32 imLerpColor(ImU32 colA, ImU32 colB, float t); /** - * @brief Clip a convex polygon against a half-plane defined by: (dot(normal, v) >= offset) - * - * This function uses the Sutherland-Hodgman algorithm to clip a polygon against a line defined by a normal vector and an offset. - * @param[in] poly Vector of vertices representing the polygon to be clipped. - * @param[in] normal The normal vector of the line used for clipping. - * @param[in] offset The offset from the origin of the line. - * @param[out] outPoly Output vector to store the clipped polygon vertices. - */ - void clipPolygonWithLine(const std::vector& poly, const ImVec2& normal, float offset, std::vector& outPoly); + * @brief Clip a convex polygon against a half-plane defined by: (dot(normal, v) >= offset) + * + * This function uses the Sutherland-Hodgman algorithm to clip a polygon against a line defined by a normal vector + * and an offset. + * @param[in] poly Vector of vertices representing the polygon to be clipped. + * @param[in] normal The normal vector of the line used for clipping. + * @param[in] offset The offset from the origin of the line. + * @param[out] outPoly Output vector to store the clipped polygon vertices. + */ + void clipPolygonWithLine(const std::vector& poly, const ImVec2& normal, float offset, + std::vector& outPoly); /** - * @brief Fill a convex polygon with triangles using a triangle fan. - * @param[in] drawList The ImDrawList to which the triangles will be added. - * @param[in] poly Vector of vertices representing the polygon to be filled. - * @param[in] polyColors Vector of colors for each vertex in the polygon. - */ + * @brief Fill a convex polygon with triangles using a triangle fan. + * + * This function fills a convex polygon by creating a triangle fan from the first vertex to all other vertices. + * @param[in] drawList The ImDrawList to which the triangles will be added. + * @param[in] poly Vector of vertices representing the polygon to be filled. + * @param[in] polyColors Vector of colors for each vertex in the polygon. + */ void fillConvexPolygon(ImDrawList* drawList, const std::vector& poly, const std::vector& polyColors); /** - * @brief Helper to get the current item's rect and optionally apply padding - */ + * @brief Get the rectangle of the last item with optional padding. + * + * This function retrieves the minimum and maximum coordinates of the last item rendered in ImGui, + * applying the specified padding to both corners. + * + * @param padding The padding to apply to the rectangle (default is (0,0)). + * @return A pair of ImVec2 representing the minimum and maximum corners of the padded rectangle. + */ std::pair getItemRect(const ImVec2& padding = ImVec2(0, 0)); /** - * @brief Positions text centered within a rectangle - */ + * @brief Calculate the position to render centered text within a given rectangle. + * + * This function computes the top-left position to start rendering the specified text + * so that it appears centered within the rectangle defined by p_min and p_max. + * + * @param text The text string to be centered. + * @param p_min The minimum (top-left) corner of the rectangle. + * @param p_max The maximum (bottom-right) corner of the rectangle. + * @return The ImVec2 position where the text should be rendered to be centered. + */ ImVec2 calculateCenteredTextPosition(const std::string& text, const ImVec2& p_min, const ImVec2& p_max); -} +} // namespace ImNexo::utils diff --git a/editor/src/ImNexo/Widgets.cpp b/editor/src/ImNexo/Widgets.cpp index c34de45e7..15ab18979 100644 --- a/editor/src/ImNexo/Widgets.cpp +++ b/editor/src/ImNexo/Widgets.cpp @@ -12,59 +12,48 @@ // /////////////////////////////////////////////////////////////////////////////// +#include #include -#include #include -#include +#include -#include "Widgets.hpp" #include "IconsFontAwesome.h" -#include "Nexo.hpp" #include "ImNexo.hpp" +#include "Nexo.hpp" +#include "Widgets.hpp" -namespace ImNexo -{ - bool ColorEditor( - const std::string& label, - glm::vec4* selectedEntityColor, - ImGuiColorEditFlags* colorPickerMode, - bool* showPicker, - const ImGuiColorEditFlags colorButtonFlags - ) +namespace ImNexo { + bool ColorEditor(const std::string& label, glm::vec4* selectedEntityColor, ImGuiColorEditFlags* colorPickerMode, + bool* showPicker, const ImGuiColorEditFlags colorButtonFlags) { - const ImGuiStyle& style = ImGui::GetStyle(); + const ImGuiStyle& style = ImGui::GetStyle(); const ImVec2 contentAvailable = ImGui::GetContentRegionAvail(); - bool colorModified = false; + bool colorModified = false; const std::string colorButton = std::string("##ColorButton") + label; - const ImVec2 cogIconSize = ImGui::CalcTextSize(ICON_FA_COG); + const ImVec2 cogIconSize = ImGui::CalcTextSize(ICON_FA_COG); const ImVec2 cogIconPadding = style.FramePadding; - const ImVec2 itemSpacing = style.ItemSpacing; + const ImVec2 itemSpacing = style.ItemSpacing; // Color button ColorButton( - colorButton, - ImVec2(contentAvailable.x - cogIconSize.x - cogIconPadding.x * 2 - itemSpacing.x, 0), + colorButton, ImVec2(contentAvailable.x - cogIconSize.x - cogIconPadding.x * 2 - itemSpacing.x, 0), // Make room for the cog button ImVec4(selectedEntityColor->x, selectedEntityColor->y, selectedEntityColor->z, selectedEntityColor->w), - showPicker, - colorButtonFlags - ); + showPicker, colorButtonFlags); ImGui::SameLine(); - const std::string pickerSettings = std::string("##PickerSettings") + label; + const std::string pickerSettings = std::string("##PickerSettings") + label; const std::string colorPickerPopup = std::string("##ColorPickerPopup") + label; // Cog button - if (Button(std::string(ICON_FA_COG) + pickerSettings)) - { + if (Button(std::string(ICON_FA_COG) + pickerSettings)) { ImGui::OpenPopup(colorPickerPopup.c_str()); } - if (ImGui::BeginPopup(colorPickerPopup.c_str())) - { + if (ImGui::BeginPopup(colorPickerPopup.c_str())) { ImGui::Text("Picker Mode:"); if (ImGui::RadioButton("Hue Wheel", *colorPickerMode == ImGuiColorEditFlags_PickerHueWheel)) *colorPickerMode = ImGuiColorEditFlags_PickerHueWheel; @@ -74,17 +63,13 @@ namespace ImNexo } const std::string colorPickerInline = std::string("##ColorPickerInline") + label; - if (*showPicker) - { + if (*showPicker) { ImGui::Spacing(); colorModified = ImGui::ColorPicker4(colorPickerInline.c_str(), reinterpret_cast(selectedEntityColor), *colorPickerMode); - if (ImGui::IsItemActive()) - setItemActive(); - if (ImGui::IsItemActivated()) - setItemActivated(); - if (ImGui::IsItemDeactivated()) - setItemDeactivated(); + if (ImGui::IsItemActive()) setItemActive(); + if (ImGui::IsItemActivated()) setItemActivated(); + if (ImGui::IsItemDeactivated()) setItemDeactivated(); } return colorModified; } @@ -93,34 +78,34 @@ namespace ImNexo bool& closure, const DropdownOrientation orientation) { constexpr float buttonSpacing = 5.0f; - constexpr float padding = 10.0f; + constexpr float padding = 10.0f; // Calculate menu dimensions - const float menuWidth = buttonSize.x + padding; // Add padding + const float menuWidth = buttonSize.x + padding; // Add padding const float menuHeight = static_cast(buttonProps.size()) * buttonSize.y + - (static_cast(buttonProps.size()) - 1.0f) * buttonSpacing + 2 * buttonSpacing; + (static_cast(buttonProps.size()) - 1.0f) * buttonSpacing + 2 * buttonSpacing; // Calculate menu position based on orientation ImVec2 menuPos; - switch (orientation) - { - case DropdownOrientation::DOWN: - menuPos = ImVec2(buttonPos.x - padding / 2.0f, buttonPos.y + buttonSize.y); - break; - case DropdownOrientation::UP: - menuPos = ImVec2(buttonPos.x - padding / 2.0f, buttonPos.y - menuHeight); - break; - case DropdownOrientation::RIGHT: - menuPos = ImVec2(buttonPos.x + buttonSize.x, buttonPos.y - padding / 2.0f); - break; - case DropdownOrientation::LEFT: - menuPos = ImVec2(buttonPos.x - menuWidth, buttonPos.y - padding / 2.0f); - break; + using enum DropdownOrientation; + switch (orientation) { + case DOWN: + menuPos = ImVec2(buttonPos.x - padding / 2.0f, buttonPos.y + buttonSize.y); + break; + case UP: + menuPos = ImVec2(buttonPos.x - padding / 2.0f, buttonPos.y - menuHeight); + break; + case RIGHT: + menuPos = ImVec2(buttonPos.x + buttonSize.x, buttonPos.y - padding / 2.0f); + break; + case LEFT: + menuPos = ImVec2(buttonPos.x - menuWidth, buttonPos.y - padding / 2.0f); + break; } // Adjust layout for horizontal orientations - const bool isHorizontal = (orientation == DropdownOrientation::LEFT || - orientation == DropdownOrientation::RIGHT); + const bool isHorizontal = + (orientation == LEFT || orientation == RIGHT); // For horizontal layouts, swap width and height const ImVec2 menuSize = isHorizontal ? ImVec2(menuHeight, buttonSize.y + 10.0f) : ImVec2(menuWidth, menuHeight); @@ -135,32 +120,26 @@ namespace ImNexo ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); if (ImGui::Begin("##PrimitiveMenuOverlay", nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize)) - { - for (const auto& button : buttonProps) - { + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_AlwaysAutoResize)) { + for (const auto& button : buttonProps) { // Strangely here the clicked inside here does not seem to work IconGradientButton(button.uniqueId, button.icon, ImVec2(buttonSize.x, buttonSize.y), button.buttonGradient); // So we rely on IsItemClicked from imgui - if (button.onClick && ImGui::IsItemClicked(ImGuiMouseButton_Left)) - { + if (button.onClick && ImGui::IsItemClicked(ImGuiMouseButton_Left)) { button.onClick(); closure = false; } - if (button.onRightClick && ImGui::IsItemClicked(ImGuiMouseButton_Right)) - { + if (button.onRightClick && ImGui::IsItemClicked(ImGuiMouseButton_Right)) { button.onRightClick(); } - if (!button.tooltip.empty() && ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", button.tooltip.c_str()); + if (!button.tooltip.empty() && ImGui::IsItemHovered()) ImGui::SetTooltip("%s", button.tooltip.c_str()); } } // Check for clicks outside to close menu - if (ImGui::IsMouseClicked(0) && !ImGui::IsWindowHovered()) - { + if (ImGui::IsMouseClicked(0) && !ImGui::IsWindowHovered()) { closure = false; } ImGui::End(); @@ -168,30 +147,18 @@ namespace ImNexo ImGui::PopStyleVar(3); } - /** - * @brief Renders a popup for creating a primitive entity in the editor scene. - * - * This function displays a popup window that allows the user to create a 3D primitive - * (e.g., sphere or cylinder) with configurable parameters such as the number of segments - * or subdivisions. The user can adjust these parameters using a slider and create the - * primitive by clicking the "Create" button. - * - * @param sceneId The ID of the scene where the primitive will be created. - * @param primitive The type of primitive to create (e.g., SPHERE or CYLINDER). - */ void PrimitiveCustomizationMenu(const int sceneId, const nexo::Primitives primitive) { - auto& app = nexo::Application::getInstance(); + auto& app = nexo::Application::getInstance(); auto& sceneManager = app.getSceneManager(); // Static variables to track the last selected primitive and segment count static nexo::Primitives lastPrimitive = primitive; - static int segmentCount = primitive == nexo::SPHERE ? 1 : 8; + static int segmentCount = primitive == nexo::SPHERE ? 1 : 8; // Reset segment count if the primitive type changes - if (lastPrimitive != primitive) - { - segmentCount = primitive == nexo::SPHERE ? 1 : 8; + if (lastPrimitive != primitive) { + segmentCount = primitive == nexo::SPHERE ? 1 : 8; lastPrimitive = primitive; } @@ -206,26 +173,16 @@ namespace ImNexo ImGui::SliderScalar(title, ImGuiDataType_S32, &segmentCount, &minSegmentCount, &maxSegmentCount, "%d"); // Handle the "Create" button click - if (ImGui::Button("Create")) - { + if (ImGui::Button("Create")) { constexpr glm::vec4 DEFAULT_COLOR_PRIMITIVE = {0.05f * 1.5f, 0.09f * 1.15f, 0.13f * 1.25f, 1.0f}; // Create the selected primitive with the specified parameters - const nexo::ecs::Entity newPrimitive = primitive == nexo::SPHERE - ? nexo::EntityFactory3D::createSphere( - {0.0f, 0.0f, 0.0f}, - {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, - glm::vec4(DEFAULT_COLOR_PRIMITIVE), - segmentCount - ) - : nexo::EntityFactory3D::createCylinder( - {0.0f, 0.0f, 0.0f}, - {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, - glm::vec4(DEFAULT_COLOR_PRIMITIVE), - segmentCount - ); + const nexo::ecs::Entity newPrimitive = + primitive == nexo::SPHERE ? + nexo::EntityFactory3D::createSphere({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + glm::vec4(DEFAULT_COLOR_PRIMITIVE), segmentCount) : + nexo::EntityFactory3D::createCylinder({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + glm::vec4(DEFAULT_COLOR_PRIMITIVE), segmentCount); // Add the new primitive to the scene sceneManager.getScene(sceneId).addEntity(newPrimitive); @@ -233,52 +190,45 @@ namespace ImNexo // Record the creation action for undo/redo functionality auto createAction = std::make_unique(newPrimitive); nexo::editor::ActionManager::get().recordAction(std::move(createAction)); + ImGui::CloseCurrentPopup(); } // End the popup rendering ImGui::EndPopup(); } - - void PrimitiveSubMenu(const int sceneId, nexo::editor::PopupManager& popupManager) { - auto& app = nexo::Application::getInstance(); + auto& app = nexo::Application::getInstance(); auto& sceneManager = app.getSceneManager(); - if (ImGui::BeginMenu("Primitives")) - { - if (ImGui::MenuItem("Cube")) - { - const nexo::ecs::Entity newCube = nexo::EntityFactory3D::createCube( - {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); + if (ImGui::BeginMenu("Primitives")) { + if (ImGui::MenuItem("Cube")) { + const nexo::ecs::Entity newCube = + nexo::EntityFactory3D::createCube({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); sceneManager.getScene(sceneId).addEntity(newCube); auto createAction = std::make_unique(newCube); nexo::editor::ActionManager::get().recordAction(std::move(createAction)); } - if (ImGui::MenuItem("Sphere")) - { + if (ImGui::MenuItem("Sphere")) { popupManager.openPopup("Sphere creation popup"); } - if (ImGui::MenuItem("Cylinder")) - { + if (ImGui::MenuItem("Cylinder")) { popupManager.openPopup("Cylinder creation popup"); } - if (ImGui::MenuItem("Pyramid")) - { - const nexo::ecs::Entity newPyramid = nexo::EntityFactory3D::createPyramid( - {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); + if (ImGui::MenuItem("Pyramid")) { + const nexo::ecs::Entity newPyramid = + nexo::EntityFactory3D::createPyramid({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); sceneManager.getScene(sceneId).addEntity(newPyramid); auto createAction = std::make_unique(newPyramid); nexo::editor::ActionManager::get().recordAction(std::move(createAction)); } - if (ImGui::MenuItem("Tetrahedron")) - { - const nexo::ecs::Entity newTetrahedron = nexo::EntityFactory3D::createTetrahedron( - {0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - {0.0f, 0.0f, 0.0f}, {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); + if (ImGui::MenuItem("Tetrahedron")) { + const nexo::ecs::Entity newTetrahedron = + nexo::EntityFactory3D::createTetrahedron({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, + {0.05f * 1.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f}); sceneManager.getScene(sceneId).addEntity(newTetrahedron); auto createAction = std::make_unique(newTetrahedron); nexo::editor::ActionManager::get().recordAction(std::move(createAction)); @@ -286,4 +236,4 @@ namespace ImNexo ImGui::EndMenu(); } } -} +} // namespace ImNexo diff --git a/editor/src/ImNexo/Widgets.hpp b/editor/src/ImNexo/Widgets.hpp index f27372dd8..6c4682647 100644 --- a/editor/src/ImNexo/Widgets.hpp +++ b/editor/src/ImNexo/Widgets.hpp @@ -15,32 +15,26 @@ #include #include -#include "EntityFactory3D.hpp" #include "Components.hpp" #include "DocumentWindows/PopupManager.hpp" +#include "EntityFactory3D.hpp" namespace ImNexo { - /** - * @brief Draws a color editor with a button and an optional inline color picker. - * - * Displays a custom color button (with a cog icon for picker settings) and, if enabled, - * an inline color picker. The function returns true if the color was modified. - * - * @param label A unique label identifier for the widget. - * @param selectedEntityColor Pointer to the glm::vec4 representing the current color. - * @param colorPickerMode Pointer to the ImGuiColorEditFlags for the picker mode. - * @param showPicker Pointer to a boolean that determines if the inline color picker is visible. - * @param colorButtonFlags Optional flags for the color button (default is none). - * @return true if the color was modified; false otherwise. - */ - bool ColorEditor( - const std::string &label, - glm::vec4 *selectedEntityColor, - ImGuiColorEditFlags *colorPickerMode, - bool *showPicker, - ImGuiColorEditFlags colorButtonFlags = ImGuiColorEditFlags_None - ); + * @brief Draws a color editor with a button and an optional inline color picker. + * + * Displays a custom color button (with a cog icon for picker settings) and, if enabled, + * an inline color picker. The function returns true if the color was modified. + * + * @param label A unique label identifier for the widget. + * @param selectedEntityColor Pointer to the glm::vec4 representing the current color. + * @param colorPickerMode Pointer to the ImGuiColorEditFlags for the picker mode. + * @param showPicker Pointer to a boolean that determines if the inline color picker is visible. + * @param colorButtonFlags Optional flags for the color button (default is none). + * @return true if the color was modified; false otherwise. + */ + bool ColorEditor(const std::string &label, glm::vec4 *selectedEntityColor, ImGuiColorEditFlags *colorPickerMode, + bool *showPicker, ImGuiColorEditFlags colorButtonFlags = ImGuiColorEditFlags_None); /** * @brief Configuration properties for a button in a dropdown menu. @@ -50,29 +44,27 @@ namespace ImNexo { * icons, callbacks for different mouse actions, tooltips, and custom styling. */ struct ButtonProps { - std::string uniqueId; ///< Unique identifier for ImGui tracking - std::string icon; ///< Icon to display on the button (typically FontAwesome) - std::function onClick = nullptr; ///< Callback executed when button is left-clicked + std::string uniqueId; ///< Unique identifier for ImGui tracking + std::string icon; ///< Icon to display on the button (typically FontAwesome) + std::function onClick = nullptr; ///< Callback executed when button is left-clicked std::function onRightClick = nullptr; ///< Callback executed when button is right-clicked - std::string tooltip; ///< Tooltip text displayed when hovering + std::string tooltip; ///< Tooltip text displayed when hovering /** - * @brief Gradient colors for button styling - * - * Default gradient uses a dark blue theme that matches the editor's style. - * Override this with custom colors to create visually distinct buttons. - */ - std::vector buttonGradient = { - {0.0f, IM_COL32(50, 50, 70, 230)}, - {1.0f, IM_COL32(30, 30, 45, 230)} - }; + * @brief Gradient colors for button styling + * + * Default gradient uses a dark blue theme that matches the editor's style. + * Override this with custom colors to create visually distinct buttons. + */ + std::vector buttonGradient = {{0.0f, IM_COL32(50, 50, 70, 230)}, + {1.0f, IM_COL32(30, 30, 45, 230)}}; }; enum class DropdownOrientation { - DOWN, // Dropdown appears below the button - UP, // Dropdown appears above the button - RIGHT, // Dropdown appears to the right of the button - LEFT // Dropdown appears to the left of the button + DOWN, // Dropdown appears below the button + UP, // Dropdown appears above the button + RIGHT, // Dropdown appears to the right of the button + LEFT // Dropdown appears to the left of the button }; /** @@ -88,15 +80,32 @@ namespace ImNexo { * @param closure Reference to a boolean flag controlling dropdown visibility; set to false to close * @param orientation Direction the dropdown should expand (DOWN, UP, LEFT, RIGHT) */ - void ButtonDropDown( - const ImVec2& buttonPos, - ImVec2 buttonSize, - const std::vector &buttonProps, - bool &closure, - DropdownOrientation orientation = DropdownOrientation::DOWN - ); + void ButtonDropDown(const ImVec2 &buttonPos, ImVec2 buttonSize, const std::vector &buttonProps, + bool &closure, DropdownOrientation orientation = DropdownOrientation::DOWN); - void PrimitiveSubMenu(int sceneId, nexo::editor::PopupManager &popupManager); + /** + * @brief Renders a popup for creating a primitive entity in the editor scene. + * + * This function displays a popup window that allows the user to create a 3D primitive + * (e.g., sphere or cylinder) with configurable parameters such as the number of segments + * or subdivisions. The user can adjust these parameters using a slider and create the + * primitive by clicking the "Create" button. + * + * @param sceneId The ID of the scene where the primitive will be created. + * @param primitive The type of primitive to create (e.g., SPHERE or CYLINDER). + */ void PrimitiveCustomizationMenu(int sceneId, nexo::Primitives primitive); -} + /** + * @brief Renders a popup menu for selecting and customizing 3D primitives. + * + * Displays a popup window that allows the user to choose between different 3D primitives + * (e.g., sphere or cylinder) and customize their parameters (like segments or subdivisions). + * The user can create the selected primitive in the specified scene by clicking the "Create" button. + * + * @param sceneId The ID of the scene where the primitive will be created. + * @param popupManager Reference to the PopupManager handling the popup state. + */ + void PrimitiveSubMenu(int sceneId, nexo::editor::PopupManager &popupManager); + +} // namespace ImNexo diff --git a/editor/src/WindowRegistry.cpp b/editor/src/WindowRegistry.cpp index c7e88de33..5b2b5a243 100644 --- a/editor/src/WindowRegistry.cpp +++ b/editor/src/WindowRegistry.cpp @@ -16,77 +16,65 @@ namespace nexo::editor { - void WindowRegistry::setup() const - { - for (const auto &[_, windows]: m_windows) - { - for (const auto &window : windows) - { + void WindowRegistry::setup() const + { + for (const auto &[_, windows] : m_windows) { + for (const auto &window : windows) { window->setup(); } } - } + } - void WindowRegistry::shutdown() const - { - for (const auto &[_, windows]: m_windows) - { - for (const auto &window : windows) - { + void WindowRegistry::shutdown() const + { + for (const auto &[_, windows] : m_windows) { + for (const auto &window : windows) { window->shutdown(); } } - } + } - void WindowRegistry::setDockId(const std::string& name, ImGuiID id) - { - m_dockingRegistry.setDockId(name, id); - } + void WindowRegistry::setDockId(const std::string &name, ImGuiID id) + { + m_dockingRegistry.setDockId(name, id); + } - std::optional WindowRegistry::getDockId(const std::string& name) const - { - return m_dockingRegistry.getDockId(name); - } + std::optional WindowRegistry::getDockId(const std::string &name) const + { + return m_dockingRegistry.getDockId(name); + } - std::shared_ptr WindowRegistry::getFocusedWindow() const - { - for (const auto &[_, windows]: m_windows) - { - for (const auto &window : windows) - { - if (window->isFocused()) - return window; + std::shared_ptr WindowRegistry::getFocusedWindow() const + { + for (const auto &[_, windows] : m_windows) { + for (const auto &window : windows) { + if (window->isFocused()) return window; } } return nullptr; - } + } - void WindowRegistry::resetDockId(const std::string &name) - { - m_dockingRegistry.resetDockId(name); - } + void WindowRegistry::resetDockId(const std::string &name) + { + m_dockingRegistry.resetDockId(name); + } - void WindowRegistry::update() const - { - for (const auto &[_, windows]: m_windows) - { - for (const auto &window : windows) - { + void WindowRegistry::update() const + { + for (const auto &[_, windows] : m_windows) { + for (const auto &window : windows) { window->update(); } } - } + } - void WindowRegistry::render() const - { - for (const auto &[_, windows]: m_windows) - { - for (const auto &window : windows) - { - if (!window->isOpened()) - continue; - window->show(); + void WindowRegistry::render() const + { + for (const auto &[_, windows] : m_windows) { + for (const auto &window : windows) { + if (!window->isOpened()) continue; + window->show(); } } - } -} + } +} // namespace nexo::editor diff --git a/editor/src/WindowRegistry.hpp b/editor/src/WindowRegistry.hpp index 00063d623..8a69d792c 100644 --- a/editor/src/WindowRegistry.hpp +++ b/editor/src/WindowRegistry.hpp @@ -13,20 +13,19 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "IDocumentWindow.hpp" #include "DockingRegistry.hpp" -#include "exceptions/Exceptions.hpp" +#include "IDocumentWindow.hpp" #include "Logger.hpp" +#include "exceptions/Exceptions.hpp" -#include -#include -#include -#include #include +#include #include +#include +#include +#include namespace nexo::editor { - /** * @brief Helper function to cast a window from IDocumentWindow to a specific type. * @@ -37,11 +36,11 @@ namespace nexo::editor { * @param ptr The shared pointer to an IDocumentWindow instance * @return std::shared_ptr The same pointer cast to the derived type */ - template - std::shared_ptr castWindow(const std::shared_ptr& ptr) - { - return std::static_pointer_cast(ptr); - } + template + std::shared_ptr castWindow(const std::shared_ptr &ptr) + { + return std::static_pointer_cast(ptr); + } /** * @brief Non-const version of the window casting helper function. @@ -53,250 +52,256 @@ namespace nexo::editor { * @param ptr The shared pointer to an IDocumentWindow instance * @return std::shared_ptr The same pointer cast to the derived type */ - template - std::shared_ptr castWindow(std::shared_ptr& ptr) - { - return std::static_pointer_cast(ptr); - } + template + std::shared_ptr castWindow(std::shared_ptr &ptr) + { + return std::static_pointer_cast(ptr); + } - class WindowRegistry { - public: - - bool hasWindow(const std::string &windowName) const - { - for (const auto &[type, windows] : m_windows) { - if (std::ranges::any_of(windows, [&windowName](const auto &window) { - return window->getWindowName() == windowName; - })) { - return true; - } - } - return false; - } + class WindowRegistry { + public: + /** + * @brief Initializes all registered document windows. + * + * This function iterates through all registered document windows and calls their setup method + * to initialize them. It ensures that each window is properly set up before use. + */ + [[nodiscard]] bool hasWindow(const std::string &windowName) const + { + for (const auto &[type, windows] : m_windows) { + if (std::ranges::any_of( + windows, [&windowName](const auto &window) { return window->getWindowName() == windowName; })) { + return true; + } + } + return false; + } - /** - * @brief Adds a document window instance to the registry. - * - * This function registers a document window of type T by storing its shared pointer in an - * internal map keyed by the window's type identifier. Before adding the new window, it checks - * whether a window with the same name is already registered under the same type. If such a window - * exists, a WindowAlreadyRegistered exception is thrown to prevent duplicate entries. - * - * @tparam T The concrete window type derived from IDocumentWindow. - * @param window A shared pointer to the window instance to be registered. - */ - template - requires std::derived_from - void registerWindow(std::shared_ptr window) - { - auto &windowsOfType = m_windows[typeid(T)]; - if (std::ranges::any_of(windowsOfType, [&](const auto &existingWindow) { - return existingWindow->getWindowName() == window->getWindowName(); - })) - { - THROW_EXCEPTION(WindowAlreadyRegistered, typeid(T), window->getWindowName()); - } - windowsOfType.push_back(window); - } + /** + * @brief Adds a document window instance to the registry. + * + * This function registers a document window of type T by storing its shared pointer in an + * internal map keyed by the window's type identifier. Before adding the new window, it checks + * whether a window with the same name is already registered under the same type. If such a window + * exists, a WindowAlreadyRegistered exception is thrown to prevent duplicate entries. + * + * @tparam T The concrete window type derived from IDocumentWindow. + * @param window A shared pointer to the window instance to be registered. + */ + template + requires std::derived_from + void registerWindow(std::shared_ptr window) + { + auto &windowsOfType = m_windows[typeid(T)]; + if (std::ranges::any_of(windowsOfType, [&](const auto &existingWindow) { + return existingWindow->getWindowName() == window->getWindowName(); + })) { + THROW_EXCEPTION(WindowAlreadyRegistered, typeid(T), window->getWindowName()); + } + windowsOfType.push_back(window); + } - /** - * @brief Removes a window from the registry. - * - * This function searches for a window of type T with the specified name and - * removes it from the registry if found. If no window matches the criteria, - * a warning message is logged but no exception is thrown. - * - * @tparam T The concrete window type derived from IDocumentWindow. - * @param windowName The name of the window to unregister. - */ - template - requires std::derived_from - void unregisterWindow(const std::string &windowName) - { - const auto it = m_windows.find(typeid(T)); - if (it == m_windows.end()) { - LOG(NEXO_WARN, "Window of type {} not found", typeid(T).name()); - return; - } + /** + * @brief Removes a window from the registry. + * + * This function searches for a window of type T with the specified name and + * removes it from the registry if found. If no window matches the criteria, + * a warning message is logged but no exception is thrown. + * + * @tparam T The concrete window type derived from IDocumentWindow. + * @param windowName The name of the window to unregister. + */ + template + requires std::derived_from + void unregisterWindow(const std::string &windowName) + { + const auto it = m_windows.find(typeid(T)); + if (it == m_windows.end()) { + LOG(NEXO_WARN, "Window of type {} not found", typeid(T).name()); + return; + } - auto &windowsOfType = it->second; - auto found = std::ranges::find_if(windowsOfType, [&windowName](const auto &w) { - return w->getWindowName() == windowName; - }); + auto &windowsOfType = it->second; + auto found = std::ranges::find_if( + windowsOfType, [&windowName](const auto &w) { return w->getWindowName() == windowName; }); - if (found == windowsOfType.end()) { - LOG(NEXO_WARN, "Window of type {} with name {} not found", typeid(T).name(), windowName); - return; - } + if (found == windowsOfType.end()) { + LOG(NEXO_WARN, "Window of type {} with name {} not found", typeid(T).name(), windowName); + return; + } - windowsOfType.erase(found); - } + windowsOfType.erase(found); + } - /** - * @brief Retrieves a registered window of the specified type and name. - * - * This template function checks whether a window of type T with the given name has been registered. - * If the window is found, it returns the window as a weak pointer of type T. - * Otherwise, it returns an empty weak pointer. - * - * @tparam T The type of the window, which must derive from IDocumentWindow. - * @param windowName The unique name of the window to look for. - * @return std::weak_ptr A weak pointer to the registered window if found; otherwise, an empty weak pointer. - */ - template - requires std::derived_from - std::weak_ptr getWindow(const std::string &windowName) const - { - const auto it = m_windows.find(typeid(T)); - if (it == m_windows.end()) - { - LOG(NEXO_WARN, "Window of type {} not found", typeid(T).name()); - return {}; - } + /** + * @brief Retrieves a registered window of the specified type and name. + * + * This template function checks whether a window of type T with the given name has been registered. + * If the window is found, it returns the window as a weak pointer of type T. + * Otherwise, it returns an empty weak pointer. + * + * @tparam T The type of the window, which must derive from IDocumentWindow. + * @param windowName The unique name of the window to look for. + * @return std::weak_ptr A weak pointer to the registered window if found; otherwise, an empty weak pointer. + */ + template + requires std::derived_from + std::weak_ptr getWindow(const std::string &windowName) const + { + const auto it = m_windows.find(typeid(T)); + if (it == m_windows.end()) { + LOG(NEXO_WARN, "Window of type {} not found", typeid(T).name()); + return {}; + } - auto &windowsOfType = it->second; - auto found = std::ranges::find_if(windowsOfType, [&windowName](const auto &w) { - return w->getWindowName() == windowName; - }); + auto &windowsOfType = it->second; + auto found = std::ranges::find_if( + windowsOfType, [&windowName](const auto &w) { return w->getWindowName() == windowName; }); - if (found == windowsOfType.end()) - { - LOG(NEXO_WARN, "Window of type {} with name {} not found", typeid(T).name(), windowName); - return {}; - } + if (found == windowsOfType.end()) { + LOG(NEXO_WARN, "Window of type {} with name {} not found", typeid(T).name(), windowName); + return {}; + } - return std::static_pointer_cast(*found); - } + return std::static_pointer_cast(*found); + } - /** - * @brief Retrieves a range view of document windows cast to a specified derived type. - * - * This function returns a std::ranges::transform_view over the vector of document windows stored in - * m_windows for the type T. The transformation is performed using a non-capturing helper function, - * castWindow, which casts each std::shared_ptr to a std::shared_ptr. - * - * If no windows of the requested type T are found in m_windows, an empty range is returned. - * - * @tparam T The derived type of IDocumentWindow to retrieve. - * @return A transform_view range over the vector of document windows cast to std::shared_ptr. - */ - template - requires std::derived_from - std::ranges::transform_view< - std::ranges::ref_view>>, - std::shared_ptr(*)(const std::shared_ptr&) - > - getWindows() const - { - // Helper: non-capturing function for casting: - std::shared_ptr(*caster)(const std::shared_ptr&) = &castWindow; + /** + * @brief Retrieves a range view of document windows cast to a specified derived type. + * + * This function returns a std::ranges::transform_view over the vector of document windows stored in + * m_windows for the type T. The transformation is performed using a non-capturing helper function, + * castWindow, which casts each std::shared_ptr to a std::shared_ptr. + * + * If no windows of the requested type T are found in m_windows, an empty range is returned. + * + * @tparam T The derived type of IDocumentWindow to retrieve. + * @return A transform_view range over the vector of document windows cast to std::shared_ptr. + */ + template + requires std::derived_from + std::ranges::transform_view>>, + std::shared_ptr (*)(const std::shared_ptr &)> + getWindows() const + { + // Helper: non-capturing function for casting: + std::shared_ptr (*caster)(const std::shared_ptr &) = &castWindow; - const auto it = m_windows.find(typeid(T)); - if (it == m_windows.end()) { - static const std::vector> empty; - return std::ranges::transform_view(std::ranges::ref_view(empty), caster); - } - return std::ranges::transform_view(std::ranges::ref_view(it->second), caster); - } + const auto it = m_windows.find(typeid(T)); + if (it == m_windows.end()) { + static const std::vector> empty; + return std::ranges::transform_view(std::ranges::ref_view(empty), caster); + } + return std::ranges::transform_view(std::ranges::ref_view(it->second), caster); + } - /** - * @brief Retrieves a mutable range view of document windows cast to a specified derived type. - * - * Similar to the const version, but returns a transform_view that allows modifying the - * underlying windows. The transformation is performed using the non-const castWindow - * helper function, which casts each std::shared_ptr to a std::shared_ptr. - * - * If no windows of the requested type T are found in m_windows, an empty range is returned. - * - * @tparam T The derived type of IDocumentWindow to retrieve. - * @return A transform_view range over the mutable vector of document windows cast to std::shared_ptr. - */ - template - requires std::derived_from - std::ranges::transform_view< - std::ranges::ref_view>>, - std::shared_ptr(*)(std::shared_ptr&) - > - getWindows() - { - // Helper: non-capturing function for casting: - std::shared_ptr(*caster)(std::shared_ptr&) = &castWindow; + /** + * @brief Retrieves a mutable range view of document windows cast to a specified derived type. + * + * Similar to the const version, but returns a transform_view that allows modifying the + * underlying windows. The transformation is performed using the non-const castWindow + * helper function, which casts each std::shared_ptr to a std::shared_ptr. + * + * If no windows of the requested type T are found in m_windows, an empty range is returned. + * + * @tparam T The derived type of IDocumentWindow to retrieve. + * @return A transform_view range over the mutable vector of document windows cast to std::shared_ptr. + */ + template + requires std::derived_from + std::ranges::transform_view>>, + std::shared_ptr (*)(std::shared_ptr &)> + getWindows() + { + // Helper: non-capturing function for casting: + std::shared_ptr (*caster)(std::shared_ptr &) = &castWindow; - const auto it = m_windows.find(typeid(T)); - if (it == m_windows.end()) { - static std::vector> empty; - return std::ranges::transform_view(std::ranges::ref_view(empty), caster); - } - return std::ranges::transform_view(std::ranges::ref_view(it->second), caster); - } + const auto it = m_windows.find(typeid(T)); + if (it == m_windows.end()) { + static std::vector> empty; + return std::ranges::transform_view(std::ranges::ref_view(empty), caster); + } + return std::ranges::transform_view(std::ranges::ref_view(it->second), caster); + } - /** - * @brief Assigns a docking identifier to a window. - * - * Registers the specified docking identifier for the window identified by its name by delegating to the docking registry. - * - * @param name The unique name of the window. - * @param id The docking identifier to associate with the window. - */ - void setDockId(const std::string& name, ImGuiID id); + /** + * @brief Assigns a docking identifier to a window. + * + * Registers the specified docking identifier for the window identified by its name by delegating to the docking + * registry. + * + * @param name The unique name of the window. + * @param id The docking identifier to associate with the window. + */ + void setDockId(const std::string &name, ImGuiID id); - /** - * @brief Retrieves the docking identifier associated with a specified window. - * - * This function queries the docking registry for the docking identifier corresponding to the given window name. - * If the window does not have an assigned docking ID, an empty optional is returned. - * - * @param name The name of the window whose docking identifier is being requested. - * @return std::optional The docking identifier if it exists; otherwise, an empty optional. - */ - std::optional getDockId(const std::string& name) const; + /** + * @brief Retrieves the docking identifier associated with a specified window. + * + * This function queries the docking registry for the docking identifier corresponding to the given window name. + * If the window does not have an assigned docking ID, an empty optional is returned. + * + * @param name The name of the window whose docking identifier is being requested. + * @return std::optional The docking identifier if it exists; otherwise, an empty optional. + */ + [[nodiscard]] std::optional getDockId(const std::string &name) const; - std::shared_ptr getFocusedWindow() const; + /** + * @brief Retrieves the currently focused window. + * + * This function iterates through all registered windows and returns a shared pointer to the window + * that is currently focused. If no window is focused, it returns a nullptr. + * + * @return std::shared_ptr A shared pointer to the focused window, or nullptr if none are + * focused. + */ + [[nodiscard]] std::shared_ptr getFocusedWindow() const; - /** - * @brief Removes a window's docking identifier. - * - * This function removes any docking identifier association for the specified window, - * allowing it to be positioned freely or receive a new docking assignment. - * If no docking ID exists for the window, this operation has no effect. - * - * @param name The name of the window whose docking ID should be removed. - */ - void resetDockId(const std::string &name); + /** + * @brief Removes a window's docking identifier. + * + * This function removes any docking identifier association for the specified window, + * allowing it to be positioned freely or receive a new docking assignment. + * If no docking ID exists for the window, this operation has no effect. + * + * @param name The name of the window whose docking ID should be removed. + */ + void resetDockId(const std::string &name); - /** - * @brief Initializes all managed windows. - * - * Iterates through the collection of windows and calls the `setup()` method on each one. - * This function does not handle errors; it assumes each window's setup process runs without exceptions. - */ - void setup() const; + /** + * @brief Initializes all managed windows. + * + * Iterates through the collection of windows and calls the `setup()` method on each one. + * This function does not handle errors; it assumes each window's setup process runs without exceptions. + */ + void setup() const; - /** - * @brief Shuts down all registered windows. - * - * Iterates through each window in the registry and invokes its shutdown routine. - */ - void shutdown() const; + /** + * @brief Shuts down all registered windows. + * + * Iterates through each window in the registry and invokes its shutdown routine. + */ + void shutdown() const; - /** - * @brief Updates all registered windows. - * - * Iterates over the collection of registered windows and calls the update() method on each, - * ensuring that their state is refreshed. - */ - void update() const; + /** + * @brief Updates all registered windows. + * + * Iterates over the collection of registered windows and calls the update() method on each, + * ensuring that their state is refreshed. + */ + void update() const; - /** - * @brief Renders all open windows. - * - * Iterates through the registered windows and invokes the show() method on each window that is currently opened. - */ - void render() const; + /** + * @brief Renders all open windows. + * + * Iterates through the registered windows and invokes the show() method on each window that is currently + * opened. + */ + void render() const; - private: - std::unordered_map>> m_windows; + private: + std::unordered_map>> m_windows; - DockingRegistry m_dockingRegistry; - }; -} + DockingRegistry m_dockingRegistry; + }; +} // namespace nexo::editor diff --git a/editor/src/backends/ImGuiBackend.hpp b/editor/src/backends/ImGuiBackend.hpp index b1d1cbce9..a0a7b8a42 100644 --- a/editor/src/backends/ImGuiBackend.hpp +++ b/editor/src/backends/ImGuiBackend.hpp @@ -17,65 +17,65 @@ #include "renderer/Window.hpp" namespace nexo::editor { - /** - * @class ImGuiBackend - * @brief Static interface class for ImGui backend operations - * - * This class provides a unified interface for ImGui operations across different - * rendering backends. It delegates to the appropriate backend implementation - * based on the defined graphics API (e.g., OpenGL). - */ + /** + * @class ImGuiBackend + * @brief Static interface class for ImGui backend operations + * + * This class provides a unified interface for ImGui operations across different + * rendering backends. It delegates to the appropriate backend implementation + * based on the defined graphics API (e.g., OpenGL). + */ class ImGuiBackend { - public: - /** - * @brief Initializes the ImGui backend with the specified window - * - * @param[in] window The application window to initialize ImGui with - * @throws BackendRendererApiNotSupported If the current graphics API is not supported - */ - static void init(const std::shared_ptr& window); + public: + /** + * @brief Initializes the ImGui backend with the specified window + * + * @param[in] window The application window to initialize ImGui with + * @throws BackendRendererApiNotSupported If the current graphics API is not supported + */ + static void init(const std::shared_ptr& window); - /** - * @brief Shuts down and cleans up the ImGui backend - * - * @throws BackendRendererApiNotSupported If the current graphics API is not supported - */ - static void shutdown(); + /** + * @brief Shuts down and cleans up the ImGui backend + * + * @throws BackendRendererApiNotSupported If the current graphics API is not supported + */ + static void shutdown(); - /** - * @brief Initializes the font atlas for ImGui - * - * @throws BackendRendererApiNotSupported If the current graphics API is not supported - */ - static void initFontAtlas(); + /** + * @brief Initializes the font atlas for ImGui + * + * @throws BackendRendererApiNotSupported If the current graphics API is not supported + */ + static void initFontAtlas(); - /** - * @brief Begins a new ImGui frame - * - * This should be called at the beginning of each frame render cycle - * before any ImGui UI components are drawn. - * - * @throws BackendRendererApiNotSupported If the current graphics API is not supported - */ - static void begin(); + /** + * @brief Begins a new ImGui frame + * + * This should be called at the beginning of each frame render cycle + * before any ImGui UI components are drawn. + * + * @throws BackendRendererApiNotSupported If the current graphics API is not supported + */ + static void begin(); - /** - * @brief Ends the current ImGui frame and renders it to the window - * - * This should be called after all ImGui UI components have been defined - * for the current frame. - * - * @param[in] window The application window to render ImGui to - * @throws BackendRendererApiNotSupported If the current graphics API is not supported - */ - static void end(const std::shared_ptr& window); + /** + * @brief Ends the current ImGui frame and renders it to the window + * + * This should be called after all ImGui UI components have been defined + * for the current frame. + * + * @param[in] window The application window to render ImGui to + * @throws BackendRendererApiNotSupported If the current graphics API is not supported + */ + static void end(const std::shared_ptr& window); - /** - * @brief Sets up the error callback for ImGui on the window - * - * @param[in] window The application window to set the error callback for - * @throws BackendRendererApiNotSupported If the current graphics API is not supported - */ - static void setErrorCallback(const std::shared_ptr& window); + /** + * @brief Sets up the error callback for ImGui on the window + * + * @param[in] window The application window to set the error callback for + * @throws BackendRendererApiNotSupported If the current graphics API is not supported + */ + static void setErrorCallback(const std::shared_ptr& window); }; -} +} // namespace nexo::editor diff --git a/editor/src/backends/opengl/openglImGuiBackend.cpp b/editor/src/backends/opengl/openglImGuiBackend.cpp index 8d20dae89..c95ad6351 100644 --- a/editor/src/backends/opengl/openglImGuiBackend.cpp +++ b/editor/src/backends/opengl/openglImGuiBackend.cpp @@ -12,15 +12,15 @@ // /////////////////////////////////////////////////////////////////////////////// +#include "openglImGuiBackend.hpp" +#include #include "Logger.hpp" #include "exceptions/Exceptions.hpp" -#include "openglImGuiBackend.hpp" -#include "imgui_impl_opengl3.h" #include "imgui_impl_glfw.h" -#include +#include "imgui_impl_opengl3.h" namespace nexo::editor { - void OpenGLImGuiBackend::init(GLFWwindow *window) + void OpenGLImGuiBackend::init(GLFWwindow* window) { if (!ImGui_ImplGlfw_InitForOpenGL(window, true) || !ImGui_ImplOpenGL3_Init("#version 330")) THROW_EXCEPTION(BackendRendererApiInitFailed, "OPENGL"); @@ -35,8 +35,7 @@ namespace nexo::editor { void OpenGLImGuiBackend::initFontAtlas() { - if (!ImGui_ImplOpenGL3_CreateFontsTexture()) - THROW_EXCEPTION(BackendRendererApiFontInitFailed, "OPENGL"); + if (!ImGui_ImplOpenGL3_CreateFontsTexture()) THROW_EXCEPTION(BackendRendererApiFontInitFailed, "OPENGL"); } void OpenGLImGuiBackend::begin() @@ -47,7 +46,7 @@ namespace nexo::editor { ImGui::NewFrame(); } - void OpenGLImGuiBackend::end(GLFWwindow *window) + void OpenGLImGuiBackend::end(GLFWwindow* window) { ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(window); @@ -58,30 +57,32 @@ namespace nexo::editor { static auto errorCallback = [](int error, const char* description) { switch (error) { case GLFW_NOT_INITIALIZED: - case GLFW_NO_CURRENT_CONTEXT: - THROW_EXCEPTION(BackendRendererApiFatalFailure, "OPENGL", "(" + std::to_string(error) + "): " + description); + case GLFW_NO_CURRENT_CONTEXT: + THROW_EXCEPTION(BackendRendererApiFatalFailure, "OPENGL", + "(" + std::to_string(error) + "): " + description); case GLFW_INVALID_ENUM: case GLFW_INVALID_VALUE: LOG(NEXO_WARN, "[OPENGL WARNING] ({}): {}", error, description); - break; + break; case GLFW_OUT_OF_MEMORY: - THROW_EXCEPTION(BackendRendererApiFatalFailure, "OPENGL", "(" + std::to_string(error) + "): Out of memory - " + description); + THROW_EXCEPTION(BackendRendererApiFatalFailure, "OPENGL", + "(" + std::to_string(error) + "): Out of memory - " + description); case GLFW_API_UNAVAILABLE: case GLFW_VERSION_UNAVAILABLE: case GLFW_FORMAT_UNAVAILABLE: LOG(NEXO_ERROR, "[OPENGL ERROR] ({}): {}", error, description); - break; + break; case GLFW_PLATFORM_ERROR: LOG(NEXO_ERROR, "[OPENGL PLATFORM ERROR] ({}): {}", error, description); - break; + break; case GLFW_NO_WINDOW_CONTEXT: LOG(NEXO_WARN, "[OPENGL WARNING] ({}): {}", error, description); - break; + break; default: LOG(NEXO_ERROR, "[OPENGL UNKNOWN ERROR] ({}): {}", error, description); @@ -90,5 +91,4 @@ namespace nexo::editor { return reinterpret_cast(+errorCallback); } -} - +} // namespace nexo::editor diff --git a/editor/src/context/ActionGroup.cpp b/editor/src/context/ActionGroup.cpp index 31dfa00fa..fb9ce47e7 100644 --- a/editor/src/context/ActionGroup.cpp +++ b/editor/src/context/ActionGroup.cpp @@ -30,13 +30,11 @@ namespace nexo::editor { void ActionGroup::redo() { - for (const auto &action : actions) - action->redo(); + for (const auto &action : actions) action->redo(); } void ActionGroup::undo() { - for (const auto &action : std::ranges::reverse_view(actions)) - action->undo(); + for (const auto &action : std::ranges::reverse_view(actions)) action->undo(); } -} +} // namespace nexo::editor diff --git a/editor/src/context/ActionGroup.hpp b/editor/src/context/ActionGroup.hpp index a09e93243..c4e34f143 100644 --- a/editor/src/context/ActionGroup.hpp +++ b/editor/src/context/ActionGroup.hpp @@ -13,26 +13,26 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "actions/Action.hpp" #include #include +#include "actions/Action.hpp" namespace nexo::editor { /** - * Groups multiple actions into a single undoable action - */ + * Groups multiple actions into a single undoable action + */ class ActionGroup final : public Action { - public: - ActionGroup() = default; + public: + ActionGroup() = default; - void addAction(std::unique_ptr action); - [[nodiscard]] bool hasActions() const; - void redo() override; - void undo() override; + void addAction(std::unique_ptr action); + [[nodiscard]] bool hasActions() const; + void redo() override; + void undo() override; - private: - std::vector> actions; + private: + std::vector> actions; }; -} +} // namespace nexo::editor diff --git a/editor/src/context/ActionHistory.cpp b/editor/src/context/ActionHistory.cpp index 0de733672..79d8e5f9f 100644 --- a/editor/src/context/ActionHistory.cpp +++ b/editor/src/context/ActionHistory.cpp @@ -20,8 +20,7 @@ namespace nexo::editor { undoStack.push_back(std::move(action)); redoStack.clear(); - while (undoStack.size() > maxUndoLevels) - undoStack.pop_front(); + while (undoStack.size() > maxUndoLevels) undoStack.pop_front(); } bool ActionHistory::canUndo() const @@ -36,8 +35,7 @@ namespace nexo::editor { void ActionHistory::undo() { - if (!canUndo()) - return; + if (!canUndo()) return; auto action = std::move(undoStack.back()); undoStack.pop_back(); action->undo(); @@ -46,8 +44,7 @@ namespace nexo::editor { void ActionHistory::redo() { - if (!canRedo()) - return; + if (!canRedo()) return; auto action = std::move(redoStack.back()); redoStack.pop_back(); action->redo(); @@ -57,8 +54,7 @@ namespace nexo::editor { void ActionHistory::setMaxUndoLevels(const size_t levels) { maxUndoLevels = levels; - while (undoStack.size() > maxUndoLevels) - undoStack.pop_front(); + while (undoStack.size() > maxUndoLevels) undoStack.pop_front(); } void ActionHistory::clear(const unsigned int count) @@ -69,12 +65,11 @@ namespace nexo::editor { return; } const unsigned int elementsToRemove = std::min(static_cast(undoStack.size()), count); - for (unsigned int i = 0; i < elementsToRemove; ++i) - undoStack.pop_back(); + for (unsigned int i = 0; i < elementsToRemove; ++i) undoStack.pop_back(); } unsigned int ActionHistory::getUndoStackSize() const { return static_cast(undoStack.size()); } -} +} // namespace nexo::editor diff --git a/editor/src/context/ActionHistory.hpp b/editor/src/context/ActionHistory.hpp index 3cfcbc65b..d5f3cf260 100644 --- a/editor/src/context/ActionHistory.hpp +++ b/editor/src/context/ActionHistory.hpp @@ -13,34 +13,34 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "actions/Action.hpp" -#include #include +#include +#include "actions/Action.hpp" namespace nexo::editor { /** - * Maintains the undo and redo stacks - */ + * Maintains the undo and redo stacks + */ class ActionHistory { - public: - // Add a action to history after it was already executed - void addAction(std::unique_ptr action); + public: + // Add an action to history after it was already executed + void addAction(std::unique_ptr action); - [[nodiscard]] bool canUndo() const; - [[nodiscard]] bool canRedo() const; - void undo(); - void redo(); + [[nodiscard]] bool canUndo() const; + [[nodiscard]] bool canRedo() const; + void undo(); + void redo(); - void setMaxUndoLevels(size_t levels); + void setMaxUndoLevels(size_t levels); - void clear(unsigned int count = 0); - [[nodiscard]] unsigned int getUndoStackSize() const; + void clear(unsigned int count = 0); + [[nodiscard]] unsigned int getUndoStackSize() const; - private: - std::deque> undoStack; - std::deque> redoStack; - size_t maxUndoLevels = 50; + private: + std::deque> undoStack; + std::deque> redoStack; + size_t maxUndoLevels = 50; }; -} +} // namespace nexo::editor diff --git a/editor/src/context/ActionManager.cpp b/editor/src/context/ActionManager.cpp index 6f390143a..4bff896d9 100644 --- a/editor/src/context/ActionManager.cpp +++ b/editor/src/context/ActionManager.cpp @@ -70,4 +70,4 @@ namespace nexo::editor { { return history.getUndoStackSize(); } -} +} // namespace nexo::editor diff --git a/editor/src/context/ActionManager.hpp b/editor/src/context/ActionManager.hpp index 96535180d..f36e4bfa0 100644 --- a/editor/src/context/ActionManager.hpp +++ b/editor/src/context/ActionManager.hpp @@ -13,44 +13,45 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "ActionHistory.hpp" +#include #include "ActionGroup.hpp" +#include "ActionHistory.hpp" #include "context/actions/EntityActions.hpp" -#include namespace nexo::editor { class ActionManager { - public: - void recordAction(std::unique_ptr action); - void recordEntityCreation(ecs::Entity entityId); - static std::unique_ptr prepareEntityDeletion(ecs::Entity entityId); - static std::unique_ptr prepareEntityHierarchyDeletion(ecs::Entity entityId); - - template - void recordComponentChange(ecs::Entity entityId, - const typename MementoComponent::Memento& beforeState, - const typename MementoComponent::Memento& afterState) - { - auto action = std::make_unique>(entityId, beforeState, afterState); - recordAction(std::move(action)); - } - - static std::unique_ptr createActionGroup(); - - void undo(); - void redo(); - [[nodiscard]] bool canUndo() const; - [[nodiscard]] bool canRedo() const; - void clearHistory(unsigned int count = 0); - [[nodiscard]] unsigned int getUndoStackSize() const; - - static ActionManager& get() { - static ActionManager instance; - return instance; - } - private: - ActionHistory history; + public: + void recordAction(std::unique_ptr action); + void recordEntityCreation(ecs::Entity entityId); + static std::unique_ptr prepareEntityDeletion(ecs::Entity entityId); + static std::unique_ptr prepareEntityHierarchyDeletion(ecs::Entity entityId); + + template + void recordComponentChange(ecs::Entity entityId, const typename MementoComponent::Memento& beforeState, + const typename MementoComponent::Memento& afterState) + { + auto action = std::make_unique>(entityId, beforeState, afterState); + recordAction(std::move(action)); + } + + static std::unique_ptr createActionGroup(); + + void undo(); + void redo(); + [[nodiscard]] bool canUndo() const; + [[nodiscard]] bool canRedo() const; + void clearHistory(unsigned int count = 0); + [[nodiscard]] unsigned int getUndoStackSize() const; + + static ActionManager& get() + { + static ActionManager instance; + return instance; + } + + private: + ActionHistory history; }; -} +} // namespace nexo::editor diff --git a/editor/src/context/Selector.cpp b/editor/src/context/Selector.cpp index 35e05b517..c5a5fe0dc 100644 --- a/editor/src/context/Selector.cpp +++ b/editor/src/context/Selector.cpp @@ -67,14 +67,9 @@ namespace nexo::editor { bool Selector::addToSelection(const std::string_view uuid, const int entity, const SelectionType type) { - if (m_selectedEntityIds.contains(entity)) - return false; + if (m_selectedEntityIds.contains(entity)) return false; - SelectionData data = { - .entityId = entity, - .uuid = std::string(uuid), - .type = type - }; + SelectionData data = {.entityId = entity, .uuid = std::string(uuid), .type = type}; m_selectedEntities.push_back(std::move(data)); m_selectedEntityIds.insert(entity); @@ -92,9 +87,9 @@ namespace nexo::editor { return true; } - bool Selector::removeFromSelection(const int entity) { - if (!m_selectedEntityIds.contains(entity)) - return false; + bool Selector::removeFromSelection(const int entity) + { + if (!m_selectedEntityIds.contains(entity)) return false; m_selectedEntityIds.erase(entity); for (auto it = m_selectedEntities.begin(); it != m_selectedEntities.end(); ++it) { @@ -120,8 +115,7 @@ namespace nexo::editor { void Selector::clearSelection() { - for (const auto& data : m_selectedEntities) - removeSelectedTag(data.entityId); + for (const auto& data : m_selectedEntities) removeSelectedTag(data.entityId); m_selectedEntities.clear(); m_selectedEntityIds.clear(); @@ -147,8 +141,7 @@ namespace nexo::editor { SelectionType Selector::getSelectionType(const int entity) const { - if (!m_selectedEntityIds.contains(entity)) - return SelectionType::NONE; + if (!m_selectedEntityIds.contains(entity)) return SelectionType::NONE; for (const auto& data : m_selectedEntities) { if (data.entityId == entity) { @@ -193,4 +186,4 @@ namespace nexo::editor { if (Application::m_coordinator->entityHasComponent(entity)) Application::m_coordinator->removeComponent(entity); } -} +} // namespace nexo::editor diff --git a/editor/src/context/Selector.hpp b/editor/src/context/Selector.hpp index 0ed6102d5..4a322189c 100644 --- a/editor/src/context/Selector.hpp +++ b/editor/src/context/Selector.hpp @@ -15,10 +15,10 @@ #include "ecs/Coordinator.hpp" +#include #include #include #include -#include namespace nexo::editor { @@ -43,7 +43,7 @@ namespace nexo::editor { * entity UUID to UI handle mappings for consistent labeling in the interface. */ class Selector { - public: + public: /** * @brief Gets the primary selected entity * @@ -53,28 +53,28 @@ namespace nexo::editor { * * @return int The entity ID of the primary selection, or -1 if no entity is selected */ - int getPrimaryEntity() const; + [[nodiscard]] int getPrimaryEntity() const; /** * @brief Gets all selected entities * * @return const std::vector& A reference to the vector of all selected entity IDs */ - const std::vector& getSelectedEntities() const; + [[nodiscard]] const std::vector& getSelectedEntities() const; /** * @brief Gets the UUID of the primary entity * * @return const std::string& The UUID of the primary entity */ - const std::string& getPrimaryUuid() const; + [[nodiscard]] const std::string& getPrimaryUuid() const; /** * @brief Gets all selected entity UUIDs * * @return std::vector Vector containing UUIDs of all selected entities */ - std::vector getSelectedUuids() const; + [[nodiscard]] std::vector getSelectedUuids() const; /** * @brief Selects a single entity, replacing the current selection @@ -128,7 +128,7 @@ namespace nexo::editor { * * @return int The entity ID of the selected scene, or -1 if no scene is selected */ - int getSelectedScene() const; + [[nodiscard]] int getSelectedScene() const; /** * @brief Clears the current entity selection @@ -142,7 +142,7 @@ namespace nexo::editor { * @return true If the entity is selected * @return false If the entity is not selected */ - bool isEntitySelected(int entity) const; + [[nodiscard]] bool isEntitySelected(int entity) const; /** * @brief Checks if any entity is currently selected @@ -150,16 +150,16 @@ namespace nexo::editor { * @return true If at least one entity is selected * @return false If no entities are selected */ - bool hasSelection() const; + [[nodiscard]] bool hasSelection() const; /** * @brief Gets the primary selection type * * @return SelectionType The type of the primary selected entity */ - SelectionType getPrimarySelectionType() const; + [[nodiscard]] SelectionType getPrimarySelectionType() const; - SelectionType getSelectionType(int entity) const; + [[nodiscard]] SelectionType getSelectionType(int entity) const; /** * @brief Sets the selection type (only applied to subsequent selections) @@ -188,32 +188,35 @@ namespace nexo::editor { */ void setUiHandle(const std::string& uuid, std::string_view handle); - static Selector& get() { + static Selector& get() + { static Selector instance; return instance; } - private: + private: // Selection data struct SelectionData { int entityId; std::string uuid; SelectionType type; }; - std::vector m_selectedEntities; // Ordered list of selected entities - std::unordered_set m_selectedEntityIds; // Set for quick lookups + std::vector m_selectedEntities; // Ordered list of selected entities + std::unordered_set m_selectedEntityIds; // Set for quick lookups - int m_selectedScene = -1; + int m_selectedScene = -1; SelectionType m_defaultSelectionType = SelectionType::ENTITY; struct TransparentHasher { using is_transparent = void; // Marks this hasher as transparent for heterogeneous lookup - size_t operator()(std::string_view key) const noexcept { + size_t operator()(std::string_view key) const noexcept + { return std::hash{}(key); } - size_t operator()(const std::string& key) const noexcept { + size_t operator()(const std::string& key) const noexcept + { return std::hash{}(key); } }; @@ -223,4 +226,4 @@ namespace nexo::editor { static void addSelectedTag(int entity); static void removeSelectedTag(int entity); }; -} +} // namespace nexo::editor diff --git a/editor/src/context/ThumbnailCache.cpp b/editor/src/context/ThumbnailCache.cpp index dc073c00d..3458f4dae 100644 --- a/editor/src/context/ThumbnailCache.cpp +++ b/editor/src/context/ThumbnailCache.cpp @@ -108,7 +108,8 @@ namespace nexo::editor { return createTextureThumbnail(textureRef, size); } - unsigned int ThumbnailCache::getModelThumbnail(const assets::AssetRef& modelRef, const glm::vec2& size) + unsigned int ThumbnailCache::getModelThumbnail(const assets::AssetRef& modelRef, + const glm::vec2& size) { if (!modelRef.isValid()) return 0; @@ -196,10 +197,10 @@ namespace nexo::editor { utils::ScenePreviewOut previewInfo; const ecs::Entity previewEntity = - EntityFactory3D::createModel(modelRef, // model - glm::vec3(0.0f, 0.0f, 0.0f), // position - glm::vec3(1.0f), // size - glm::vec3(0.0f, 0.0f, 0.0f) // rotation - angled for better lighting + EntityFactory3D::createModel(modelRef, // model + glm::vec3(0.0f, 0.0f, 0.0f), // position + glm::vec3(1.0f), // size + glm::vec3(0.0f, 0.0f, 0.0f) // rotation - angled for better lighting ); genScenePreview("Material_Thumbnail", size, previewEntity, previewInfo); diff --git a/editor/src/context/ThumbnailCache.hpp b/editor/src/context/ThumbnailCache.hpp index 8f5b235ae..58cc25b36 100644 --- a/editor/src/context/ThumbnailCache.hpp +++ b/editor/src/context/ThumbnailCache.hpp @@ -108,7 +108,7 @@ namespace nexo::editor { * @param assetId UUID of the asset. * @return True if a thumbnail is cached, false otherwise. */ - bool hasThumbnail(const boost::uuids::uuid& assetId) const; + [[nodiscard]] bool hasThumbnail(const boost::uuids::uuid& assetId) const; private: ThumbnailCache() = default; diff --git a/editor/src/context/actions/Action.hpp b/editor/src/context/actions/Action.hpp index 256481172..3c87209de 100644 --- a/editor/src/context/actions/Action.hpp +++ b/editor/src/context/actions/Action.hpp @@ -16,15 +16,15 @@ namespace nexo::editor { /** - * Base Action interface for all undoable operations - */ + * Base Action interface for all undoable operations + */ class Action { - public: - virtual ~Action() = default; + public: + virtual ~Action() = default; - virtual void redo() = 0; + virtual void redo() = 0; - virtual void undo() = 0; + virtual void undo() = 0; }; -} +} // namespace nexo::editor diff --git a/editor/src/context/actions/AssetActions.cpp b/editor/src/context/actions/AssetActions.cpp index a215bd541..f56553109 100644 --- a/editor/src/context/actions/AssetActions.cpp +++ b/editor/src/context/actions/AssetActions.cpp @@ -13,25 +13,25 @@ /////////////////////////////////////////////////////////////////////////////// #include "AssetActions.hpp" +#include #include "assets/AssetCatalog.hpp" namespace nexo::editor { - AssetMoveAction::AssetMoveAction(assets::AssetID assetId, - const std::string& fromPath, - const std::string& toPath) - : m_assetId(assetId), m_fromPath(fromPath), m_toPath(toPath) {} + AssetMoveAction::AssetMoveAction(const assets::AssetID assetId, std::string fromPath, std::string toPath) + : m_assetId(assetId), m_fromPath(std::move(fromPath)), m_toPath(std::move(toPath)) + {} void AssetMoveAction::redo() { - auto& catalog = assets::AssetCatalog::getInstance(); + const auto& catalog = assets::AssetCatalog::getInstance(); catalog.moveAsset(m_assetId, m_toPath); } void AssetMoveAction::undo() { - auto& catalog = assets::AssetCatalog::getInstance(); + const auto& catalog = assets::AssetCatalog::getInstance(); catalog.moveAsset(m_assetId, m_fromPath); } -} // namespace nexo::editor \ No newline at end of file +} // namespace nexo::editor diff --git a/editor/src/context/actions/AssetActions.hpp b/editor/src/context/actions/AssetActions.hpp index 72fb09395..1601c3cb1 100644 --- a/editor/src/context/actions/AssetActions.hpp +++ b/editor/src/context/actions/AssetActions.hpp @@ -13,9 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include #include "Action.hpp" #include "assets/Asset.hpp" -#include namespace nexo::editor { @@ -25,18 +25,16 @@ namespace nexo::editor { * Handles undo/redo for asset location changes */ class AssetMoveAction final : public Action { - public: - AssetMoveAction(assets::AssetID assetId, - const std::string& fromPath, - const std::string& toPath); + public: + AssetMoveAction(assets::AssetID assetId, std::string fromPath, std::string toPath); void redo() override; void undo() override; - private: + private: assets::AssetID m_assetId; std::string m_fromPath; std::string m_toPath; }; -} // namespace nexo::editor \ No newline at end of file +} // namespace nexo::editor diff --git a/editor/src/context/actions/ComponentRestoreFactory.cpp b/editor/src/context/actions/ComponentRestoreFactory.cpp index 222504864..92c2a80b6 100644 --- a/editor/src/context/actions/ComponentRestoreFactory.cpp +++ b/editor/src/context/actions/ComponentRestoreFactory.cpp @@ -31,25 +31,53 @@ namespace nexo::editor { using ActionFactory = std::function(ecs::Entity)>; static const std::unordered_map factories{ - {typeid(components::TransformComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::RenderComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::SceneTag), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::CameraComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::AmbientLightComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::DirectionalLightComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::PointLightComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::SpotLightComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::UuidComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::PerspectiveCameraController), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::PerspectiveCameraTarget), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::MaterialComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::StaticMeshComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::ParentComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::NameComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, - {typeid(components::RootComponent), [](ecs::Entity e){ return std::make_unique>(e); }}, + {typeid(components::TransformComponent), + [](ecs::Entity e) { return std::make_unique>(e); }}, + {typeid(components::RenderComponent), + [](ecs::Entity e) { return std::make_unique>(e); }}, + {typeid(components::SceneTag), + [](ecs::Entity e) { return std::make_unique>(e); }}, + {typeid(components::CameraComponent), + [](ecs::Entity e) { return std::make_unique>(e); }}, + {typeid(components::AmbientLightComponent), + [](ecs::Entity e) { + return std::make_unique>(e); + }}, + {typeid(components::DirectionalLightComponent), + [](ecs::Entity e) { + return std::make_unique>(e); + }}, + {typeid(components::PointLightComponent), + [](ecs::Entity e) { + return std::make_unique>(e); + }}, + {typeid(components::SpotLightComponent), + [](ecs::Entity e) { return std::make_unique>(e); }}, + {typeid(components::UuidComponent), + [](ecs::Entity e) { return std::make_unique>(e); }}, + {typeid(components::PerspectiveCameraController), + [](ecs::Entity e) { + return std::make_unique>(e); + }}, + {typeid(components::PerspectiveCameraTarget), + [](ecs::Entity e) { + return std::make_unique>(e); + }}, + {typeid(components::MaterialComponent), + [](ecs::Entity e) { return std::make_unique>(e); }}, + {typeid(components::StaticMeshComponent), + [](ecs::Entity e) { + return std::make_unique>(e); + }}, + {typeid(components::ParentComponent), + [](ecs::Entity e) { return std::make_unique>(e); }}, + {typeid(components::NameComponent), + [](ecs::Entity e) { return std::make_unique>(e); }}, + {typeid(components::RootComponent), + [](ecs::Entity e) { return std::make_unique>(e); }}, }; - auto typeId = std::type_index(typeIndex.type()); + const auto typeId = std::type_index(typeIndex.type()); auto it = factories.find(typeId); if (it != factories.end()) { return (it->second)(entity); diff --git a/editor/src/context/actions/ComponentRestoreFactory.hpp b/editor/src/context/actions/ComponentRestoreFactory.hpp index c4f8facf9..280ea518e 100644 --- a/editor/src/context/actions/ComponentRestoreFactory.hpp +++ b/editor/src/context/actions/ComponentRestoreFactory.hpp @@ -13,15 +13,14 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include +#include #include "Action.hpp" #include "ecs/Definitions.hpp" -#include -#include -#include namespace nexo::editor { class ComponentRestoreFactory { - public: - static std::unique_ptr createRestoreComponent(ecs::Entity entity, const std::any& typeIndex); + public: + static std::unique_ptr createRestoreComponent(ecs::Entity entity, const std::any& typeIndex); }; -} +} // namespace nexo::editor diff --git a/editor/src/context/actions/EntityActions.cpp b/editor/src/context/actions/EntityActions.cpp index d8cfbc0af..d5ebcc78b 100644 --- a/editor/src/context/actions/EntityActions.cpp +++ b/editor/src/context/actions/EntityActions.cpp @@ -19,20 +19,18 @@ namespace nexo::editor { void EntityCreationAction::redo() { - const auto &coordinator = Application::m_coordinator; - m_entityId = coordinator->createEntity(); + const auto& coordinator = Application::m_coordinator; + m_entityId = coordinator->createEntity(); - for (const auto &action : m_componentRestoreActions) - action->undo(); + for (const auto& action : m_componentRestoreActions) action->undo(); } void EntityCreationAction::undo() { - const auto &coordinator = Application::m_coordinator; + const auto& coordinator = Application::m_coordinator; const std::vector& componentsTypeIndex = coordinator->getAllComponents(m_entityId); - for (const auto &typeIndex : componentsTypeIndex) { - if (!coordinator->supportsMementoPattern(typeIndex)) - continue; + for (const auto& typeIndex : componentsTypeIndex) { + if (!coordinator->supportsMementoPattern(typeIndex)) continue; m_componentRestoreActions.push_back(ComponentRestoreFactory::createRestoreComponent(m_entityId, typeIndex)); } coordinator->destroyEntity(m_entityId); @@ -40,19 +38,17 @@ namespace nexo::editor { EntityDeletionAction::EntityDeletionAction(const ecs::Entity entityId) : m_entityId(entityId) { - const auto &coordinator = Application::m_coordinator; + const auto& coordinator = Application::m_coordinator; const std::vector& componentsTypeIndex = coordinator->getAllComponents(m_entityId); - for (const auto &typeIndex : componentsTypeIndex) { - if (!coordinator->supportsMementoPattern(typeIndex)) - continue; + for (const auto& typeIndex : componentsTypeIndex) { + if (!coordinator->supportsMementoPattern(typeIndex)) continue; auto typeId = std::type_index(typeIndex.type()); if (typeId == typeid(components::ParentComponent)) { auto parentOpt = coordinator->tryGetComponent(entityId); if (parentOpt.has_value()) { ecs::Entity oldParent = parentOpt->get().parent; m_componentRestoreActions.push_back( - std::make_unique(entityId, oldParent, ecs::INVALID_ENTITY) - ); + std::make_unique(entityId, oldParent, ecs::INVALID_ENTITY)); } continue; } @@ -64,10 +60,10 @@ namespace nexo::editor { { // Simply destroy the entity const auto& coordinator = Application::m_coordinator; - auto parentOpt = coordinator->tryGetComponent(m_entityId); + auto parentOpt = coordinator->tryGetComponent(m_entityId); if (parentOpt.has_value()) { - ecs::Entity oldParent = parentOpt->get().parent; - auto parentTransformOpt = coordinator->tryGetComponent(oldParent); + const ecs::Entity oldParent = parentOpt->get().parent; + const auto parentTransformOpt = coordinator->tryGetComponent(oldParent); if (parentTransformOpt.has_value()) { parentTransformOpt->get().removeChild(m_entityId); } @@ -78,9 +74,8 @@ namespace nexo::editor { void EntityDeletionAction::undo() { const auto& coordinator = Application::m_coordinator; - m_entityId = coordinator->createEntity(); - for (const auto &action : m_componentRestoreActions) - action->undo(); + m_entityId = coordinator->createEntity(); + for (const auto& action : m_componentRestoreActions) action->undo(); } void EntityParentChangeAction::redo() @@ -162,12 +157,11 @@ namespace nexo::editor { } } - EntityHierarchyDeletionAction::EntityHierarchyDeletionAction(ecs::Entity rootEntity) - : m_root(rootEntity), m_group(std::make_unique()) + EntityHierarchyDeletionAction::EntityHierarchyDeletionAction(const ecs::Entity rootEntity) + : m_root(rootEntity), m_group(std::make_unique()) { std::function collectActions = [&](ecs::Entity entity) { - - auto transformOpt = Application::m_coordinator->tryGetComponent(entity); + const auto transformOpt = Application::m_coordinator->tryGetComponent(entity); if (transformOpt) { for (const auto& child : transformOpt->get().children) { collectActions(child); @@ -176,7 +170,7 @@ namespace nexo::editor { } } - auto parentOpt = Application::m_coordinator->tryGetComponent(entity); + const auto parentOpt = Application::m_coordinator->tryGetComponent(entity); if (parentOpt && parentOpt->get().parent != ecs::INVALID_ENTITY) { ecs::Entity parent = parentOpt->get().parent; m_parentRelations.emplace_back(entity, parent); @@ -188,19 +182,21 @@ namespace nexo::editor { collectActions(rootEntity); } - void EntityHierarchyDeletionAction::redo() { + void EntityHierarchyDeletionAction::redo() + { m_group->redo(); } - void EntityHierarchyDeletionAction::undo() { + void EntityHierarchyDeletionAction::undo() + { m_group->undo(); } - EntityHierarchyCreationAction::EntityHierarchyCreationAction(ecs::Entity rootEntity) + EntityHierarchyCreationAction::EntityHierarchyCreationAction(const ecs::Entity rootEntity) : m_root(rootEntity), m_group(std::make_unique()) { std::function collectActions = [&](ecs::Entity entity) { - auto transformOpt = Application::m_coordinator->tryGetComponent(entity); + const auto transformOpt = Application::m_coordinator->tryGetComponent(entity); if (transformOpt) { for (const auto& child : transformOpt->get().children) { collectActions(child); @@ -209,7 +205,7 @@ namespace nexo::editor { } } - auto parentOpt = Application::m_coordinator->tryGetComponent(entity); + const auto parentOpt = Application::m_coordinator->tryGetComponent(entity); if (parentOpt && parentOpt->get().parent != ecs::INVALID_ENTITY) { ecs::Entity parent = parentOpt->get().parent; m_parentRelations.emplace_back(entity, parent); @@ -222,20 +218,19 @@ namespace nexo::editor { for (const auto& [child, parent] : m_parentRelations) { if (parent != ecs::INVALID_ENTITY) { - m_group->addAction(std::make_unique( - child, ecs::INVALID_ENTITY, parent)); + m_group->addAction(std::make_unique(child, ecs::INVALID_ENTITY, parent)); } } } - void EntityHierarchyCreationAction::redo() { + void EntityHierarchyCreationAction::redo() + { m_group->redo(); } - void EntityHierarchyCreationAction::undo() { + void EntityHierarchyCreationAction::undo() + { m_group->undo(); } - - -} +} // namespace nexo::editor diff --git a/editor/src/context/actions/EntityActions.hpp b/editor/src/context/actions/EntityActions.hpp index d04d71017..8b0bef406 100644 --- a/editor/src/context/actions/EntityActions.hpp +++ b/editor/src/context/actions/EntityActions.hpp @@ -21,177 +21,178 @@ namespace nexo::editor { template class ComponentRestoreAction final : public Action { - public: - explicit ComponentRestoreAction(const ecs::Entity entity) : m_entity(entity) - { - ComponentType &target = Application::m_coordinator->getComponent(m_entity); - m_memento = target.save(); - }; - - void undo() override - { - ComponentType target; - target.restore(m_memento); - Application::m_coordinator->addComponent(m_entity, target); - } - - void redo() override - { - //We have nothing to do here since we are simply redeleting the entity and its components - } - - private: - ecs::Entity m_entity; - typename ComponentType::Memento m_memento; + public: + explicit ComponentRestoreAction(const ecs::Entity entity) : m_entity(entity) + { + ComponentType &target = Application::m_coordinator->getComponent(m_entity); + m_memento = target.save(); + }; + + void undo() override + { + ComponentType target; + target.restore(m_memento); + Application::m_coordinator->addComponent(m_entity, target); + } + + void redo() override + { + // We have nothing to do here since we are simply redeleting the entity and its components + } + + private: + ecs::Entity m_entity; + typename ComponentType::Memento m_memento; }; template class ComponentAddAction final : public Action { - public: - explicit ComponentAddAction(const ecs::Entity entity) - : m_entity(entity) {} - - void undo() override - { - m_memento = Application::m_coordinator->getComponent(m_entity).save(); - Application::m_coordinator->removeComponent(m_entity); - } - - void redo() override - { - //We have nothing to do here since we are simply redeleting the entity and its components - } - - private: - ecs::Entity m_entity; - typename ComponentType::Memento m_memento; + public: + explicit ComponentAddAction(const ecs::Entity entity) : m_entity(entity) + {} + + void undo() override + { + m_memento = Application::m_coordinator->getComponent(m_entity).save(); + Application::m_coordinator->removeComponent(m_entity); + } + + void redo() override + { + // We have nothing to do here since we are simply redeleting the entity and its components + } + + private: + ecs::Entity m_entity; + typename ComponentType::Memento m_memento; }; template class ComponentRemoveAction final : public Action { - public: - explicit ComponentRemoveAction(const ecs::Entity entity) : m_entity(entity) - { - m_memento = Application::m_coordinator->getComponent(m_entity).save(); - } - - void undo() override - { - ComponentType target; - target.restore(m_memento); - Application::m_coordinator->addComponent(m_entity, target); - } - - void redo() override - { - Application::m_coordinator->removeComponent(m_entity); - } - - private: - ecs::Entity m_entity; - typename ComponentType::Memento m_memento; + public: + explicit ComponentRemoveAction(const ecs::Entity entity) : m_entity(entity) + { + m_memento = Application::m_coordinator->getComponent(m_entity).save(); + } + + void undo() override + { + ComponentType target; + target.restore(m_memento); + Application::m_coordinator->addComponent(m_entity, target); + } + + void redo() override + { + Application::m_coordinator->removeComponent(m_entity); + } + + private: + ecs::Entity m_entity; + typename ComponentType::Memento m_memento; }; template class ComponentChangeAction final : public Action { - public: - explicit ComponentChangeAction( - const ecs::Entity entity, - const typename ComponentType::Memento& before, - const typename ComponentType::Memento& after - ) : m_entity(entity), m_beforeState(before), m_afterState(after){} - - void redo() override - { - ComponentType &target = Application::m_coordinator->getComponent(m_entity); - target.restore(m_afterState); - } - - void undo() override - { - ComponentType &target = Application::m_coordinator->getComponent(m_entity); - target.restore(m_beforeState); - } - - private: - ecs::Entity m_entity; - typename ComponentType::Memento m_beforeState; - typename ComponentType::Memento m_afterState; + public: + explicit ComponentChangeAction(const ecs::Entity entity, const typename ComponentType::Memento &before, + const typename ComponentType::Memento &after) + : m_entity(entity), m_beforeState(before), m_afterState(after) + {} + + void redo() override + { + ComponentType &target = Application::m_coordinator->getComponent(m_entity); + target.restore(m_afterState); + } + + void undo() override + { + ComponentType &target = Application::m_coordinator->getComponent(m_entity); + target.restore(m_beforeState); + } + + private: + ecs::Entity m_entity; + typename ComponentType::Memento m_beforeState; + typename ComponentType::Memento m_afterState; }; /** - * Stores information needed to undo/redo entity creation - * Relies on engine systems for actual creation/deletion logic - */ + * Stores information needed to undo/redo entity creation + * Relies on engine systems for actual creation/deletion logic + */ class EntityCreationAction final : public Action { - public: - explicit EntityCreationAction(const ecs::Entity entityId) - : m_entityId(entityId) {} + public: + explicit EntityCreationAction(const ecs::Entity entityId) : m_entityId(entityId) + {} - void redo() override; - void undo() override; + void redo() override; + void undo() override; - private: - ecs::Entity m_entityId; - std::vector> m_componentRestoreActions; + private: + ecs::Entity m_entityId; + std::vector> m_componentRestoreActions; }; /** - * Stores information needed to undo/redo entity deletion - * Relies on engine systems for actual deletion logic - */ + * Stores information needed to undo/redo entity deletion + * Relies on engine systems for actual deletion logic + */ class EntityDeletionAction final : public Action { - public: - explicit EntityDeletionAction(ecs::Entity entityId); - - void redo() override; - void undo() override; - private: - ecs::Entity m_entityId; - std::vector> m_componentRestoreActions; + public: + explicit EntityDeletionAction(ecs::Entity entityId); + + void redo() override; + void undo() override; + + private: + ecs::Entity m_entityId; + std::vector> m_componentRestoreActions; }; /** - * Stores information needed to undo/redo entity parent changes - * Handles hierarchy component updates - */ + * Stores information needed to undo/redo entity parent changes + * Handles hierarchy component updates + */ class EntityParentChangeAction final : public Action { - public: - EntityParentChangeAction(const ecs::Entity entity, const ecs::Entity oldParent, const ecs::Entity newParent) - : m_entity(entity), m_oldParent(oldParent), m_newParent(newParent) {} + public: + EntityParentChangeAction(const ecs::Entity entity, const ecs::Entity oldParent, const ecs::Entity newParent) + : m_entity(entity), m_oldParent(oldParent), m_newParent(newParent) + {} - void redo() override; - void undo() override; + void redo() override; + void undo() override; - private: - ecs::Entity m_entity; - ecs::Entity m_oldParent; - ecs::Entity m_newParent; + private: + ecs::Entity m_entity; + ecs::Entity m_oldParent; + ecs::Entity m_newParent; }; class EntityHierarchyDeletionAction final : public Action { - public: + public: explicit EntityHierarchyDeletionAction(ecs::Entity rootEntity); void redo() override; void undo() override; - private: + private: ecs::Entity m_root; std::unique_ptr m_group; std::vector> m_parentRelations; }; class EntityHierarchyCreationAction final : public Action { - public: + public: explicit EntityHierarchyCreationAction(ecs::Entity rootEntity); void redo() override; void undo() override; - private: + private: ecs::Entity m_root; std::unique_ptr m_group; std::vector> m_parentRelations; }; -} +} // namespace nexo::editor diff --git a/editor/src/context/actions/StateAction.hpp b/editor/src/context/actions/StateAction.hpp index fbac24d4c..dab87d12f 100644 --- a/editor/src/context/actions/StateAction.hpp +++ b/editor/src/context/actions/StateAction.hpp @@ -13,10 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "Action.hpp" - #include #include +#include "Action.hpp" namespace nexo::editor { @@ -48,5 +47,4 @@ namespace nexo::editor { typename T::Memento m_beforeState; typename T::Memento m_afterState; }; - } diff --git a/editor/src/exceptions/Exceptions.hpp b/editor/src/exceptions/Exceptions.hpp index bde4bd7ed..ab4d69043 100644 --- a/editor/src/exceptions/Exceptions.hpp +++ b/editor/src/exceptions/Exceptions.hpp @@ -15,114 +15,265 @@ #pragma once #include "Exception.hpp" +#include "ecs/Entity.hpp" // to not remove +#include #include #include -#include namespace nexo::editor { class FileNotFoundException final : public Exception { - public: - /** - * @brief Constructs a FileNotFoundException. - * - * Initializes the exception with an error message indicating that the specified file was not found. - * The error message is automatically formatted to include the given file path. - * - * @param filePath The path of the file that could not be located. - * @param loc The source location where the exception is thrown (defaults to the current location). - */ - explicit FileNotFoundException(const std::string &filePath, - const std::source_location loc = std::source_location::current()) - : Exception("File not found: " + filePath, loc) {} + public: + /** + * @brief Constructs a FileNotFoundException. + * + * Initializes the exception with an error message indicating that the specified file was not found. + * The error message is automatically formatted to include the given file path. + * + * @param filePath The path of the file that could not be located. + * @param loc The source location where the exception is thrown (defaults to the current location). + */ + explicit FileNotFoundException(const std::string &filePath, + const std::source_location &loc = std::source_location::current()) + : Exception("File not found: " + filePath, loc) + {} }; + /** + * @brief Exception class for file read errors. + * + * This exception is thrown when an error occurs while reading a file. + * It includes the file path and a specific error message to provide context about the failure. + */ class FileReadException final : public Exception { - public: - explicit FileReadException(const std::string &filePath, const std::string &message, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Error reading file {}: {}", filePath, message), loc) {} + public: + explicit FileReadException(const std::string &filePath, const std::string &message, + const std::source_location &loc = std::source_location::current()) + : Exception(std::format("Error reading file {}: {}", filePath, message), loc) + {} }; + /** + * @brief Exception class for file write errors. + * + * This exception is thrown when an error occurs while writing to a file. + * It includes the file path and a specific error message to provide context about the failure. + */ class FileWriteException final : public Exception { - public: - explicit FileWriteException(const std::string &filePath, const std::string &message, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Error writing to file {}: {}", filePath, message), loc) {} + public: + explicit FileWriteException(const std::string &filePath, const std::string &message, + const std::source_location &loc = std::source_location::current()) + : Exception(std::format("Error writing to file {}: {}", filePath, message), loc) + {} }; class WindowNotRegistered final : public Exception { - public: - /** - * @brief Constructs a WindowNotRegistered exception. - * - * Initializes an exception indicating that the specified window type is not registered in the WindowRegistry. - * - * @param windowTypeIndex The type index of the unregistered window. - * @param loc The source location where the exception is thrown (defaults to the current location). - */ - explicit WindowNotRegistered(const std::type_index windowTypeIndex, const std::source_location loc = std::source_location::current()) - : Exception(std::format("Window not registered: {}. Make sure the window is registered in the WindowRegistry before accessing it.", windowTypeIndex.name()), loc) {} + public: + /** + * @brief Constructs a WindowNotRegistered exception. + * + * Initializes an exception indicating that the specified window type is not registered in the WindowRegistry. + * + * @param windowTypeIndex The type index of the unregistered window. + * @param loc The source location where the exception is thrown (defaults to the current location). + */ + explicit WindowNotRegistered(const std::type_index windowTypeIndex, + const std::source_location &loc = std::source_location::current()) + : Exception(std::format("Window not registered: {}. Make sure the window is registered in the " + "WindowRegistry before accessing it.", + windowTypeIndex.name()), + loc) + {} }; class WindowAlreadyRegistered final : public Exception { - public: - /** - * @brief Constructs a WindowAlreadyRegistered exception. - * - * Initializes an exception indicating that the specified window type with the specified name is already registered in the WindowRegistry. - * - * @param windowTypeIndex The type index of the unregistered window. - * @param windowName The name of the window. - * @param loc The source location where the exception is thrown (defaults to the current location). - */ - explicit WindowAlreadyRegistered(const std::type_index windowTypeIndex, const std::string &windowName, const std::source_location loc = std::source_location::current()) - : Exception(std::format("Window {} already registered as: {}. Make sure the type and name is unique.", windowName, windowTypeIndex.name()), loc) {} + public: + /** + * @brief Constructs a WindowAlreadyRegistered exception. + * + * Initializes an exception indicating that the specified window type with the specified name is already + * registered in the WindowRegistry. + * + * @param windowTypeIndex The type index of the unregistered window. + * @param windowName The name of the window. + * @param loc The source location where the exception is thrown (defaults to the current location). + */ + explicit WindowAlreadyRegistered(const std::type_index windowTypeIndex, const std::string &windowName, + const std::source_location &loc = std::source_location::current()) + : Exception(std::format("Window {} already registered as: {}. Make sure the type and name is unique.", + windowName, windowTypeIndex.name()), + loc) + {} }; class BackendRendererApiNotSupported final : public Exception { - public: - /** - * @brief Constructs a BackendRendererApiNotSupported exception. - * - * This exception is thrown when a backend render API is not supported. - * The error message is generated by appending the given backend API name to a predefined message. - * - * @param backendApiName The name of the unsupported backend render API. - * @param loc The source location where the exception is thrown, defaulting to the current location. - */ - explicit BackendRendererApiNotSupported(const std::string &backendApiName, - const std::source_location loc = std::source_location::current()) - : Exception("Backend render API not supported: " + backendApiName, loc) {} + public: + /** + * @brief Constructs a BackendRendererApiNotSupported exception. + * + * This exception is thrown when a backend render API is not supported. + * The error message is generated by appending the given backend API name to a predefined message. + * + * @param backendApiName The name of the unsupported backend render API. + * @param loc The source location where the exception is thrown, defaulting to the current location. + */ + explicit BackendRendererApiNotSupported(const std::string &backendApiName, + const std::source_location &loc = std::source_location::current()) + : Exception("Backend render API not supported: " + backendApiName, loc) + {} }; + /** + * @brief Exception class for backend renderer API initialization failures. + * + * This exception is thrown when the initialization of a backend renderer API fails. + * It includes the name of the backend API to provide context about the failure. + */ class BackendRendererApiInitFailed final : public Exception { - public: - explicit BackendRendererApiInitFailed(const std::string &backendApiName, - const std::source_location loc = std::source_location::current()) - : Exception("Backend render API init failed: " + backendApiName, loc) {} + public: + explicit BackendRendererApiInitFailed(const std::string &backendApiName, + const std::source_location &loc = std::source_location::current()) + : Exception("Backend render API init failed: " + backendApiName, loc) + {} }; + /** + * @brief Exception class for backend renderer API font initialization failures. + * + * This exception is thrown when the font initialization of a backend renderer API fails. + * It includes the name of the backend API to provide context about the failure. + */ class BackendRendererApiFontInitFailed final : public Exception { - public: - explicit BackendRendererApiFontInitFailed(const std::string &backendApiName, - const std::source_location loc = std::source_location::current()) - : Exception("Backend render API font init failed: " + backendApiName, loc) {} + public: + explicit BackendRendererApiFontInitFailed(const std::string &backendApiName, + const std::source_location &loc = std::source_location::current()) + : Exception("Backend render API font init failed: " + backendApiName, loc) + {} }; + /** + * @brief Exception class for fatal errors in backend renderer APIs. + * + * This exception is thrown when a fatal error occurs in a backend renderer API. + * It includes the name of the backend API and a specific error message to provide context about the failure. + */ class BackendRendererApiFatalFailure final : public Exception { - public: - explicit BackendRendererApiFatalFailure(const std::string &backendApiName, const std::string &message, - const std::source_location loc = std::source_location::current()) - : Exception("[" + backendApiName + " FATAL ERROR]" + message, loc) {} + public: + explicit BackendRendererApiFatalFailure(const std::string &backendApiName, const std::string &message, + const std::source_location &loc = std::source_location::current()) + : Exception("[" + backendApiName + " FATAL ERROR]" + message, loc) + {} }; + /** + * @brief Exception class for invalid test file formats. + * + * This exception is thrown when a test file has an invalid format. + * It includes the file path and a specific error message to provide context about the failure. + */ class InvalidTestFileFormat final : public Exception { - public: - explicit InvalidTestFileFormat(const std::string &filePath, const std::string &message, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Invalid test file protocol format {}: {}", filePath, message), loc) {} + public: + explicit InvalidTestFileFormat(const std::string &filePath, const std::string &message, + const std::source_location &loc = std::source_location::current()) + : Exception(std::format("Invalid test file protocol format {}: {}", filePath, message), loc) + {} + }; + + /** + * @brief Exception class for empty log file paths. + * + * This exception is thrown when a log file path is found to be empty. + * It provides a default error message indicating the issue. + */ + class LogFilePathEmptyException final : public Exception { + public: + explicit LogFilePathEmptyException(const std::source_location &loc = std::source_location::current()) + : Exception("Log file path is empty.", loc) + {} + }; + + /** + * @brief Exception class for empty resolved log file paths. + * + * This exception is thrown when a resolved log file path is found to be empty. + * It provides a default error message indicating the issue. + */ + class LogResolvedPathEmptyException final : public Exception { + public: + explicit LogResolvedPathEmptyException(const std::source_location &loc = std::source_location::current()) + : Exception("Resolved log file path is empty.", loc) + {} + }; + + /** + * @brief Exception class for asset import errors. + * + * This exception is thrown when an error occurs during the import of an asset. + * It includes the asset path and a specific error message to provide context about the failure. + */ + class AssetImportException final : public Exception { + public: + explicit AssetImportException(const std::string &assetPath, const std::string &message, + const std::source_location &loc = std::source_location::current()) + : Exception(std::format("Exception while importing {}: {}", assetPath, message), loc) + {} + }; + + /** + * @brief Exception class for unsupported entity shape types. + * + * This exception is thrown when an unsupported shape type is used for entity creation. + * It provides a default error message indicating the issue. + */ + class UnsupportedEntityShapeType final : public Exception { + public: + explicit UnsupportedEntityShapeType(const std::source_location &loc = std::source_location::current()) + : Exception("Unsupported shape type for entity creation.", loc) + {} + }; + + /** + * @brief Exception class for invalid physics body IDs. + * + * This exception is thrown when an invalid body ID is encountered during physics body creation. + * It provides a default error message indicating the issue. + */ + class InvalidBodyId final : public Exception { + public: + explicit InvalidBodyId(const ecs::Entity entity, + const std::source_location &loc = std::source_location::current()) + : Exception(std::format("Failed to create physics body for entity {}", entity), loc) + {} + }; + + /** + * @brief Exception class for physics body creation errors. + * + * This exception is thrown when an error occurs during the creation of a physics body for an entity. + * It includes the entity ID and a specific error message to provide context about the failure. + */ + class PhysicBodyCreationException final : public Exception { + public: + explicit PhysicBodyCreationException(const ecs::Entity entity, const std::string &message, + const std::source_location &loc = std::source_location::current()) + : Exception(std::format("Exception during physics body recreation for entity {}: {}", entity, message), loc) + {} + }; + + /** + * @brief Exception class for physics component creation errors. + * + * This exception is thrown when an error occurs during the creation of a physics component for an entity. + * It includes the entity ID and a specific error message to provide context about the failure. + */ + class PhysicComponentCreationException final : public Exception { + public: + explicit PhysicComponentCreationException(const ecs::Entity entity, const std::string &message, + const std::source_location &loc = std::source_location::current()) + : Exception(std::format("Exception during physics component creation for entity {}: {}", entity, message), + loc) + {} }; -} +} // namespace nexo::editor diff --git a/editor/src/inputs/Command.cpp b/editor/src/inputs/Command.cpp index f0aadec56..223202bfb 100644 --- a/editor/src/inputs/Command.cpp +++ b/editor/src/inputs/Command.cpp @@ -13,46 +13,46 @@ /////////////////////////////////////////////////////////////////////////////// #include "Command.hpp" +#include +#include #include #include -#include #include -#include -#include #include namespace nexo::editor { struct StringHash { - using is_transparent = void; // enable heterogeneous lookup - size_t operator()(std::string_view sv) const noexcept { + using is_transparent = void; // enable heterogeneous lookup + size_t operator()(const std::string_view sv) const noexcept + { return std::hash{}(sv); } - size_t operator()(const std::string &s) const noexcept { + size_t operator()(const std::string &s) const noexcept + { return operator()(std::string_view(s)); } }; // 2) Transparent equal struct StringEqual { - using is_transparent = void; // enable heterogeneous lookup - bool operator()(std::string_view a, std::string_view b) const noexcept { + using is_transparent = void; // enable heterogeneous lookup + bool operator()(std::string_view a, std::string_view b) const noexcept + { return a == b; } - bool operator()(const std::string &a, const std::string &b) const noexcept { + bool operator()(const std::string &a, const std::string &b) const noexcept + { return a == b; } }; - Command::Command( - std::string description, - const std::string &key, - const std::function &pressedCallback, - const std::function &releaseCallback, - const std::function &repeatCallback, - bool isModifier, - const std::vector &childrens) - : m_description(std::move(description)), m_key(key), m_pressedCallback(pressedCallback), m_releaseCallback(releaseCallback), m_repeatCallback(repeatCallback), m_isModifier(isModifier), m_childrens(childrens) + Command::Command(std::string description, const std::string &key, const std::function &pressedCallback, + const std::function &releaseCallback, const std::function &repeatCallback, + bool isModifier, const std::vector &children) + : m_description(std::move(description)), m_key(key), m_pressedCallback(pressedCallback), + m_releaseCallback(releaseCallback), m_repeatCallback(repeatCallback), m_isModifier(isModifier), + m_children(children) { // Create a mapping of key names to ImGuiKey values static const std::unordered_map keyMap = { @@ -66,23 +66,58 @@ namespace nexo::editor { {"win", ImGuiKey_LeftSuper}, // Alphanumeric keys - {"a", ImGuiKey_A}, {"b", ImGuiKey_B}, {"c", ImGuiKey_C}, {"d", ImGuiKey_D}, - {"e", ImGuiKey_E}, {"f", ImGuiKey_F}, {"g", ImGuiKey_G}, {"h", ImGuiKey_H}, - {"i", ImGuiKey_I}, {"j", ImGuiKey_J}, {"k", ImGuiKey_K}, {"l", ImGuiKey_L}, - {"m", ImGuiKey_M}, {"n", ImGuiKey_N}, {"o", ImGuiKey_O}, {"p", ImGuiKey_P}, - {"q", ImGuiKey_Q}, {"r", ImGuiKey_R}, {"s", ImGuiKey_S}, {"t", ImGuiKey_T}, - {"u", ImGuiKey_U}, {"v", ImGuiKey_V}, {"w", ImGuiKey_W}, {"x", ImGuiKey_X}, - {"y", ImGuiKey_Y}, {"z", ImGuiKey_Z}, + {"a", ImGuiKey_A}, + {"b", ImGuiKey_B}, + {"c", ImGuiKey_C}, + {"d", ImGuiKey_D}, + {"e", ImGuiKey_E}, + {"f", ImGuiKey_F}, + {"g", ImGuiKey_G}, + {"h", ImGuiKey_H}, + {"i", ImGuiKey_I}, + {"j", ImGuiKey_J}, + {"k", ImGuiKey_K}, + {"l", ImGuiKey_L}, + {"m", ImGuiKey_M}, + {"n", ImGuiKey_N}, + {"o", ImGuiKey_O}, + {"p", ImGuiKey_P}, + {"q", ImGuiKey_Q}, + {"r", ImGuiKey_R}, + {"s", ImGuiKey_S}, + {"t", ImGuiKey_T}, + {"u", ImGuiKey_U}, + {"v", ImGuiKey_V}, + {"w", ImGuiKey_W}, + {"x", ImGuiKey_X}, + {"y", ImGuiKey_Y}, + {"z", ImGuiKey_Z}, // Numbers - {"0", ImGuiKey_0}, {"1", ImGuiKey_1}, {"2", ImGuiKey_2}, {"3", ImGuiKey_3}, - {"4", ImGuiKey_4}, {"5", ImGuiKey_5}, {"6", ImGuiKey_6}, {"7", ImGuiKey_7}, - {"8", ImGuiKey_8}, {"9", ImGuiKey_9}, + {"0", ImGuiKey_0}, + {"1", ImGuiKey_1}, + {"2", ImGuiKey_2}, + {"3", ImGuiKey_3}, + {"4", ImGuiKey_4}, + {"5", ImGuiKey_5}, + {"6", ImGuiKey_6}, + {"7", ImGuiKey_7}, + {"8", ImGuiKey_8}, + {"9", ImGuiKey_9}, // Function keys - {"f1", ImGuiKey_F1}, {"f2", ImGuiKey_F2}, {"f3", ImGuiKey_F3}, {"f4", ImGuiKey_F4}, - {"f5", ImGuiKey_F5}, {"f6", ImGuiKey_F6}, {"f7", ImGuiKey_F7}, {"f8", ImGuiKey_F8}, - {"f9", ImGuiKey_F9}, {"f10", ImGuiKey_F10}, {"f11", ImGuiKey_F11}, {"f12", ImGuiKey_F12}, + {"f1", ImGuiKey_F1}, + {"f2", ImGuiKey_F2}, + {"f3", ImGuiKey_F3}, + {"f4", ImGuiKey_F4}, + {"f5", ImGuiKey_F5}, + {"f6", ImGuiKey_F6}, + {"f7", ImGuiKey_F7}, + {"f8", ImGuiKey_F8}, + {"f9", ImGuiKey_F9}, + {"f10", ImGuiKey_F10}, + {"f11", ImGuiKey_F11}, + {"f12", ImGuiKey_F12}, // Special keys {"space", ImGuiKey_Space}, @@ -122,8 +157,7 @@ namespace nexo::editor { {"keypad+", ImGuiKey_KeypadAdd}, {"keypad-", ImGuiKey_KeypadSubtract}, {"keypad*", ImGuiKey_KeypadMultiply}, - {"keypad/", ImGuiKey_KeypadDivide} - }; + {"keypad/", ImGuiKey_KeypadDivide}}; // Split the key string by '+' (e.g., "Ctrl+Shift+S") std::istringstream keyStream(key); @@ -135,8 +169,7 @@ namespace nexo::editor { segment.erase(segment.find_last_not_of(" \t") + 1); // Convert to lowercase for case-insensitive comparison - std::ranges::transform(segment, segment.begin(), - [](const unsigned char c){ return std::tolower(c); }); + std::ranges::transform(segment, segment.begin(), [](const unsigned char c) { return std::tolower(c); }); // Look up in the map and set the bit in the signature const auto it = keyMap.find(segment); @@ -158,25 +191,22 @@ namespace nexo::editor { void Command::executePressedCallback() const { - if (m_pressedCallback) - m_pressedCallback(); + if (m_pressedCallback) m_pressedCallback(); } void Command::executeReleasedCallback() const { - if (m_releaseCallback) - m_releaseCallback(); + if (m_releaseCallback) m_releaseCallback(); } void Command::executeRepeatCallback() const { - if (m_repeatCallback) - m_repeatCallback(); + if (m_repeatCallback) m_repeatCallback(); } std::span Command::getChildren() const { - return {m_childrens}; + return {m_children}; } const std::bitset &Command::getSignature() const @@ -199,4 +229,4 @@ namespace nexo::editor { return m_description; } -} +} // namespace nexo::editor diff --git a/editor/src/inputs/Command.hpp b/editor/src/inputs/Command.hpp index 06c341988..de5545acd 100644 --- a/editor/src/inputs/Command.hpp +++ b/editor/src/inputs/Command.hpp @@ -13,71 +13,212 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include #include -#include #include +#include #include +#include namespace nexo::editor { class Command { - public: - Command( - std::string description, - const std::string &key, - const std::function &pressedCallback, - const std::function &releaseCallback, - const std::function &repeatCallback, - bool isModifier = false, - const std::vector &childrens = {} - ); - - [[nodiscard]] bool exactMatch(const std::bitset &inputSignature) const; - [[nodiscard]] bool partialMatch(const std::bitset &inputSignature) const; - void executePressedCallback() const; - void executeReleasedCallback() const; - void executeRepeatCallback() const; - [[nodiscard]] std::span getChildren() const; - [[nodiscard]] const std::bitset &getSignature() const; - [[nodiscard]] const std::string &getKey() const; - [[nodiscard]] const std::string &getDescription() const; - [[nodiscard]] bool isModifier() const; - - class Builder { - public: - Builder& description(std::string val) { desc = std::move(val); return *this; } - Builder& key(std::string val) { k = std::move(val); return *this; } - Builder& onPressed(const std::function &cb) { pressed = cb; return *this; } - Builder& onReleased(const std::function &cb) { released = cb; return *this; } - Builder& onRepeat(const std::function &cb) { repeat = cb; return *this; } - Builder& modifier(const bool val) { mod = val; return *this; } - Builder& addChild(Command child) { children.push_back(std::move(child)); return *this; } - - [[nodiscard]] Command build() const { - return {desc, k, pressed, released, repeat, mod, children}; - } - - private: - std::string desc; - std::string k; - std::function pressed = nullptr; - std::function released = nullptr; - std::function repeat = nullptr; - bool mod = false; - std::vector children; - }; - - static Builder create() { return {}; } - - private: - std::bitset m_signature; - std::string m_description; - std::string m_key; - std::function m_pressedCallback; - std::function m_releaseCallback; - std::function m_repeatCallback; - bool m_isModifier; - std::vector m_childrens; + public: + /** @brief Constructs a Command object with the specified parameters. + * @param description A brief description of the command. + * @param key A string representing the key combination for the command (e.g., "Ctrl+S"). + * @param pressedCallback A function to be called when the command is activated (key pressed). + * @param releaseCallback A function to be called when the command is deactivated (key released). + * @param repeatCallback A function to be called when the command is repeated (key held down). + * @param isModifier A boolean indicating if the command is a modifier key (e.g., Ctrl, Shift). + * @param children A vector of child Command objects representing sub-commands or related commands. + */ + Command(std::string description, const std::string &key, const std::function &pressedCallback, + const std::function &releaseCallback, const std::function &repeatCallback, + bool isModifier = false, const std::vector &children = {}); + + /** + * @brief Checks if the input signature exactly matches the command's signature. + * An exact match means that both signatures contain the same keys with no additional keys in either signature. + * @param inputSignature The bitset representing the current input key combination. + * @return True if there is an exact match, false otherwise. + */ + [[nodiscard]] bool exactMatch(const std::bitset &inputSignature) const; + + /** @brief Checks if the input signature partially matches the command's signature. + * A partial match means that all keys in the command's signature are present in the input signature, + * but the input signature may contain additional keys. + * @param inputSignature The bitset representing the current input key combination. + * @return True if there is a partial match, false otherwise. + */ + [[nodiscard]] bool partialMatch(const std::bitset &inputSignature) const; + + /** + * @brief Executes the pressed callback if it is set. + */ + void executePressedCallback() const; + + /** + * @brief Executes the callback associated with the release of the command if it is set. + */ + void executeReleasedCallback() const; + + /** + * @brief Executes the repeat callback if it is set. + */ + void executeRepeatCallback() const; + + /** + * @brief Retrieves a span of child Command objects. + * @return A std::span containing the child Command objects. + */ + [[nodiscard]] std::span getChildren() const; + + /** + * @brief Retrieves the signature bitset representing the command's key combination. + * @return A const reference to the bitset representing the command's signature. + */ + [[nodiscard]] const std::bitset &getSignature() const; + + /** + * @brief Retrieves the key string associated with the command. + * @return A const reference to the key string. + */ + [[nodiscard]] const std::string &getKey() const; + + /** + * @brief Retrieves the description of the command. + * @return A const reference to the command's description string. + */ + [[nodiscard]] const std::string &getDescription() const; + + /** + * @brief Checks if the command is a modifier key. + * @return True if the command is a modifier key, false otherwise. + */ + [[nodiscard]] bool isModifier() const; + + /** + * @class Builder + * @brief A builder class for constructing Command objects with a fluent interface. + * + * This class allows for step-by-step construction of Command objects by setting various properties + * such as description, key, callbacks, modifier status, and child commands. + */ + class Builder { + public: + /** + * @brief Sets the description of the command. + * @param val The description string. + * @return A reference to the Builder instance for method chaining. + */ + Builder &description(std::string val) + { + desc = std::move(val); + return *this; + } + + /** + * @brief Sets the key combination for the command. + * @param val The key combination string (e.g., "Ctrl+S"). + * @return A reference to the Builder instance for method chaining. + */ + Builder &key(std::string val) + { + k = std::move(val); + return *this; + } + + /** + * @brief Sets the callback function to be executed when the command is pressed. + * @param cb The function to be called on key press. + * @return A reference to the Builder instance for method chaining. + */ + Builder &onPressed(const std::function &cb) + { + pressed = cb; + return *this; + } + + /** + * @brief Sets the callback function to be executed when the command is released. + * @param cb The function to be called on key release. + * @return A reference to the Builder instance for method chaining. + */ + Builder &onReleased(const std::function &cb) + { + released = cb; + return *this; + } + + /** + * @brief Sets the callback function to be executed when the command is repeated (key held down). + * @param cb The function to be called on key repeat. + * @return A reference to the Builder instance for method chaining. + */ + Builder &onRepeat(const std::function &cb) + { + repeat = cb; + return *this; + } + + /** + * @brief Sets whether the command is a modifier key. + * @param val True if the command is a modifier key, false otherwise. + * @return A reference to the Builder instance for method chaining. + */ + Builder &modifier(const bool val) + { + mod = val; + return *this; + } + + /** + * @brief Adds a child Command to the current command. + * @param child The child Command object to be added. + * @return A reference to the Builder instance for method chaining. + */ + Builder &addChild(Command child) + { + children.push_back(std::move(child)); + return *this; + } + + /** + * @brief Builds and returns the constructed Command object. + * @return The constructed Command object. + */ + [[nodiscard]] Command build() const + { + return {desc, k, pressed, released, repeat, mod, children}; + } + + private: + std::string desc; + std::string k; + std::function pressed = nullptr; + std::function released = nullptr; + std::function repeat = nullptr; + bool mod = false; + std::vector children; + }; + + /** + * @brief Creates and returns a new Builder instance for constructing Command objects. + * @return A new Builder instance. + */ + static Builder create() + { + return {}; + } + + private: + std::bitset m_signature; + std::string m_description; + std::string m_key; + std::function m_pressedCallback; + std::function m_releaseCallback; + std::function m_repeatCallback; + bool m_isModifier; + std::vector m_children; }; -} +} // namespace nexo::editor diff --git a/editor/src/inputs/InputManager.cpp b/editor/src/inputs/InputManager.cpp index f1f0c6c96..536effee5 100644 --- a/editor/src/inputs/InputManager.cpp +++ b/editor/src/inputs/InputManager.cpp @@ -26,15 +26,9 @@ namespace nexo::editor { std::bitset repeatSignature; std::bitset currentlyHeldKeys; - static const std::set excludedKeys = { - ImGuiKey_MouseLeft, - ImGuiKey_MouseRight, - ImGuiKey_MouseMiddle, - ImGuiKey_MouseX1, - ImGuiKey_MouseX2, - ImGuiKey_MouseWheelX, - ImGuiKey_MouseWheelY - }; + static const std::set excludedKeys = {ImGuiKey_MouseLeft, ImGuiKey_MouseRight, ImGuiKey_MouseMiddle, + ImGuiKey_MouseX1, ImGuiKey_MouseX2, ImGuiKey_MouseWheelX, + ImGuiKey_MouseWheelY}; // Track keys that were held down last frame static std::bitset lastFrameHeldKeys; @@ -47,19 +41,16 @@ namespace nexo::editor { for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_COUNT + ImGuiKey_NamedKey_BEGIN - 5; key++) { constexpr float multiPressThreshold = 0.3f; - if (excludedKeys.contains(static_cast(key))) - continue; + if (excludedKeys.contains(static_cast(key))) continue; const auto imKey = static_cast(key); - const auto idx = static_cast(key - ImGuiKey_NamedKey_BEGIN); - - const bool keyDown = ImGui::IsKeyDown(imKey); + const auto idx = static_cast(key - ImGuiKey_NamedKey_BEGIN); // Update currently held keys - if (keyDown) { + if (ImGui::IsKeyDown(imKey)) { currentlyHeldKeys.set(idx); - if (!lastFrameHeldKeys[idx]) { // Key was just pressed this frame + if (!lastFrameHeldKeys[idx]) { // Key was just pressed this frame pressedSignature.set(idx); // Handle multiple press detection @@ -141,11 +132,10 @@ namespace nexo::editor { lastFrameHeldKeys = currentlyHeldKeys; } - void InputManager::processRepeatCommands( - const std::span& commands, - const std::bitset& repeatSignature, - const std::bitset& currentlyHeldKeys - ) { + void InputManager::processRepeatCommands(const std::span& commands, + const std::bitset& repeatSignature, + const std::bitset& currentlyHeldKeys) + { for (const auto& command : commands) { // If this is a non-modifier command that has a repeat key if (command.exactMatch(repeatSignature)) { @@ -166,7 +156,7 @@ namespace nexo::editor { } // Also check deeper in the hierarchy - const auto &remainingBits = repeatSignature; + const auto& remainingBits = repeatSignature; processRepeatCommands(command.getChildren(), remainingBits, currentlyHeldKeys); } // Standard partial match handling @@ -181,7 +171,7 @@ namespace nexo::editor { } // Add this method implementation - std::vector InputManager::getAllPossibleCommands(const WindowState& windowState) const + std::vector InputManager::getAllPossibleCommands(const WindowState& windowState) { std::vector allCommands; // Use an empty signature to get all commands @@ -190,26 +180,18 @@ namespace nexo::editor { return allCommands; } - std::vector InputManager::getPossibleCommands(const WindowState& windowState) const + std::vector InputManager::getPossibleCommands(const WindowState& windowState) { std::bitset pressedSignature; - static const std::set excludedKeys = { - ImGuiKey_MouseLeft, - ImGuiKey_MouseRight, - ImGuiKey_MouseMiddle, - ImGuiKey_MouseX1, - ImGuiKey_MouseX2, - ImGuiKey_MouseWheelX, - ImGuiKey_MouseWheelY - }; + static const std::set excludedKeys = {ImGuiKey_MouseLeft, ImGuiKey_MouseRight, ImGuiKey_MouseMiddle, + ImGuiKey_MouseX1, ImGuiKey_MouseX2, ImGuiKey_MouseWheelX, + ImGuiKey_MouseWheelY}; // Get currently pressed keys for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_COUNT + ImGuiKey_NamedKey_BEGIN - 5; key++) { - if (excludedKeys.contains(static_cast(key))) - continue; - if (ImGui::IsKeyDown(static_cast(key))) - { + if (excludedKeys.contains(static_cast(key))) continue; + if (ImGui::IsKeyDown(static_cast(key))) { pressedSignature.set(static_cast(key - ImGuiKey_NamedKey_BEGIN)); } } @@ -219,10 +201,9 @@ namespace nexo::editor { return possibleCommands; } - void InputManager::collectPossibleCommands( - const std::span& commands, - const std::bitset& pressedSignature, - std::vector& possibleCommands) const + void InputManager::collectPossibleCommands(const std::span& commands, + const std::bitset& pressedSignature, + std::vector& possibleCommands) { for (const auto& command : commands) { // If no keys are pressed, show all possible command chains @@ -254,7 +235,8 @@ namespace nexo::editor { for (const auto& child : command.getChildren()) { if (hasActivatedChildModifier) { // Skip non-modifier children or modifiers that aren't pressed - if (!child.isModifier() || (child.getSignature() & pressedSignature) != child.getSignature()) { + if (!child.isModifier() || + (child.getSignature() & pressedSignature) != child.getSignature()) { continue; } @@ -267,10 +249,8 @@ namespace nexo::editor { if (child.isModifier() && !child.getChildren().empty()) { // Build combinations for this child modifier for (const auto& grandchild : child.getChildren()) { - possibleCommands.emplace_back( - child.getKey() + "+" + grandchild.getKey(), - grandchild.getDescription() - ); + possibleCommands.emplace_back(child.getKey() + "+" + grandchild.getKey(), + grandchild.getDescription()); } } else { // Child is not a modifier @@ -286,7 +266,7 @@ namespace nexo::editor { // Recursively check child commands if this is not an exact match if (!command.getChildren().empty() && !command.exactMatch(pressedSignature)) { auto remainingBits = pressedSignature ^ command.getSignature(); - if (remainingBits.any()) { // Only recurse if there are remaining bits + if (remainingBits.any()) { // Only recurse if there are remaining bits collectPossibleCommands(command.getChildren(), remainingBits, possibleCommands); } } @@ -295,12 +275,9 @@ namespace nexo::editor { } // Helper method to recursively build all possible command combinations - void InputManager::buildCommandCombinations( - const Command& command, - const std::string& prefix, - std::vector& combinations) const + void InputManager::buildCommandCombinations(const Command& command, const std::string& prefix, + std::vector& combinations) { - std::string currentPrefix = prefix.empty() ? command.getKey() : prefix + "+" + command.getKey(); // If this is a leaf command or not a modifier, add the combination @@ -314,4 +291,4 @@ namespace nexo::editor { buildCommandCombinations(child, currentPrefix, combinations); } } -} +} // namespace nexo::editor diff --git a/editor/src/inputs/InputManager.hpp b/editor/src/inputs/InputManager.hpp index 565fa2216..c4ed8c373 100644 --- a/editor/src/inputs/InputManager.hpp +++ b/editor/src/inputs/InputManager.hpp @@ -13,50 +13,117 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "WindowState.hpp" -#include "Command.hpp" #include #include +#include "Command.hpp" +#include "WindowState.hpp" namespace nexo::editor { + /** + * @Struct CommandInfo + * @brief Simple struct to hold command key and description for display purposes. + */ struct CommandInfo { std::string key; std::string description; - CommandInfo(std::string k, std::string d) : key(std::move(k)), description(std::move(d)) {} + /** + * @brief Constructor to initialize CommandInfo with key and description. + * @param k The key combination string. + * @param d The command description. + */ + CommandInfo(std::string k, std::string d) : key(std::move(k)), description(std::move(d)) + {} }; + /** + * @class InputManager + * @brief Manages input processing and command execution based on key states. + * + * The InputManager class tracks the current and previous states of keys, + * processes input events, and executes associated command callbacks. + * It supports detecting key presses, releases, and repeats, as well as + * handling modifier keys and nested command structures. + */ class InputManager { - public: - InputManager() = default; - ~InputManager() = default; - - // Process inputs based on current window state - void processInputs(const WindowState& windowState); - - // Update these method signatures: - [[nodiscard]] std::vector getPossibleCommands(const WindowState& windowState) const; - [[nodiscard]] std::vector getAllPossibleCommands(const WindowState& windowState) const; - - private: - // Current and previous key states for detecting changes - std::bitset m_currentKeyState; - std::bitset m_keyWasDownLastFrame; - - void processRepeatCommands( - const std::span& commands, - const std::bitset& repeatSignature, - const std::bitset& currentlyHeldKeys - ); - void collectPossibleCommands( - const std::span& commands, - const std::bitset& pressedSignature, - std::vector& possibleCommands) const; - - void buildCommandCombinations( - const Command& command, - const std::string& prefix, - std::vector& combinations) const; + public: + InputManager() = default; + ~InputManager() = default; + + // Process inputs based on current window state + /** + * @brief Processes input events and executes associated command callbacks. + * @param windowState The current state of the window, including available commands. + * + * This method checks the current state of keys, detects changes from the previous frame, + * and executes the appropriate callbacks for commands that match the detected key combinations. + * It handles key presses, releases, and repeats, as well as modifier keys and nested commands. + */ + static void processInputs(const WindowState& windowState); + + // Update these method signatures: + /** + * @brief Retrieves a list of possible commands based on the current key state. + * @param windowState The current state of the window, including available commands. + * @return A vector of CommandInfo objects representing possible commands. + * + * This method analyzes the current key state and matches it against the available commands + * in the provided WindowState. It returns a list of commands that can be activated based on + * the currently pressed keys, including handling modifier keys and nested command structures. + */ + [[nodiscard]] static std::vector getPossibleCommands(const WindowState& windowState); + + /** @brief Retrieves all possible commands regardless of current key state. + * @param windowState The current state of the window, including available commands. + * @return A vector of CommandInfo objects representing all possible commands. + * + * This method returns a comprehensive list of all commands available in the provided + * WindowState, without filtering based on the current key state. It is useful for displaying + * all commands to the user, such as in a help menu or command palette. + */ + [[nodiscard]] static std::vector getAllPossibleCommands(const WindowState& windowState); + + private: + // Current and previous key states for detecting changes + std::bitset m_currentKeyState; + std::bitset m_keyWasDownLastFrame; + + /** + * @brief Bitset tracking keys held down in the last frame. + * + * This bitset is used to determine which keys were held down in the previous frame, + * allowing for the detection of key releases and repeats. + */ + static void processRepeatCommands(const std::span& commands, + const std::bitset& repeatSignature, + const std::bitset& currentlyHeldKeys); + + /** + * @brief Recursively collects possible commands based on the pressed key signature. + * @param commands A span of available Command objects to check against. + * @param pressedSignature A bitset representing the currently pressed keys. + * @param possibleCommands A vector to store the collected CommandInfo objects. + * + * This method traverses the provided commands and checks if they match the current + * pressed key signature. It collects commands that can be activated based on the + * pressed keys, including handling modifier keys and nested command structures. + */ + static void collectPossibleCommands(const std::span& commands, + const std::bitset& pressedSignature, + std::vector& possibleCommands); + + /** + * @brief Recursively builds command combinations for modifier commands. + * @param command The current Command object to process. + * @param prefix The accumulated key prefix for nested commands. + * @param combinations A vector to store the resulting CommandInfo combinations. + * + * This method is used to generate all possible key combinations for commands + * that are modifiers and have child commands. It constructs the full key + * combination strings by appending child command keys to the prefix. + */ + static void buildCommandCombinations(const Command& command, const std::string& prefix, + std::vector& combinations); }; -} +} // namespace nexo::editor diff --git a/editor/src/inputs/WindowState.cpp b/editor/src/inputs/WindowState.cpp index 52334a589..26f931ea2 100644 --- a/editor/src/inputs/WindowState.cpp +++ b/editor/src/inputs/WindowState.cpp @@ -30,4 +30,4 @@ namespace nexo::editor { { return {m_commands.data(), m_commands.size()}; } -} +} // namespace nexo::editor diff --git a/editor/src/inputs/WindowState.hpp b/editor/src/inputs/WindowState.hpp index 8fcec7b15..fd8fbe44a 100644 --- a/editor/src/inputs/WindowState.hpp +++ b/editor/src/inputs/WindowState.hpp @@ -13,23 +13,57 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "Command.hpp" #include +#include "Command.hpp" namespace nexo::editor { + /** + * @class WindowState + * @brief Represents the state of a window, including its unique identifier and associated commands. + * + * The WindowState class encapsulates a unique identifier for the window and manages a collection of + * Command objects that define the input commands available within that window. It provides methods to + * register new commands and retrieve the list of registered commands. + */ class WindowState { - public: - WindowState() = default; - - WindowState(const unsigned int id) : m_id(id) {} - ~WindowState() = default; - - [[nodiscard]] unsigned int getId() const; - void registerCommand(const Command &command); - [[nodiscard]] std::span getCommands() const; - private: - unsigned int m_id{}; - std::vector m_commands; + public: + /** + * @brief Default constructor for the WindowState class. + */ + WindowState() = default; + + /** + * @brief Constructs a WindowState with a unique identifier. + * @param id The unique identifier for the window state. + */ + WindowState(const unsigned int id) : m_id(id) + {} + /** + * @brief Destructor for the WindowState class. + */ + ~WindowState() = default; + + /** + * @brief Retrieves the unique identifier of the window state. + * @return The unique identifier as an unsigned integer. + */ + [[nodiscard]] unsigned int getId() const; + + /** + * @brief Registers a new command to the window state. + * @param command The Command object to be registered. + */ + void registerCommand(const Command &command); + + /** + * @brief Retrieves a span of all registered commands. + * @return A std::span containing all registered Command objects. + */ + [[nodiscard]] std::span getCommands() const; + + private: + unsigned int m_id{}; + std::vector m_commands; }; -} +} // namespace nexo::editor diff --git a/editor/src/utils/Config.cpp b/editor/src/utils/Config.cpp index 470afb3c3..b31455fd4 100644 --- a/editor/src/utils/Config.cpp +++ b/editor/src/utils/Config.cpp @@ -16,16 +16,14 @@ #include "Path.hpp" #include -#include -#include #include +#include +#include namespace nexo::editor { - ImGuiID findWindowDockIDFromConfig(const std::string& windowName) { - std::string configPath = Path::resolvePathRelativeToExe( - "../config/default-layout.ini").string(); + std::string configPath = Path::resolvePathRelativeToExe("../config/default-layout.ini").string(); std::ifstream configFile(configPath); if (!configFile.is_open()) { std::cout << "Could not open config file: " << configPath << std::endl; @@ -34,7 +32,7 @@ namespace nexo::editor { std::string line; bool inWindowSection = false; - ImGuiID dockId = 0; + ImGuiID dockId = 0; std::string windowHeader = "[Window][" + windowName + "]"; @@ -47,8 +45,7 @@ namespace nexo::editor { if (inWindowSection) { // If we hit a new section, stop searching - if (!line.empty() && line[0] == '[') - break; + if (!line.empty() && line[0] == '[') break; std::regex dockIdRegex("DockId=(0x[0-9a-fA-F]+)"); @@ -68,8 +65,7 @@ namespace nexo::editor { std::vector findAllEditorScenes() { - std::string configPath = Path::resolvePathRelativeToExe( - "../config/default-layout.ini").string(); + std::string configPath = Path::resolvePathRelativeToExe("../config/default-layout.ini").string(); std::ifstream configFile(configPath); std::vector sceneWindows; @@ -96,8 +92,7 @@ namespace nexo::editor { void setAllWindowDockIDsFromConfig(WindowRegistry& registry) { - std::string configPath = Path::resolvePathRelativeToExe( - "../config/default-layout.ini").string(); + std::string configPath = Path::resolvePathRelativeToExe("../config/default-layout.ini").string(); std::ifstream configFile(configPath); if (!configFile.is_open()) { @@ -108,7 +103,7 @@ namespace nexo::editor { std::string line; std::string currentWindowName; bool inWindowSection = false; - bool isHashedWindow = false; + bool isHashedWindow = false; std::regex windowHeaderRegex(R"(\[Window\]\[(.+)\])"); std::regex dockIdRegex("DockId=(0x[0-9a-fA-F]+)"); @@ -118,7 +113,7 @@ namespace nexo::editor { std::smatch windowMatch; if (std::regex_search(line, windowMatch, windowHeaderRegex) && windowMatch.size() > 1) { currentWindowName = windowMatch[1].str(); - inWindowSection = true; + inWindowSection = true; // Check if the window name starts with ### isHashedWindow = currentWindowName.starts_with("###"); @@ -131,32 +126,32 @@ namespace nexo::editor { // If we hit a new section, reset state if (!line.empty() && line[0] == '[') { inWindowSection = false; - isHashedWindow = false; + isHashedWindow = false; continue; } std::smatch dockMatch; if (std::regex_search(line, dockMatch, dockIdRegex) && dockMatch.size() > 1) { std::string hexDockId = dockMatch[1]; - ImGuiID dockId = 0; + ImGuiID dockId = 0; std::stringstream ss; ss << std::hex << hexDockId; ss >> dockId; // Set the dock ID for this window in the registry if (dockId != 0) { - std::cout << "Setting dock id " << dockId << " for hashed window " - << currentWindowName << std::endl; + std::cout << "Setting dock id " << dockId << " for hashed window " << currentWindowName + << std::endl; registry.setDockId(currentWindowName, dockId); } } } else if (inWindowSection && !line.empty() && line[0] == '[') { // Reset state when we hit a new section inWindowSection = false; - isHashedWindow = false; + isHashedWindow = false; } } configFile.close(); } -} +} // namespace nexo::editor diff --git a/editor/src/utils/Config.hpp b/editor/src/utils/Config.hpp index fdb77661f..e1a19dcc7 100644 --- a/editor/src/utils/Config.hpp +++ b/editor/src/utils/Config.hpp @@ -15,47 +15,47 @@ #include "WindowRegistry.hpp" -#include #include +#include #include namespace nexo::editor { - /** - * @brief Finds and returns the dock ID of a given window from the ImGui configuration file. - * - * This function reads the configuration file (typically the imgui.ini or a custom layout file) - * and extracts the DockId for the window identified by @p windowName. The dock ID is returned as an - * ImGuiID (unsigned integer). If the window or the dock ID is not found, the function returns 0. - * - * @param windowName The name of the window whose dock ID is to be found. - * @return ImGuiID The dock ID corresponding to the window. Returns 0 if not found. - */ - ImGuiID findWindowDockIDFromConfig(const std::string& windowName); + /** + * @brief Finds and returns the dock ID of a given window from the ImGui configuration file. + * + * This function reads the configuration file (typically the imgui.ini or a custom layout file) + * and extracts the DockId for the window identified by @p windowName. The dock ID is returned as an + * ImGuiID (unsigned integer). If the window or the dock ID is not found, the function returns 0. + * + * @param windowName The name of the window whose dock ID is to be found. + * @return ImGuiID The dock ID corresponding to the window. Returns 0 if not found. + */ + ImGuiID findWindowDockIDFromConfig(const std::string& windowName); - /** - * @brief Finds all editor scene windows defined in the configuration file. - * - * Scans the default layout configuration file and extracts all window names that match - * the pattern for editor scene windows (those with names matching "###Default Scene" followed by digits). - * This allows the editor to reconstruct scene windows from a saved layout configuration. - * - * @return A vector of strings containing the window names of all editor scenes found in the config file. - * Returns an empty vector if no scene windows are found or if the config file cannot be opened. - */ - std::vector findAllEditorScenes(); + /** + * @brief Finds all editor scene windows defined in the configuration file. + * + * Scans the default layout configuration file and extracts all window names that match + * the pattern for editor scene windows (those with names matching "###Default Scene" followed by digits). + * This allows the editor to reconstruct scene windows from a saved layout configuration. + * + * @return A vector of strings containing the window names of all editor scenes found in the config file. + * Returns an empty vector if no scene windows are found or if the config file cannot be opened. + */ + std::vector findAllEditorScenes(); - /** - * @brief Sets dock IDs for all windows in the registry based on the configuration file. - * - * Reads the default layout configuration file and extracts dock IDs for all windows with - * names starting with "###" (hashed windows). For each matching window found in the config file, - * the function updates the corresponding window in the registry with the appropriate dock ID. - * This allows the editor to restore a previously saved docking layout. - * - * The function specifically targets hashed window names (starting with "###") as these are - * the identifier format used for persistent window references in ImGui. - * - * @param registry Reference to the WindowRegistry where dock IDs will be set. - */ - void setAllWindowDockIDsFromConfig(WindowRegistry& registry); -} + /** + * @brief Sets dock IDs for all windows in the registry based on the configuration file. + * + * Reads the default layout configuration file and extracts dock IDs for all windows with + * names starting with "###" (hashed windows). For each matching window found in the config file, + * the function updates the corresponding window in the registry with the appropriate dock ID. + * This allows the editor to restore a previously saved docking layout. + * + * The function specifically targets hashed window names (starting with "###") as these are + * the identifier format used for persistent window references in ImGui. + * + * @param registry Reference to the WindowRegistry where dock IDs will be set. + */ + void setAllWindowDockIDsFromConfig(WindowRegistry& registry); +} // namespace nexo::editor diff --git a/editor/src/utils/EditorProps.cpp b/editor/src/utils/EditorProps.cpp index 9e2c9df59..8f6f2c3e5 100644 --- a/editor/src/utils/EditorProps.cpp +++ b/editor/src/utils/EditorProps.cpp @@ -13,32 +13,39 @@ /////////////////////////////////////////////////////////////////////////////// #include "EditorProps.hpp" +#include "Nexo.hpp" +#include "Path.hpp" #include "Renderer3D.hpp" +#include "assets/AssetCatalog.hpp" #include "components/BillboardMesh.hpp" #include "components/Render3D.hpp" -#include "Path.hpp" -#include "Nexo.hpp" -#include "assets/AssetCatalog.hpp" namespace nexo::editor::utils { + /** + * @brief Adds camera props to the specified entity. + * + * This function attaches a billboard mesh and a material component to the given entity, + * representing a camera icon in the editor. The billboard uses a predefined camera icon texture + * and is set up to always face the camera in the 3D scene. + * + * @param entity The ECS entity to which the camera props will be added. + */ static void addCameraProps(const ecs::Entity entity) { auto& catalog = assets::AssetCatalog::getInstance(); static const assets::AssetRef cameraIconTexture = catalog.createAsset( assets::AssetLocation("_internal::cameraIcon@_internal"), - Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png") - ); + Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png")); static const assets::AssetRef materialRef = [&catalog]() { - auto billboardMat = std::make_unique(); - billboardMat->isOpaque = false; + auto billboardMat = std::make_unique(); + billboardMat->isOpaque = false; billboardMat->albedoTexture = cameraIconTexture; - billboardMat->shader = "Albedo unshaded transparent"; - return catalog.createAsset( - assets::AssetLocation("_internal::CameraPropMat@_internal"), - std::move(billboardMat)); + billboardMat->shader = "Albedo unshaded transparent"; + return catalog.createAsset(assets::AssetLocation("_internal::CameraPropMat@_internal"), + std::move(billboardMat)); }(); components::MaterialComponent matComponent; @@ -51,23 +58,30 @@ namespace nexo::editor::utils { Application::m_coordinator->addComponent(entity, matComponent); } + /** + * @brief Adds point light props to the specified entity. + * + * This function attaches a billboard mesh and a material component to the given entity, + * representing a point light icon in the editor. The billboard uses a predefined point light icon texture + * and is set up to always face the camera in the 3D scene. + * + * @param entity The ECS entity to which the point light props will be added. + */ static void addPointLightProps(const ecs::Entity entity) { auto& catalog = assets::AssetCatalog::getInstance(); static const assets::AssetRef pointLightIconTexture = catalog.createAsset( assets::AssetLocation("_internal::pointLightIcon@_internal"), - Path::resolvePathRelativeToExe("../resources/textures/pointLightIcon.png") - ); + Path::resolvePathRelativeToExe("../resources/textures/pointLightIcon.png")); static const assets::AssetRef materialRef = [&catalog]() { - auto billboardMat = std::make_unique(); - billboardMat->isOpaque = false; + auto billboardMat = std::make_unique(); + billboardMat->isOpaque = false; billboardMat->albedoTexture = pointLightIconTexture; - billboardMat->shader = "Albedo unshaded transparent"; + billboardMat->shader = "Albedo unshaded transparent"; return catalog.createAsset( - assets::AssetLocation("_internal::PointLightPropMat@_internal"), - std::move(billboardMat)); + assets::AssetLocation("_internal::PointLightPropMat@_internal"), std::move(billboardMat)); }(); components::MaterialComponent matComponent; @@ -80,23 +94,30 @@ namespace nexo::editor::utils { Application::m_coordinator->addComponent(entity, matComponent); } + /** + * @brief Adds spotlight props to the specified entity. + * + * This function attaches a billboard mesh and a material component to the given entity, + * representing a spotlight icon in the editor. The billboard uses a predefined spotlight icon texture + * and is set up to always face the camera in the 3D scene. + * + * @param entity The ECS entity to which the spotlight props will be added. + */ static void addSpotLightProps(const ecs::Entity entity) { auto& catalog = assets::AssetCatalog::getInstance(); static const assets::AssetRef spotLightIconTexture = catalog.createAsset( assets::AssetLocation("_internal::spotLightIcon@_internal"), - Path::resolvePathRelativeToExe("../resources/textures/spotLightIcon.png") - ); + Path::resolvePathRelativeToExe("../resources/textures/spotLightIcon.png")); static const assets::AssetRef materialRef = [&catalog]() { - auto billboardMat = std::make_unique(); - billboardMat->isOpaque = false; + auto billboardMat = std::make_unique(); + billboardMat->isOpaque = false; billboardMat->albedoTexture = spotLightIconTexture; - billboardMat->shader = "Albedo unshaded transparent"; - return catalog.createAsset( - assets::AssetLocation("_internal::SpotLightPropMat@_internal"), - std::move(billboardMat)); + billboardMat->shader = "Albedo unshaded transparent"; + return catalog.createAsset(assets::AssetLocation("_internal::SpotLightPropMat@_internal"), + std::move(billboardMat)); }(); components::MaterialComponent matComponent; @@ -111,8 +132,7 @@ namespace nexo::editor::utils { void addPropsTo(const ecs::Entity entity, const PropsType type) { - switch (type) - { + switch (type) { case PropsType::CAMERA: addCameraProps(entity); break; @@ -127,4 +147,4 @@ namespace nexo::editor::utils { } } -} +} // namespace nexo::editor::utils diff --git a/editor/src/utils/EditorProps.hpp b/editor/src/utils/EditorProps.hpp index 1e8edc334..9d77753db 100644 --- a/editor/src/utils/EditorProps.hpp +++ b/editor/src/utils/EditorProps.hpp @@ -17,11 +17,25 @@ namespace nexo::editor::utils { - enum class PropsType { - CAMERA, - POINT_LIGHT, - SPOT_LIGHT - }; + /** + * @enum PropsType + * @brief Enumeration representing different types of editor props that can be added to entities. + * + * This enum defines the types of props that can be associated with entities in the editor. + * Each type corresponds to a specific visual representation or functionality within the editor environment. + */ + enum class PropsType { CAMERA, POINT_LIGHT, SPOT_LIGHT }; + /** + * @brief Adds editor props to a given entity based on the specified PropsType. + * + * This function attaches visual and functional components to the provided entity + * according to the type of props specified. It supports adding camera props, + * point light props, and spotlight props, each represented by distinct textures + * and materials. + * + * @param entity The ECS entity to which the props will be added. + * @param type The type of props to add, defined by the PropsType enum. + */ void addPropsTo(ecs::Entity entity, PropsType type); -} +} // namespace nexo::editor::utils diff --git a/editor/src/utils/FileSystem.cpp b/editor/src/utils/FileSystem.cpp index e84a281b7..8ef9bf14f 100644 --- a/editor/src/utils/FileSystem.cpp +++ b/editor/src/utils/FileSystem.cpp @@ -17,8 +17,9 @@ #include #endif -#include -#include +#include // needed on linux +#include // needed on linux + #include "FileSystem.hpp" #include "Logger.hpp" diff --git a/editor/src/utils/FileSystem.hpp b/editor/src/utils/FileSystem.hpp index 0c81da626..2a4031660 100644 --- a/editor/src/utils/FileSystem.hpp +++ b/editor/src/utils/FileSystem.hpp @@ -39,4 +39,4 @@ namespace nexo::editor::utils { * security implications if folderPath contains untrusted input. */ void openFolder(const std::string &folderPath); -} +} // namespace nexo::editor::utils diff --git a/editor/src/utils/ScenePreview.cpp b/editor/src/utils/ScenePreview.cpp index ca883a5f7..385cb4be5 100644 --- a/editor/src/utils/ScenePreview.cpp +++ b/editor/src/utils/ScenePreview.cpp @@ -12,13 +12,14 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "ScenePreview.hpp" +#include + #include "CameraFactory.hpp" #include "LightFactory.hpp" #include "Nexo.hpp" +#include "ScenePreview.hpp" #include "components/Camera.hpp" #include "components/MaterialComponent.hpp" -#include "components/StaticMesh.hpp" namespace nexo::editor::utils { @@ -42,6 +43,7 @@ namespace nexo::editor::utils { static ecs::Entity copyEntity(const ecs::Entity entity) { + // TODO: Deep copy components instead of shallow copy // const ecs::Entity entityCopy = Application::m_coordinator->createEntity(); // const auto staticMeshCopy = // Application::m_coordinator->getComponent(entity); const auto materialCopy = @@ -59,7 +61,17 @@ namespace nexo::editor::utils { return Application::m_coordinator->duplicateEntity(entity); } - glm::vec3 oldComputeCameraPosition(const ecs::Entity entity) + /** + * @brief Computes a default camera position for an entity based on its transform component. + * + * This function calculates a camera position that provides a good view of the entity + * by using its size to determine an appropriate distance and applying fixed yaw and pitch offsets. + * If the entity does not have a model component or if no vertices are available, this legacy method is used. + * + * @param entity The ECS entity for which to compute the camera position. + * @return glm::vec3 The computed camera position in world space. + */ + glm::vec3 computeLegacyCameraPosition(const ecs::Entity entity) { const auto &transformComponentBase = Application::m_coordinator->getComponent(entity); @@ -101,14 +113,15 @@ namespace nexo::editor::utils { if (!modelComponent) { LOG(NEXO_ERROR, "Entity {} does not have model component, using default camera position computation", entity); - return oldComputeCameraPosition(entity); + return computeLegacyCameraPosition(entity); } - // const auto vertices = modelComponent.model.getVertices(); // TODO: Create get vertices method for the model + // TODO: Create get vertices method for the model + // const auto vertices = modelComponent.model.getVertices(); const std::vector &vertices = {}; if (vertices.empty()) { LOG(NEXO_ERROR, "No vertices available for entity {}, using default camera position computation", entity); - return oldComputeCameraPosition(entity); + return computeLegacyCameraPosition(entity); } // 1) Find AABB min/max @@ -131,7 +144,7 @@ namespace nexo::editor::utils { const glm::vec3 extents{(vMax.x - vMin.x) * 0.5f, (vMax.y - vMin.y) * 0.5f, (vMax.z - vMin.z) * 0.5f}; // 3) Compute half‐angles in radians - constexpr float deg2rad = 3.14159265f / 180.0f; + constexpr float deg2rad = std::numbers::pi_v / 180.0f; const float halfVFovRad = 0.5f * verticalFovDeg * deg2rad; const float halfHFovRad = std::atan(std::tan(halfVFovRad) * aspectRatio); @@ -164,18 +177,18 @@ namespace nexo::editor::utils { const auto framebuffer = renderer::NxFramebuffer::create(framebufferSpecs); const glm::vec3 cameraPos = computeCameraPosition(entity, 45.0f, previewSize.x / previewSize.y, - transformComponentBase.pos - transformComponent.pos); + transformComponentBase.pos - transformComponent.pos); - const ecs::Entity cameraId = CameraFactory::createPerspectiveCamera(cameraPos, framebufferSpecs.width, - framebufferSpecs.height, framebuffer, clearColor); + const ecs::Entity cameraId = CameraFactory::createPerspectiveCamera( + cameraPos, framebufferSpecs.width, framebufferSpecs.height, framebuffer, clearColor); auto &cameraTransform = Application::m_coordinator->getComponent(cameraId); cameraTransform.pos = cameraPos; auto &cameraComponent = Application::m_coordinator->getComponent(cameraId); cameraComponent.render = true; - const glm::vec3 newFront = glm::normalize(transformComponent.pos - cameraPos); - cameraTransform.quat = glm::normalize(glm::quatLookAt(newFront, glm::vec3(0.0f, 1.0f, 0.0f))); + const glm::vec3 newFront = glm::normalize(transformComponent.pos - cameraPos); + cameraTransform.quat = glm::normalize(glm::quatLookAt(newFront, glm::vec3(0.0f, 1.0f, 0.0f))); components::PerspectiveCameraTarget cameraTarget; cameraTarget.targetEntity = entityCopy; diff --git a/editor/src/utils/ScenePreview.hpp b/editor/src/utils/ScenePreview.hpp index 2fd180615..9099f8507 100644 --- a/editor/src/utils/ScenePreview.hpp +++ b/editor/src/utils/ScenePreview.hpp @@ -13,13 +13,13 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include #include "components/Transform.hpp" #include "core/scene/SceneManager.hpp" -#include namespace nexo::editor::utils { - /** + /** * @brief Structure to store output data for a generated scene preview. * * Contains the scene ID, the preview camera entity ID, and a copy of the entity @@ -27,10 +27,10 @@ namespace nexo::editor::utils { * was successfully generated. */ struct ScenePreviewOut { - scene::SceneId sceneId{}; ///< The ID of the generated preview scene. - ecs::Entity cameraId{}; ///< The entity ID of the preview camera. - ecs::Entity entityCopy{}; ///< A copy of the original entity for preview purposes. - bool sceneGenerated = false; ///< Flag indicating whether the scene preview was generated. + scene::SceneId sceneId{}; ///< The ID of the generated preview scene. + ecs::Entity cameraId{}; ///< The entity ID of the preview camera. + ecs::Entity entityCopy{}; ///< A copy of the original entity for preview purposes. + bool sceneGenerated = false; ///< Flag indicating whether the scene preview was generated. }; /** @@ -42,21 +42,22 @@ namespace nexo::editor::utils { * @param objectTransform The transform component of the object. * @return float The computed bounding sphere radius. */ - float computeBoundingSphereRadius(const components::TransformComponent &objectTransform); + float computeBoundingSphereRadius(const components::TransformComponent &objectTransform); - /** - * @brief Computes the half-angle of a spotlight based on an object's transform. - * - * Uses the bounding sphere radius of the object and the distance between the object - * and the light position to compute the half-angle of the spotlight. - * - * @param objectTransform The transform component of the object. - * @param lightPosition The position of the light. - * @return float The computed half-angle in radians. - */ - float computeSpotlightHalfAngle(const components::TransformComponent &objectTransform, const glm::vec3 &lightPosition); + /** + * @brief Computes the half-angle of a spotlight based on an object's transform. + * + * Uses the bounding sphere radius of the object and the distance between the object + * and the light position to compute the half-angle of the spotlight. + * + * @param objectTransform The transform component of the object. + * @param lightPosition The position of the light. + * @return float The computed half-angle in radians. + */ + float computeSpotlightHalfAngle(const components::TransformComponent &objectTransform, + const glm::vec3 &lightPosition); - /** + /** * @brief Generates a scene preview. * * Creates an editor scene with a copy of the given entity, a preview camera, and some default lights. @@ -68,6 +69,7 @@ namespace nexo::editor::utils { * @param out Output structure containing preview scene details. * @param clearColor The clear color of the camera */ - void genScenePreview(const std::string &uniqueSceneName, const glm::vec2 &previewSize, ecs::Entity entity, ScenePreviewOut &out, const glm::vec4 &clearColor = {0.05f, 0.05f, 0.05f, 0.0f}); + void genScenePreview(const std::string &uniqueSceneName, const glm::vec2 &previewSize, ecs::Entity entity, + ScenePreviewOut &out, const glm::vec4 &clearColor = {0.05f, 0.05f, 0.05f, 0.0f}); -} +} // namespace nexo::editor::utils diff --git a/editor/src/utils/String.cpp b/editor/src/utils/String.cpp index e1d80f3e8..ef3dfaa95 100644 --- a/editor/src/utils/String.cpp +++ b/editor/src/utils/String.cpp @@ -19,22 +19,18 @@ #include namespace nexo::editor::utils { - std::string removeIconPrefix(const std::string &str) - { - if (const size_t pos = str.find(' '); pos != std::string::npos) - return str.substr(pos + 1); - return str; - } + std::string removeIconPrefix(const std::string &str) + { + if (const size_t pos = str.find(' '); pos != std::string::npos) return str.substr(pos + 1); + return str; + } - void trim(std::string &s) + void trim(std::string &s) { - auto not_space = [](const char c){ return !std::isspace(static_cast(c)); }; + auto not_space = [](const char c) { return !std::isspace(static_cast(c)); }; s.erase(s.begin(), std::ranges::find_if(s, not_space)); - const auto rit = std::ranges::find_if( - s | std::views::reverse, - not_space - ); - s.erase(rit.base(),s.end()); + const auto rit = std::ranges::find_if(s | std::views::reverse, not_space); + s.erase(rit.base(), s.end()); } -} +} // namespace nexo::editor::utils diff --git a/editor/src/utils/String.hpp b/editor/src/utils/String.hpp index 417f3c918..23df1e6ae 100644 --- a/editor/src/utils/String.hpp +++ b/editor/src/utils/String.hpp @@ -16,17 +16,25 @@ #include namespace nexo::editor::utils { - /** - * @brief Removes the icon prefix from a string. - * - * This function assumes that an icon is prefixed to the string followed by a space. - * It searches for the first space in the string and returns the substring following that space. - * If no space is found, the original string is returned unchanged. - * - * @param str The input string that may contain an icon prefix. - * @return std::string The string with the icon prefix removed. - */ - std::string removeIconPrefix(const std::string &str); + /** + * @brief Removes the icon prefix from a string. + * + * This function assumes that an icon is prefixed to the string followed by a space. + * It searches for the first space in the string and returns the substring following that space. + * If no space is found, the original string is returned unchanged. + * + * @param str The input string that may contain an icon prefix. + * @return std::string The string with the icon prefix removed. + */ + std::string removeIconPrefix(const std::string &str); - void trim(std::string &s); -} + /** + * @brief Trims leading and trailing whitespace from a string in place. + * + * This function modifies the input string by removing any whitespace characters + * (spaces, tabs, newlines, etc.) from the beginning and end of the string. + * + * @param s The string to be trimmed. It will be modified directly. + */ + void trim(std::string &s); +} // namespace nexo::editor::utils diff --git a/editor/src/utils/TransparentStringHash.hpp b/editor/src/utils/TransparentStringHash.hpp index 2cc6d0e6f..6071eb893 100644 --- a/editor/src/utils/TransparentStringHash.hpp +++ b/editor/src/utils/TransparentStringHash.hpp @@ -16,53 +16,62 @@ #include namespace nexo::editor { - // Custom transparent heterogeneous hasher for strings. - struct TransparentStringHash { - // This alias tells the container that our hasher supports heterogeneous lookup. - using is_transparent = void; + /** + * @struct TransparentStringHash + * @brief A custom hash functor that supports heterogeneous lookup for string types. + * + * This struct provides hash functions for std::string, std::string_view, and C-style strings (const char*). + * It enables the use of these different string types as keys in hash-based containers like std::unordered_map + * without requiring conversions to a single type, thus improving performance and usability. + * + * The presence of the `is_transparent` type alias indicates to standard library containers that this hasher + * supports heterogeneous lookup, allowing for more flexible key types. + */ + struct TransparentStringHash { + // This alias tells the container that our hasher supports heterogeneous lookup. + using is_transparent = void; - /** - * @brief Computes the hash value for a given std::string. - * - * This function leverages the standard library's std::hash to generate a hash value - * from the provided std::string, facilitating its use as a key in hash-based containers. - * - * @param s The input string to hash. - * @return size_t The computed hash value. - */ - size_t operator()(const std::string &s) const noexcept - { - return std::hash{}(s); - } + /** + * @brief Computes the hash value for a given std::string. + * + * This function leverages the standard library's std::hash to generate a hash value + * from the provided std::string, facilitating its use as a key in hash-based containers. + * + * @param s The input string to hash. + * @return size_t The computed hash value. + */ + size_t operator()(const std::string& s) const noexcept + { + return std::hash{}(s); + } - /** - * @brief Computes the hash value for a std::string_view. - * - * Uses the standard library's std::hash implementation for std::string_view to compute - * the hash of the provided string view. - * - * @param s The string view whose hash is to be computed. - * @return The computed hash value. - */ - size_t operator()(std::string_view s) const noexcept - { - return std::hash{}(s); - } + /** + * @brief Computes the hash value for a std::string_view. + * + * Uses the standard library's std::hash implementation for std::string_view to compute + * the hash of the provided string view. + * + * @param s The string view whose hash is to be computed. + * @return The computed hash value. + */ + size_t operator()(std::string_view s) const noexcept + { + return std::hash{}(s); + } - /** - * @brief Computes the hash value for a C-style null-terminated string. - * - * This function converts the provided C-string into a std::string_view and then computes its hash - * using the standard library's hash for string views, supporting heterogeneous lookup. - * - * @param s A null-terminated C-string to be hashed. - * @return size_t The computed hash value for the input string. - */ - size_t operator()(const char* s) const noexcept - { - if (!s) - return 0; - return std::hash{}(s); - } - }; -} + /** + * @brief Computes the hash value for a C-style null-terminated string. + * + * This function converts the provided C-string into a std::string_view and then computes its hash + * using the standard library's hash for string views, supporting heterogeneous lookup. + * + * @param s A null-terminated C-string to be hashed. + * @return size_t The computed hash value for the input string. + */ + size_t operator()(const char* s) const noexcept + { + if (!s) return 0; + return std::hash{}(s); + } + }; +} // namespace nexo::editor diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index c1df4ebd7..849d5b8fb 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -13,6 +13,7 @@ set(COMMON_SOURCES common/math/Vector.cpp common/math/Projection.cpp common/Path.cpp + common/math/Bounds.cpp engine/src/Nexo.cpp engine/src/EntityFactory3D.cpp engine/src/LightFactory.cpp @@ -25,6 +26,7 @@ set(COMMON_SOURCES engine/src/core/event/opengl/InputOpenGl.cpp engine/src/core/event/WindowEvent.cpp engine/src/components/Camera.cpp + engine/src/components/Video.cpp engine/src/components/Transform.cpp engine/src/renderer/Buffer.cpp engine/src/renderer/Shader.cpp @@ -47,6 +49,7 @@ set(COMMON_SOURCES engine/src/renderer/primitives/Pyramid.cpp engine/src/renderer/primitives/Cylinder.cpp engine/src/renderer/primitives/Sphere.cpp + engine/src/renderer/primitives/Box.cpp engine/src/core/scene/Scene.cpp engine/src/core/scene/SceneManager.cpp engine/src/ecs/Entity.cpp @@ -57,6 +60,7 @@ set(COMMON_SOURCES engine/src/systems/CameraSystem.cpp engine/src/systems/RenderCommandSystem.cpp engine/src/systems/RenderBillboardSystem.cpp + engine/src/systems/RenderVideoSystem.cpp engine/src/systems/LightSystem.cpp engine/src/systems/ScriptingSystem.cpp engine/src/systems/PhysicsSystem.cpp @@ -66,6 +70,7 @@ set(COMMON_SOURCES engine/src/systems/lights/SpotLightsSystem.cpp engine/src/systems/TransformHierarchySystem.cpp engine/src/systems/TransformMatrixSystem.cpp + engine/src/systems/AABBDebugSystem.cpp engine/src/renderPasses/ForwardPass.cpp engine/src/renderPasses/GridPass.cpp engine/src/renderPasses/MaskPass.cpp @@ -84,7 +89,7 @@ set(COMMON_SOURCES ) # Add API-specific sources -if(NEXO_GRAPHICS_API STREQUAL "OpenGL") +if (NEXO_GRAPHICS_API STREQUAL "OpenGL") list(APPEND COMMON_SOURCES engine/src/renderer/opengl/OpenGlBuffer.cpp engine/src/renderer/opengl/OpenGlWindow.cpp @@ -96,7 +101,7 @@ if(NEXO_GRAPHICS_API STREQUAL "OpenGL") engine/src/renderer/opengl/OpenGlFramebuffer.cpp engine/src/renderer/opengl/OpenGlShaderReflection.cpp ) -endif() +endif () # Create the library add_library(nexoRenderer STATIC ${COMMON_SOURCES}) @@ -104,18 +109,18 @@ add_library(nexoRenderer STATIC ${COMMON_SOURCES}) # Find glm and add its include directories find_package(glm CONFIG REQUIRED) -if(glm_FOUND) +if (glm_FOUND) message(STATUS "GLM found at: ${CMAKE_SOURCE_DIR}/vcpkg/installed/x64-linux/include") target_include_directories(nexoRenderer PUBLIC ${CMAKE_SOURCE_DIR}/vcpkg/installed/x64-linux/include) -else() +else () message(FATAL_ERROR "GLM not found!") -endif() +endif () # Add include directories for this target target_include_directories(nexoRenderer PUBLIC - ${CMAKE_SOURCE_DIR}/engine/include - ${CMAKE_SOURCE_DIR}/engine/src - ${CMAKE_SOURCE_DIR}/common) + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/engine/src + ${CMAKE_SOURCE_DIR}/common) # loguru find_package(loguru CONFIG REQUIRED) @@ -134,10 +139,14 @@ target_link_libraries(nexoRenderer PRIVATE assimp::assimp) find_package(Boost CONFIG REQUIRED COMPONENTS dll) target_link_libraries(nexoRenderer PRIVATE Boost::dll) -# joltphysics +# jolt-physics find_package(Jolt CONFIG REQUIRED) target_link_libraries(nexoRenderer PRIVATE Jolt::Jolt) +# OpenCV +find_package(OpenCV CONFIG REQUIRED) +target_link_libraries(nexoRenderer PRIVATE opencv_core opencv_imgproc opencv_highgui) + ########################################### # Scripting ########################################### @@ -146,13 +155,13 @@ target_link_libraries(nexoRenderer PRIVATE Jolt::Jolt) find_package(unofficial-nethost CONFIG REQUIRED) target_link_libraries(nexoRenderer PRIVATE unofficial::nethost::nethost) -if(NEXO_GRAPHICS_API STREQUAL "OpenGL") +if (NEXO_GRAPHICS_API STREQUAL "OpenGL") target_compile_definitions(nexoRenderer PRIVATE NX_GRAPHICS_API_OPENGL) find_package(OpenGL REQUIRED) find_package(glfw3 3.4 REQUIRED) find_package(glad CONFIG REQUIRED) target_link_libraries(nexoRenderer PRIVATE OpenGL::GL glfw glad::glad) -endif() +endif () target_compile_definitions(nexoRenderer PRIVATE NEXO_EXPORT) set_target_properties(nexoRenderer PROPERTIES ENABLE_EXPORTS ON) diff --git a/engine/include/json.hpp b/engine/include/json.hpp index 7ac836bf2..8323c6fba 100644 --- a/engine/include/json.hpp +++ b/engine/include/json.hpp @@ -31,10 +31,14 @@ namespace nexo { * * @note See nlohmann::json documentation for detailed implementation guidelines. */ - template + template concept JSONSerializable = requires(T obj, json& j) { - { to_json(j, obj) } -> std::same_as; - { from_json(j, obj) } -> std::same_as; + { + to_json(j, obj) + } -> std::same_as; + { + from_json(j, obj) + } -> std::same_as; }; } // namespace nexo diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index 60df50c3f..42a0bc86c 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -16,39 +16,40 @@ #include #include -#include #include "Renderer3D.hpp" +#include "Timestep.hpp" #include "components/BillboardMesh.hpp" #include "components/Camera.hpp" +#include "components/Editor.hpp" #include "components/Light.hpp" +#include "components/MaterialComponent.hpp" #include "components/Model.hpp" #include "components/Name.hpp" #include "components/Parent.hpp" +#include "components/Render.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" #include "components/StaticMesh.hpp" #include "components/Transform.hpp" -#include "components/Editor.hpp" #include "components/Uuid.hpp" -#include "components/Render.hpp" -#include "components/MaterialComponent.hpp" +#include "components/Video.hpp" #include "core/event/Input.hpp" -#include "Timestep.hpp" #include "exceptions/Exceptions.hpp" -#include "renderer/RendererExceptions.hpp" #include "renderer/Renderer.hpp" +#include "renderer/RendererExceptions.hpp" #include "scripting/native/Scripting.hpp" +#include "systems/AABBDebugSystem.hpp" #include "systems/CameraSystem.hpp" #include "systems/RenderBillboardSystem.hpp" #include "systems/RenderCommandSystem.hpp" +#include "systems/ScriptingSystem.hpp" #include "systems/TransformHierarchySystem.hpp" #include "systems/TransformMatrixSystem.hpp" -#include "systems/ScriptingSystem.hpp" #include "systems/lights/DirectionalLightsSystem.hpp" #include "systems/lights/PointLightsSystem.hpp" -std::unique_ptr nexo::Application::_instance = nullptr; +std::unique_ptr nexo::Application::_instance = nullptr; std::shared_ptr nexo::Application::m_coordinator = nullptr; namespace nexo { @@ -72,7 +73,7 @@ namespace nexo { LOG(NEXO_DEV, "Signal listeners registered"); } - void Application::registerEcsComponents() const + void Application::registerEcsComponents() { m_coordinator->registerComponent(); m_coordinator->registerComponent(); @@ -103,10 +104,10 @@ namespace nexo { m_coordinator->registerComponent(); m_coordinator->registerComponent(); m_coordinator->registerComponent(); + m_coordinator->registerComponent(); m_coordinator->registerComponent(); m_coordinator->registerComponent(); m_coordinator->registerSingletonComponent(); - m_coordinator->registerComponent(); } @@ -124,9 +125,8 @@ namespace nexo { m_window->setKeyCallback([this](const int key, const int action, const int mods) { event::EventKey eventKey; eventKey.keycode = key; - eventKey.mods = mods; - switch (action) - { + eventKey.mods = mods; + switch (action) { case GLFW_PRESS: { eventKey.action = event::KeyAction::PRESSED; break; @@ -139,7 +139,8 @@ namespace nexo { eventKey.action = event::KeyAction::REPEAT; break; } - default: return; + default: + return; } m_eventManager->emitEvent(std::make_shared(eventKey)); }); @@ -147,9 +148,8 @@ namespace nexo { m_window->setKeyCallback([this](const int key, const int action, const int mods) { event::EventKey eventKey; eventKey.keycode = key; - eventKey.mods = mods; - switch (action) - { + eventKey.mods = mods; + switch (action) { case GLFW_PRESS: { eventKey.action = event::KeyAction::PRESSED; break; @@ -162,7 +162,8 @@ namespace nexo { eventKey.action = event::KeyAction::REPEAT; break; } - default: return; + default: + return; } m_eventManager->emitEvent(std::make_shared(eventKey)); }); @@ -170,9 +171,8 @@ namespace nexo { m_window->setMouseClickCallback([this](const int button, const int action, const int mods) { event::EventMouseClick event; event.button = static_cast(button); - event.mods = mods; - switch (action) - { + event.mods = mods; + switch (action) { case GLFW_PRESS: { event.action = event::KeyAction::PRESSED; break; @@ -180,7 +180,8 @@ namespace nexo { case GLFW_RELEASE: event.action = event::KeyAction::RELEASED; break; - default: return; + default: + return; } m_eventManager->emitEvent(std::make_shared(event)); }); @@ -195,36 +196,39 @@ namespace nexo { std::make_shared(static_cast(xpos), static_cast(ypos))); }); - m_window->setFileDropCallback([this](const int count, const char** paths) { + m_window->setFileDropCallback([this](const int count, const char **paths) { std::vector files; files.reserve(count); for (int i = 0; i < count; ++i) { files.emplace_back(paths[i]); } - m_eventManager->emitEvent( - std::make_shared(files)); + m_eventManager->emitEvent(std::make_shared(files)); }); } void Application::registerSystems() { m_cameraContextSystem = m_coordinator->registerGroupSystem(); - m_perspectiveCameraControllerSystem = m_coordinator->registerQuerySystem(); + m_perspectiveCameraControllerSystem = + m_coordinator->registerQuerySystem(); m_perspectiveCameraTargetSystem = m_coordinator->registerQuerySystem(); - m_renderCommandSystem = m_coordinator->registerGroupSystem(); - m_renderBillboardSystem = m_coordinator->registerGroupSystem(); - m_transformHierarchySystem = m_coordinator->registerGroupSystem(); - m_transformMatrixSystem = m_coordinator->registerQuerySystem(); - m_physicsSystem = m_coordinator->registerQuerySystem(); + m_renderCommandSystem = m_coordinator->registerGroupSystem(); + m_renderBillboardSystem = m_coordinator->registerGroupSystem(); + m_renderVideoSystem = m_coordinator->registerGroupSystem(); + m_transformHierarchySystem = m_coordinator->registerGroupSystem(); + m_transformMatrixSystem = m_coordinator->registerQuerySystem(); + m_physicsSystem = m_coordinator->registerQuerySystem(); m_physicsSystem->init(); - auto pointLightSystem = m_coordinator->registerGroupSystem(); + auto pointLightSystem = m_coordinator->registerGroupSystem(); auto directionalLightSystem = m_coordinator->registerGroupSystem(); - auto spotLightSystem = m_coordinator->registerGroupSystem(); - auto ambientLightSystem = m_coordinator->registerGroupSystem(); - m_lightSystem = std::make_shared(ambientLightSystem, directionalLightSystem, pointLightSystem, spotLightSystem); + auto spotLightSystem = m_coordinator->registerGroupSystem(); + auto ambientLightSystem = m_coordinator->registerGroupSystem(); + m_lightSystem = std::make_shared(ambientLightSystem, directionalLightSystem, + pointLightSystem, spotLightSystem); m_scriptingSystem = std::make_shared(); + m_aabbdebugSystem = m_coordinator->registerQuerySystem(); } int Application::initScripting() const @@ -239,13 +243,13 @@ namespace nexo { Application::Application() { - m_window = renderer::NxWindow::create(); + m_window = renderer::NxWindow::create(); m_eventManager = std::make_shared(); registerAllDebugListeners(); registerSignalListeners(); // Debug flags - //m_eventDebugFlags |= DEBUG_LOG_KEYBOARD_EVENT; + // m_eventDebugFlags |= DEBUG_LOG_KEYBOARD_EVENT; m_coordinator = std::make_shared(); @@ -256,8 +260,7 @@ namespace nexo { void Application::displayProfileResults() const { - for (auto &result: m_profilesResults) - { + for (auto &result : m_profilesResults) { std::ostringstream stream; stream << std::fixed << std::setprecision(3) << result.time; std::string label = stream.str() + "ms" + " " + result.name; @@ -277,8 +280,7 @@ namespace nexo { m_window->setDarkMode(true); #ifdef NX_GRAPHICS_API_OPENGL - if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) - { + if (!gladLoadGLLoader(reinterpret_cast(glfwGetProcAddress))) { THROW_EXCEPTION(renderer::NxGraphicsApiInitFailure, "Failed to initialize OpenGL context with glad"); } LOG(NEXO_INFO, "OpenGL context initialized with glad"); @@ -298,7 +300,7 @@ namespace nexo { void Application::beginFrame() { - const auto time = glfwGetTime(); + const auto time = glfwGetTime(); m_worldState.time.deltaTime = time - m_worldState.time.totalTime; m_worldState.time.totalTime = time; m_worldState.stats.frameCount += 1; @@ -306,60 +308,61 @@ namespace nexo { void Application::run(const SceneInfo &sceneInfo) { - auto &renderContext = m_coordinator->getSingletonComponent(); + static bool areVideoLoaded = false; + auto &renderContext = m_coordinator->getSingletonComponent(); if (isInPlayMode()) { m_scriptingSystem->update(); } - if (!m_isMinimized) - { - renderContext.sceneRendered = static_cast(sceneInfo.id); - renderContext.sceneType = sceneInfo.sceneType; + if (!m_isMinimized) { + renderContext.sceneRendered = static_cast(sceneInfo.id); + renderContext.sceneType = sceneInfo.sceneType; if (sceneInfo.isChildWindow) { - renderContext.isChildWindow = true; + renderContext.isChildWindow = true; renderContext.viewportBounds[0] = sceneInfo.viewportBounds[0]; renderContext.viewportBounds[1] = sceneInfo.viewportBounds[1]; } - if (m_SceneManager.getScene(sceneInfo.id).isRendered()) - { + if (m_SceneManager.getScene(sceneInfo.id).isRendered()) { m_transformMatrixSystem->update(); m_transformHierarchySystem->update(); - m_cameraContextSystem->update(); - m_lightSystem->update(); - m_renderCommandSystem->update(); - m_renderBillboardSystem->update(); - for (auto &camera : renderContext.cameras) - camera.pipeline.execute(); - // We have to unbind after the whole pipeline since multiple passes can use the same textures - // but we cant bind everything beforehand since a resize can be triggered and invalidate the whole state + m_cameraContextSystem->update(); + m_lightSystem->update(); + m_renderCommandSystem->update(); + m_renderBillboardSystem->update(); + m_aabbdebugSystem->update(); + if (!areVideoLoaded) { + m_renderVideoSystem->update(); + areVideoLoaded = true; + } + for (auto &camera : renderContext.cameras) camera.pipeline.execute(); + // We have to unbind after the whole pipeline since multiple passes can use the same textures + // but we cant bind everything beforehand since a resize can be triggered and invalidate the whole state renderer::NxRenderer3D::get().unbindTextures(); - + if (isInPlayMode()) { m_physicsSystem->update(); + m_renderVideoSystem->update(); } - } - if (m_SceneManager.getScene(sceneInfo.id).isActive()) - { - m_perspectiveCameraControllerSystem->update(m_worldState.time.deltaTime); - } + } + if (m_SceneManager.getScene(sceneInfo.id).isActive()) { + m_perspectiveCameraControllerSystem->update(m_worldState.time.deltaTime); + } } // Update (swap buffers and poll events) - if (sceneInfo.renderingType == RenderingType::WINDOW) - m_window->onUpdate(); + if (sceneInfo.renderingType == RenderingType::WINDOW) m_window->onUpdate(); m_eventManager->dispatchEvents(); renderContext.reset(); - if (m_displayProfileResult) - displayProfileResults(); + if (m_displayProfileResult) displayProfileResults(); } - void Application::endFrame() + void Application::endFrame() const { - m_eventManager->clearEvents(); + m_eventManager->clearEvents(); } - ecs::Entity Application::createEntity() const + ecs::Entity Application::createEntity() { return m_coordinator->createEntity(); } @@ -381,14 +384,13 @@ namespace nexo { m_coordinator->destroyEntity(entity); } - void Application::removeEntityFromParent(const ecs::Entity entity) const + void Application::removeEntityFromParent(const ecs::Entity entity) { // Get the parent component to find the parent entity auto parentComponent = m_coordinator->tryGetComponent(entity); - if (!parentComponent || parentComponent->get().parent == ecs::INVALID_ENTITY) - return; + if (!parentComponent || parentComponent->get().parent == ecs::INVALID_ENTITY) return; - ecs::Entity parentEntity = parentComponent->get().parent; + const ecs::Entity parentEntity = parentComponent->get().parent; // Get the parent's transform component which now stores children auto parentTransform = m_coordinator->tryGetComponent(parentEntity); @@ -402,15 +404,13 @@ namespace nexo { { // Check if this entity has a transform component with children auto transform = m_coordinator->tryGetComponent(entity); - if (!transform || transform->get().children.empty()) - return; + if (!transform || transform->get().children.empty()) return; // Create a copy of the children vector since we'll be modifying it during iteration const std::vector childrenCopy = transform->get().children; // Delete each child entity recursively - for (const auto& childEntity : childrenCopy) - { + for (const auto &childEntity : childrenCopy) { if (childEntity != ecs::INVALID_ENTITY && childEntity != entity) // Avoid circular references deleteEntity(childEntity); } @@ -418,4 +418,4 @@ namespace nexo { // Clear the children list to avoid dangling references transform->get().children.clear(); } -} +} // namespace nexo diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index bcb144175..feb840bf1 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -13,32 +13,35 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include #include #include #include -#include -#include "Types.hpp" -#include "renderer/Window.hpp" -#include "core/event/WindowEvent.hpp" -#include "core/event/SignalEvent.hpp" -#include "ecs/Coordinator.hpp" -#include "core/scene/SceneManager.hpp" #include "Logger.hpp" #include "Timer.hpp" +#include "Types.hpp" #include "WorldState.hpp" -#include "components/Light.hpp" +// #include "components/Light.hpp" #include "components/PhysicsBodyComponent.hpp" +#include "core/event/SignalEvent.hpp" +#include "core/event/WindowEvent.hpp" +#include "core/scene/SceneManager.hpp" +#include "ecs/Coordinator.hpp" +#include "renderer/Window.hpp" #include "systems/CameraSystem.hpp" #include "systems/LightSystem.hpp" -#include "systems/RenderCommandSystem.hpp" +#include "systems/PhysicsSystem.hpp" #include "systems/RenderBillboardSystem.hpp" +#include "systems/RenderCommandSystem.hpp" +#include "systems/RenderVideoSystem.hpp" #include "systems/TransformHierarchySystem.hpp" #include "systems/TransformMatrixSystem.hpp" -#include "systems/PhysicsSystem.hpp" +#include "systems/AABBDebugSystem.hpp" -#define NEXO_PROFILE(name) nexo::Timer timer##__LINE__(name, [&](ProfileResult profileResult) {m_profileResults.push_back(profileResult); }) +#define NEXO_PROFILE(name) \ + nexo::Timer timer##__LINE__(name, [&](ProfileResult profileResult) { m_profileResults.push_back(profileResult); }) namespace nexo { @@ -46,251 +49,504 @@ namespace nexo { class ScriptingSystem; } - enum class GameState { - EDITOR_MODE, - PLAY_MODE - }; + enum class GameState { EDITOR_MODE, PLAY_MODE }; enum EventDebugFlags { - DEBUG_LOG_RESIZE_EVENT = 1 << 0, - DEBUG_LOG_KEYBOARD_EVENT = 1 << 1, - DEBUG_LOG_MOUSE_CLICK_EVENT = 1 << 2, + DEBUG_LOG_RESIZE_EVENT = 1 << 0, + DEBUG_LOG_KEYBOARD_EVENT = 1 << 1, + DEBUG_LOG_MOUSE_CLICK_EVENT = 1 << 2, DEBUG_LOG_MOUSE_SCROLL_EVENT = 1 << 3, - DEBUG_LOG_MOUSE_MOVE_EVENT = 1 << 4 + DEBUG_LOG_MOUSE_MOVE_EVENT = 1 << 4 }; - class Application : LISTENS_TO( - event::EventKey, - event::EventWindowClose, - event::EventWindowResize, - event::EventMouseClick, - event::EventMouseScroll, - event::EventMouseMove, - event::EventAnySignal, - event::EventSignal, - event::EventSignalInterrupt - ) { - public: - ~Application() override = default; - Application(const Application&) = delete; - Application& operator=(const Application&) = delete; - Application(Application&&) = delete; - Application& operator=(Application&&) = delete; - - void init(); - - /** - * @brief Begins a new frame by updating the timestep. - * - * Calculates the time elapsed since the last frame using glfwGetTime() - * and updates the current timestep. Also updates the last frame time. - */ - void beginFrame(); - - struct SceneInfo { - scene::SceneId id; - RenderingType renderingType = RenderingType::WINDOW; - SceneType sceneType = SceneType::GAME; - bool isChildWindow = false; //<< Is the current scene embedded in a sub window ? - glm::vec2 viewportBounds[2]{}; //<< Viewport bounds in absolute coordinates (if the window viewport is embedded in the window), this is used for mouse coordinates - }; - - /** - * @brief Runs the application for the specified scene and rendering type. - * - * This function performs the following steps: - * - Retrieves the RenderContext singleton and sets the current scene to be rendered. - * - If the application window is not minimized: - * - If the scene is marked as rendered, it updates the camera context, light, and render systems. - * - If the scene is active, it updates the perspective camera controller system. - * - Depending on the rendering type, it triggers a window update (swaps buffers and polls events). - * - Dispatches events via the EventManager. - * - Resets the RenderContext for the next frame. - * - If profiling is enabled, displays the profiling results. - * - * @param sceneId The ID of the scene to render. - * @param renderingType The rendering mode (e.g., WINDOW or other types). - * @param sceneType The type of scene to render. - */ - void run(const SceneInfo &sceneInfo); - - /** - * @brief Ends the current frame by clearing processed events. - * - * Clears all the events that have been dispatched during the frame, - * preparing the EventManager for the next frame. - */ - void endFrame(); - - void handleEvent(event::EventKey &event) override - { - if (this->m_eventDebugFlags & DEBUG_LOG_KEYBOARD_EVENT) - std::cout << event << std::endl; - } - - void handleEvent([[maybe_unused]] event::EventWindowClose &event) override - { - m_isRunning = false; - } - - void handleEvent(event::EventWindowResize &event) override - { - if (event.height == 0 || event.width == 0) - m_isMinimized = true; - if (m_isMinimized && event.width != 0 && event.height != 0) - m_isMinimized = false; - if (this->m_eventDebugFlags & DEBUG_LOG_RESIZE_EVENT) - std::cout << event << std::endl; + class Application final + : LISTENS_TO(event::EventKey, event::EventWindowClose, event::EventWindowResize, event::EventMouseClick, + event::EventMouseScroll, event::EventMouseMove, event::EventAnySignal, event::EventSignal, + event::EventSignalInterrupt) { + public: + ~Application() override = default; + Application(const Application &) = delete; + Application &operator=(const Application &) = delete; + Application(Application &&) = delete; + Application &operator=(Application &&) = delete; + + /** + * @brief Initializes the application, setting up the window, event manager, and ECS coordinator. + * + * This function performs the following steps: + * - Initializes the input system with the application window. + * - Registers signal handlers with the event manager. + * - Creates and configures the ECS coordinator. + * - Registers all necessary components and systems with the ECS coordinator. + * - Initializes various systems including rendering, physics, and scripting. + * + * @throws RendererException if there is an error initializing the rendering context. + */ + void init(); + + /** + * @brief Begins a new frame by updating the timestep. + * + * Calculates the time elapsed since the last frame using glfwGetTime() + * and updates the current timestep. Also updates the last frame time. + */ + void beginFrame(); + + struct SceneInfo { + scene::SceneId id; + RenderingType renderingType = RenderingType::WINDOW; + SceneType sceneType = SceneType::GAME; + bool isChildWindow = false; //<< Is the current scene embedded in a sub window ? + glm::vec2 viewportBounds[2]{}; //<< Viewport bounds in absolute coordinates (if the window viewport is + // embedded in the window), this is used for mouse coordinates + }; + + /** + * @brief Runs the application for the specified scene and rendering type. + * + * This function performs the following steps: + * - Retrieves the RenderContext singleton and sets the current scene to be rendered. + * - If the application window is not minimized: + * - If the scene is marked as rendered, it updates the camera context, light, and render systems. + * - If the scene is active, it updates the perspective camera controller system. + * - Depending on the rendering type, it triggers a window update (swaps buffers and polls events). + * - Dispatches events via the EventManager. + * - Resets the RenderContext for the next frame. + * - If profiling is enabled, displays the profiling results. + * + * @param sceneInfo + */ + void run(const SceneInfo &sceneInfo); + + /** + * @brief Ends the current frame by clearing processed events. + * + * Clears all the events that have been dispatched during the frame, + * preparing the EventManager for the next frame. + */ + void endFrame() const; + + /** + * @brief Handles keyboard input events + * @param event The keyboard event to process + * Logs the keyboard event if DEBUG_LOG_KEYBOARD_EVENT flag is set + */ + void handleEvent(event::EventKey &event) override + { + if (this->m_eventDebugFlags & DEBUG_LOG_KEYBOARD_EVENT) std::cout << event << std::endl; + } + + /** + * @brief Handles window close events + * @param event The window close event to process + * Sets the application running state to false, initiating shutdown + */ + void handleEvent([[maybe_unused]] event::EventWindowClose &event) override + { + m_isRunning = false; + } + + /** + * @brief Handles window resize events + * @param event The resize event containing new width and height + * Updates minimized state based on window dimensions and logs if DEBUG_LOG_RESIZE_EVENT is set + */ + void handleEvent(event::EventWindowResize &event) override + { + if (event.height == 0 || event.width == 0) m_isMinimized = true; + if (m_isMinimized && event.width != 0 && event.height != 0) m_isMinimized = false; + if (this->m_eventDebugFlags & DEBUG_LOG_RESIZE_EVENT) std::cout << event << std::endl; + } + + /** + * @brief Handles mouse click events + * @param event The mouse click event to process + * Logs the mouse click if DEBUG_LOG_MOUSE_CLICK_EVENT flag is set + */ + void handleEvent(event::EventMouseClick &event) override + { + if (this->m_eventDebugFlags & DEBUG_LOG_MOUSE_CLICK_EVENT) std::cout << event << std::endl; + } + + /** + * @brief Handles mouse scroll events + * @param event The mouse scroll event to process + * Logs the scroll event if DEBUG_LOG_MOUSE_SCROLL_EVENT flag is set + */ + void handleEvent(event::EventMouseScroll &event) override + { + if (this->m_eventDebugFlags & DEBUG_LOG_MOUSE_SCROLL_EVENT) std::cout << event << std::endl; + } + + /** + * @brief Handles mouse movement events + * @param event The mouse move event to process + * Logs the movement if DEBUG_LOG_MOUSE_MOVE_EVENT flag is set + */ + void handleEvent(event::EventMouseMove &event) override + { + if (this->m_eventDebugFlags & DEBUG_LOG_MOUSE_MOVE_EVENT) std::cout << event << std::endl; + } + + /** + * @brief Handles any signal events + * @param event The signal event to process + * Logs reception of the signal event + */ + void handleEvent(event::EventAnySignal &event) override + { + LOG(NEXO_INFO, "Received signal via {}", event); + } + + /** + * @brief Handles terminate signal events + * @param event The terminate event to process + * Logs reception and sets application running state to false + */ + void handleEvent(event::EventSignal &) override + { + LOG(NEXO_INFO, "Received terminate signal"); + m_isRunning = false; + } + + /** + * @brief Handles interrupt signal events + * @param event The interrupt event to process + * Logs reception and sets application running state to false + */ + void handleEvent(event::EventSignalInterrupt &) override + { + LOG(NEXO_INFO, "Received interrupt signal"); + m_isRunning = false; + } + + /** + * @brief Gets the event manager instance + * @return Shared pointer to the event manager + */ + [[nodiscard]] std::shared_ptr getEventManager() const + { + return m_eventManager; + } + + /** + * @brief Sets debug flags for event logging + * @param flags Bitmask of debug flags to set + */ + void setEventDebugFlags(const int flags) + { + m_eventDebugFlags = flags; + } + + /** + * @brief Removes specified debug flags + * @param flag Debug flag to remove + */ + void removeEventDebugFlags(const int flag) + { + m_eventDebugFlags &= flag; + } + + /** + * @brief Adds a debug flag to the current flags + * @param flag Debug flag to add + */ + void addEventDebugFlag(const int flag) + { + m_eventDebugFlags |= flag; + } + + /** + * @brief Resets all debug flags to 0 + */ + void resetEventDebugFlags() + { + m_eventDebugFlags = 0; + } + + /** + * @brief Checks if application is running + * @return True if application is running, false otherwise + */ + [[nodiscard]] bool isRunning() const + { + return m_isRunning; + } + + /** + * @brief Creates a new entity. + * + * Delegates the creation to the ECS coordinator. + * + * @return ecs::Entity The newly created entity. + */ + static ecs::Entity createEntity(); + + /** + * @brief Gets the camera context system. + * + * @return std::shared_ptr Shared pointer to the camera context system. + */ + [[nodiscard]] std::shared_ptr getCameraContextSystem() const + { + return m_cameraContextSystem; + } + + /** + * @brief Gets the light system. + * + * @return std::shared_ptr Shared pointer to the light system. + */ + [[nodiscard]] std::shared_ptr getPhysicsSystem() const + { + return m_physicsSystem; + } + + /** + * @brief Deletes an existing entity. + * + * If the entity has a SceneTag component, it is first removed from the corresponding scene, + * and then destroyed by the ECS coordinator. + * + * @param entity The entity to delete. + */ + void deleteEntity(ecs::Entity entity); + + /** + * @brief Removes the entity from its parent in the hierarchy + * @param entity The entity to remove from its parent + */ + static void removeEntityFromParent(ecs::Entity entity); + + /** + * @brief Recursively deletes all children of the specified entity + * @param entity The parent entity whose children should be deleted + */ + void deleteEntityChildren(ecs::Entity entity); + + /** + * @brief Gets the singleton instance of the Application class + * @return Reference to the Application instance + * Creates the instance if it doesn't exist yet + */ + static Application &getInstance() + { + if (!_instance) _instance.reset(new Application()); + return *_instance; + } + + /** + * @brief Sets a custom derived Application instance as the singleton + * @tparam DerivedApp Type of the derived Application class + * @tparam Args Parameter pack for constructor arguments + * @param args Constructor arguments for the derived class + */ + template + static void setInstance(Args &&...args) + { + _instance = std::make_unique(std::forward(args)...); + } + + /** + * @brief Gets a component of type T for the specified entity + * @tparam T Type of component to retrieve + * @param entity Entity to get the component from + * @return Reference to the component + */ + template + static T &getEntityComponent(const ecs::Entity entity) + { + return m_coordinator->getComponent(entity); + } + + /** + * @brief Gets all component types attached to the specified entity + * @param entity Entity to get components from + * @return Vector of component types + */ + static std::vector getAllEntityComponentTypes(const ecs::Entity entity) + { + return m_coordinator->getAllComponentTypes(entity); + } + + /** + * @brief Gets the SceneManager instance + * @return Reference to the SceneManager + */ + scene::SceneManager &getSceneManager() + { + return m_SceneManager; + } + + /** + * @brief Gets the window instance + * @return Const reference to the shared window pointer + */ + [[nodiscard]] const std::shared_ptr &getWindow() const + { + return m_window; + } + + /** + * @brief Checks if the application window is open + * @return True if window is open, false otherwise + */ + [[nodiscard]] bool isWindowOpen() const + { + return m_window->isOpen(); + } + + /** + * @brief Gets the current world state + * @return Reference to the WorldState instance + */ + [[nodiscard]] WorldState &getWorldState() + { + return m_worldState; + } + + /** + * @brief Initializes the scripting subsystem + * @return Status code indicating success (0) or failure (non-zero) + */ + [[nodiscard]] int initScripting() const; + + /** + * @brief Shuts down the scripting subsystem + * @return Status code indicating success (0) or failure (non-zero) + */ + [[nodiscard]] int shutdownScripting() const; + + /** + * @brief Gets the current game state + * @return Current GameState enum value + */ + [[nodiscard]] GameState getGameState() const + { + return m_gameState; + } + + /** + * @brief Sets the game state + * @param state New GameState to set + * Resets video system when entering play mode + */ + void setGameState(GameState state) + { + m_gameState = state; + if (state == GameState::PLAY_MODE) { + m_renderVideoSystem->reset(); } - - void handleEvent(event::EventMouseClick &event) override - { - if (this->m_eventDebugFlags & DEBUG_LOG_MOUSE_CLICK_EVENT) - std::cout << event << std::endl; - } - - void handleEvent(event::EventMouseScroll &event) override - { - if (this->m_eventDebugFlags & DEBUG_LOG_MOUSE_SCROLL_EVENT) - std::cout << event << std::endl; - } - - void handleEvent(event::EventMouseMove &event) override - { - if (this->m_eventDebugFlags & DEBUG_LOG_MOUSE_MOVE_EVENT) - std::cout << event << std::endl; - } - - void handleEvent(event::EventAnySignal &event) override - { - LOG(NEXO_INFO, "Received signal via {}", event); - } - - void handleEvent(event::EventSignal&) override - { - LOG(NEXO_INFO, "Received terminate signal"); - m_isRunning = false; - } - - void handleEvent(event::EventSignalInterrupt&) override - { - LOG(NEXO_INFO, "Received interrupt signal"); - m_isRunning = false; - } - - [[nodiscard]] std::shared_ptr getEventManager() const { return m_eventManager; }; - void setEventDebugFlags(const int flags) {m_eventDebugFlags = flags; }; - void removeEventDebugFlags(const int flag) {m_eventDebugFlags &= flag; }; - void addEventDebugFlag(const int flag) {m_eventDebugFlags |= flag; }; - void resetEventDebugFlags() {m_eventDebugFlags = 0; }; - - bool isRunning() const { return m_isRunning; }; - - /** - * @brief Creates a new entity. - * - * Delegates the creation to the ECS coordinator. - * - * @return ecs::Entity The newly created entity. - */ - ecs::Entity createEntity() const; - - std::shared_ptr getPhysicsSystem() const { - return m_physicsSystem; - } - - /** - * @brief Deletes an existing entity. - * - * If the entity has a SceneTag component, it is first removed from the corresponding scene, - * and then destroyed by the ECS coordinator. - * - * @param entity The entity to delete. - */ - void deleteEntity(ecs::Entity entity); - void removeEntityFromParent(const ecs::Entity entity) const; - void deleteEntityChildren(const ecs::Entity entity); - - static Application &getInstance() - { - if (!_instance) - _instance.reset(new Application()); - return *_instance; - } - - template - static void setInstance(Args &&... args) - { - _instance = std::make_unique(std::forward(args)...); - } - - template - static T &getEntityComponent(const ecs::Entity entity) - { - return m_coordinator->getComponent(entity); - } - - static std::vector getAllEntityComponentTypes(const ecs::Entity entity) - { - return m_coordinator->getAllComponentTypes(entity); - } - - scene::SceneManager &getSceneManager() { return m_SceneManager; } - - [[nodiscard]] const std::shared_ptr &getWindow() const { return m_window; } - [[nodiscard]] bool isWindowOpen() const { return m_window->isOpen(); } - [[nodiscard]] WorldState &getWorldState() { return m_worldState; } - - int initScripting() const; - int shutdownScripting() const; - - // Game state management - GameState getGameState() const { return m_gameState; } - void setGameState(GameState state) { m_gameState = state; } - bool isInPlayMode() const { return m_gameState == GameState::PLAY_MODE; } - bool isInEditorMode() const { return m_gameState == GameState::EDITOR_MODE; } - - static std::shared_ptr m_coordinator; - protected: - Application(); - std::shared_ptr m_eventManager; - - private: - void registerAllDebugListeners(); - void registerSignalListeners(); - void registerEcsComponents() const; - void registerWindowCallbacks() const; - void registerSystems(); - void initScripting(); - - void displayProfileResults() const; - static std::unique_ptr _instance; - - scene::SceneId m_nextSceneId = 0; - scene::SceneManager m_SceneManager; - - bool m_isRunning = true; - bool m_isMinimized = false; - bool m_displayProfileResult = true; - std::shared_ptr m_window; - - WorldState m_worldState; - GameState m_gameState = GameState::EDITOR_MODE; - - int m_eventDebugFlags{}; - - std::shared_ptr m_cameraContextSystem; - std::shared_ptr m_lightSystem; - std::shared_ptr m_transformMatrixSystem; - std::shared_ptr m_transformHierarchySystem; - std::shared_ptr m_perspectiveCameraControllerSystem; - std::shared_ptr m_perspectiveCameraTargetSystem; - std::shared_ptr m_scriptingSystem; - std::shared_ptr m_renderCommandSystem; - std::shared_ptr m_renderBillboardSystem; - std::shared_ptr m_physicsSystem; - - std::vector m_profilesResults; - + } + + /** + * @brief Checks if the game is in play mode + * @return True if in play mode, false otherwise + */ + [[nodiscard]] bool isInPlayMode() const + { + return m_gameState == GameState::PLAY_MODE; + } + + /** + * @brief Checks if the game is in editor mode + * @return True if in editor mode, false otherwise + */ + [[nodiscard]] bool isInEditorMode() const + { + return m_gameState == GameState::EDITOR_MODE; + } + static std::shared_ptr m_coordinator; + + protected: + Application(); + std::shared_ptr m_eventManager; + + private: + /** + * @brief Registers all debug listeners for the application. + * + * This function sets up listeners for debugging purposes, allowing the application + * to handle and log various debug events. + */ + void registerAllDebugListeners(); + + /** + * @brief Registers signal listeners for the application. + * + * This function sets up listeners to handle system signals such as termination + * or interruption, ensuring the application can respond appropriately. + */ + void registerSignalListeners(); + + /** + * @brief Registers all ECS components with the coordinator. + * + * This static function initializes and registers all necessary components + * for the Entity Component System (ECS) used in the application. + */ + static void registerEcsComponents(); + + /** + * @brief Registers window-related callbacks. + * + * This function sets up callbacks for window events such as resizing, + * closing, and input handling. + */ + void registerWindowCallbacks() const; + + /** + * @brief Registers all systems used in the application. + * + * This function initializes and registers all systems, such as rendering, + * physics, and scripting, with the ECS coordinator. + */ + void registerSystems(); + + /** + * @brief Initializes the scripting system. + * + * This function sets up the scripting environment, allowing the application + * to execute scripts and interact with the scripting API. + */ + void initScripting(); + + /** + * @brief Displays profiling results. + * + * This function outputs the profiling results collected during the application's + * execution, providing insights into performance metrics. + */ + void displayProfileResults() const; + + static std::unique_ptr _instance; + + scene::SceneId m_nextSceneId = 0; + scene::SceneManager m_SceneManager; + + bool m_isRunning = true; + bool m_isMinimized = false; + bool m_displayProfileResult = true; + std::shared_ptr m_window; + + WorldState m_worldState; + GameState m_gameState = GameState::EDITOR_MODE; + + int m_eventDebugFlags{}; + + std::shared_ptr m_cameraContextSystem; + std::shared_ptr m_lightSystem; + std::shared_ptr m_transformMatrixSystem; + std::shared_ptr m_transformHierarchySystem; + std::shared_ptr m_perspectiveCameraControllerSystem; + std::shared_ptr m_perspectiveCameraTargetSystem; + std::shared_ptr m_scriptingSystem; + std::shared_ptr m_renderCommandSystem; + std::shared_ptr m_renderBillboardSystem; + std::shared_ptr m_renderVideoSystem; + std::shared_ptr m_physicsSystem; + std::shared_ptr m_aabbdebugSystem; + + std::vector m_profilesResults; }; -} +} // namespace nexo diff --git a/engine/src/CameraFactory.cpp b/engine/src/CameraFactory.cpp index da52e6ef6..7d3fbc11b 100644 --- a/engine/src/CameraFactory.cpp +++ b/engine/src/CameraFactory.cpp @@ -16,42 +16,43 @@ #include #include "Application.hpp" -#include "components/Transform.hpp" #include "components/Camera.hpp" +#include "components/Transform.hpp" #include "components/Uuid.hpp" #include "renderPasses/ForwardPass.hpp" namespace nexo { - ecs::Entity CameraFactory::createPerspectiveCamera(glm::vec3 pos, unsigned int width, - unsigned int height, std::shared_ptr renderTarget, - const glm::vec4 &clearColor, float fov, float nearPlane, float farPlane) - { - components::TransformComponent transform{}; - transform.pos = pos; + ecs::Entity CameraFactory::createPerspectiveCamera(glm::vec3 pos, unsigned int width, unsigned int height, + std::shared_ptr renderTarget, + const glm::vec4 &clearColor, float fov, float nearPlane, + float farPlane) + { + components::TransformComponent transform{}; + transform.pos = pos; - components::CameraComponent camera{}; - camera.width = width; - camera.height = height; - camera.fov = fov; - camera.nearPlane = nearPlane; - camera.farPlane = farPlane; - camera.type = components::CameraType::PERSPECTIVE; + components::CameraComponent camera{}; + camera.width = width; + camera.height = height; + camera.fov = fov; + camera.nearPlane = nearPlane; + camera.farPlane = farPlane; + camera.type = components::CameraType::PERSPECTIVE; - auto forwardPass = std::make_shared(); - renderer::PassId forwardPassId = camera.pipeline.addRenderPass(forwardPass); - camera.pipeline.setFinalOutputPass(forwardPassId); - camera.pipeline.setCameraClearColor(clearColor); - if (renderTarget) { - camera.m_renderTarget = std::move(renderTarget); - camera.pipeline.setRenderTarget(camera.m_renderTarget); - } - camera.clearColor = clearColor; + auto forwardPass = std::make_shared(); + renderer::PassId forwardPassId = camera.pipeline.addRenderPass(forwardPass); + camera.pipeline.setFinalOutputPass(forwardPassId); + camera.pipeline.setCameraClearColor(clearColor); + if (renderTarget) { + camera.m_renderTarget = std::move(renderTarget); + camera.pipeline.setRenderTarget(camera.m_renderTarget); + } + camera.clearColor = clearColor; - ecs::Entity newCamera = Application::m_coordinator->createEntity(); - Application::m_coordinator->addComponent(newCamera, transform); - Application::m_coordinator->addComponent(newCamera, camera); - components::UuidComponent uuid; + ecs::Entity newCamera = Application::m_coordinator->createEntity(); + Application::m_coordinator->addComponent(newCamera, transform); + Application::m_coordinator->addComponent(newCamera, camera); + components::UuidComponent uuid; Application::m_coordinator->addComponent(newCamera, uuid); - return newCamera; - } -} + return newCamera; + } +} // namespace nexo diff --git a/engine/src/CameraFactory.hpp b/engine/src/CameraFactory.hpp index 6be2a1998..d8c873777 100644 --- a/engine/src/CameraFactory.hpp +++ b/engine/src/CameraFactory.hpp @@ -19,10 +19,24 @@ #include namespace nexo { - class CameraFactory { - public: - static ecs::Entity createPerspectiveCamera(glm::vec3 pos, unsigned int width, - unsigned int height, std::shared_ptr renderTarget = nullptr, - const glm::vec4 &clearColor = {37.0f/255.0f, 35.0f/255.0f, 50.0f/255.0f, 111.0f/255.0f}, float fov = 45.0f, float nearPlane = 1.0f, float farPlane = 100.0f); - }; -} + class CameraFactory { + public: + /** + * @brief Creates a perspective camera entity with specified parameters. + * @param pos The position of the camera in 3D space. + * @param width The width of the camera viewport. + * @param height The height of the camera viewport. + * @param renderTarget Optional framebuffer to render the camera's view to. + * @param clearColor The color to clear the camera's view with. + * @param fov The field of view angle in degrees. + * @param nearPlane The near clipping plane distance. + * @param farPlane The far clipping plane distance. + * @return The created camera entity. + */ + static ecs::Entity createPerspectiveCamera(glm::vec3 pos, unsigned int width, unsigned int height, + std::shared_ptr renderTarget = nullptr, + const glm::vec4 &clearColor = {37.0f / 255.0f, 35.0f / 255.0f, + 50.0f / 255.0f, 111.0f / 255.0f}, + float fov = 45.0f, float nearPlane = 1.0f, float farPlane = 100.0f); + }; +} // namespace nexo diff --git a/engine/src/EntityFactory3D.cpp b/engine/src/EntityFactory3D.cpp index 045e02749..6d41fc987 100644 --- a/engine/src/EntityFactory3D.cpp +++ b/engine/src/EntityFactory3D.cpp @@ -13,51 +13,49 @@ /////////////////////////////////////////////////////////////////////////////// #include "EntityFactory3D.hpp" +#include "Application.hpp" #include "Definitions.hpp" #include "Renderer3D.hpp" +#include "assets/AssetCatalog.hpp" #include "assets/AssetLocation.hpp" #include "components/BillboardMesh.hpp" #include "components/Light.hpp" +#include "components/MaterialComponent.hpp" #include "components/Name.hpp" #include "components/Parent.hpp" #include "components/Render.hpp" #include "components/Render3D.hpp" +#include "components/StaticMesh.hpp" #include "components/Transform.hpp" #include "components/Uuid.hpp" -#include "components/StaticMesh.hpp" -#include "components/MaterialComponent.hpp" -#include "assets/AssetCatalog.hpp" -#include "Application.hpp" #include "math/Matrix.hpp" #define GLM_ENABLE_EXPERIMENTAL -#include #include +#include -namespace nexo -{ +namespace nexo { ecs::Entity EntityFactory3D::createCube(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, glm::vec4 color) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); components::StaticMeshComponent mesh; mesh.vao = renderer::NxRenderer3D::getCubeVAO(); - auto material = std::make_unique(); - material->albedoColor = color; + auto material = std::make_unique(); + material->albedoColor = color; const auto materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::CubeMatFlatColor@_internal"), - std::move(material)); + assets::AssetLocation("_internal::CubeMatFlatColor@_internal"), std::move(material)); components::MaterialComponent matComponent; matComponent.material = materialRef; components::UuidComponent uuid; components::RenderComponent renderComponent; renderComponent.isRendered = true; - renderComponent.type = components::PrimitiveType::CUBE; + renderComponent.type = components::PrimitiveType::CUBE; const ecs::Entity newCube = Application::m_coordinator->createEntity(); Application::m_coordinator->addComponent(newCube, transform); @@ -74,7 +72,7 @@ namespace nexo const components::Material& material) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); @@ -82,15 +80,14 @@ namespace nexo mesh.vao = renderer::NxRenderer3D::getCubeVAO(); const auto materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::CubeMat@_internal"), - std::make_unique(material)); + assets::AssetLocation("_internal::CubeMat@_internal"), std::make_unique(material)); components::MaterialComponent matComponent; matComponent.material = materialRef; components::UuidComponent uuid; components::RenderComponent renderComponent; renderComponent.isRendered = true; - renderComponent.type = components::PrimitiveType::CUBE; + renderComponent.type = components::PrimitiveType::CUBE; const ecs::Entity newCube = Application::m_coordinator->createEntity(); Application::m_coordinator->addComponent(newCube, transform); @@ -105,14 +102,13 @@ namespace nexo ecs::Entity EntityFactory3D::createBillboard(const glm::vec3& pos, const glm::vec3& size, const glm::vec4& color) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; - auto material = std::make_unique(); - material->albedoColor = color; + auto material = std::make_unique(); + material->albedoColor = color; const auto materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::BillboardMatFlatColor@_internal"), - std::move(material)); + assets::AssetLocation("_internal::BillboardMatFlatColor@_internal"), std::move(material)); components::MaterialComponent matComponent; matComponent.material = materialRef; @@ -122,7 +118,7 @@ namespace nexo components::UuidComponent uuid; components::RenderComponent renderComponent; renderComponent.isRendered = true; - renderComponent.type = components::PrimitiveType::BILLBOARD; + renderComponent.type = components::PrimitiveType::BILLBOARD; const ecs::Entity newBillboard = Application::m_coordinator->createEntity(); Application::m_coordinator->addComponent(newBillboard, transform); @@ -138,7 +134,7 @@ namespace nexo const components::Material& material) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; components::BillboardComponent mesh; @@ -153,7 +149,7 @@ namespace nexo components::UuidComponent uuid; components::RenderComponent renderComponent; renderComponent.isRendered = true; - renderComponent.type = components::PrimitiveType::BILLBOARD; + renderComponent.type = components::PrimitiveType::BILLBOARD; const ecs::Entity newBillboard = Application::m_coordinator->createEntity(); Application::m_coordinator->addComponent(newBillboard, transform); @@ -168,18 +164,17 @@ namespace nexo ecs::Entity EntityFactory3D::createTetrahedron(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, glm::vec4 color) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); components::StaticMeshComponent mesh; mesh.vao = renderer::NxRenderer3D::getTetrahedronVAO(); - auto material = std::make_unique(); - material->albedoColor = color; + auto material = std::make_unique(); + material->albedoColor = color; const auto materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::TetrahedronMatFlatColor@_internal"), - std::move(material)); + assets::AssetLocation("_internal::TetrahedronMatFlatColor@_internal"), std::move(material)); components::MaterialComponent matComponent; matComponent.material = materialRef; @@ -199,7 +194,7 @@ namespace nexo const components::Material& material) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); @@ -226,18 +221,17 @@ namespace nexo ecs::Entity EntityFactory3D::createPyramid(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, glm::vec4 color) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); components::StaticMeshComponent mesh; mesh.vao = renderer::NxRenderer3D::getPyramidVAO(); - auto material = std::make_unique(); - material->albedoColor = color; + auto material = std::make_unique(); + material->albedoColor = color; const auto materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::PyramidMatFlatColor@_internal"), - std::move(material)); + assets::AssetLocation("_internal::PyramidMatFlatColor@_internal"), std::move(material)); components::MaterialComponent matComponent; matComponent.material = materialRef; @@ -256,7 +250,7 @@ namespace nexo const components::Material& material) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); @@ -264,8 +258,7 @@ namespace nexo mesh.vao = renderer::NxRenderer3D::getPyramidVAO(); const auto materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::PyramidMat@_internal"), - std::make_unique(material)); + assets::AssetLocation("_internal::PyramidMat@_internal"), std::make_unique(material)); components::MaterialComponent matComponent; matComponent.material = materialRef; @@ -280,22 +273,21 @@ namespace nexo return newPyramid; } - ecs::Entity EntityFactory3D::createCylinder(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, - glm::vec4 color, unsigned int nbSegment) + ecs::Entity EntityFactory3D::createCylinder(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, glm::vec4 color, + unsigned int nbSegment) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); components::StaticMeshComponent mesh; mesh.vao = renderer::NxRenderer3D::getCylinderVAO(nbSegment); - auto material = std::make_unique(); - material->albedoColor = color; + auto material = std::make_unique(); + material->albedoColor = color; const auto materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::CylinderMatFlatColor@_internal"), - std::move(material)); + assets::AssetLocation("_internal::CylinderMatFlatColor@_internal"), std::move(material)); components::MaterialComponent matComponent; matComponent.material = materialRef; @@ -314,7 +306,7 @@ namespace nexo const components::Material& material, unsigned int nbSegment) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); @@ -338,22 +330,21 @@ namespace nexo return newCylinder; } - ecs::Entity EntityFactory3D::createSphere(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, - glm::vec4 color, const unsigned int nbSubdivision) + ecs::Entity EntityFactory3D::createSphere(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, glm::vec4 color, + const unsigned int nbSubdivision) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); components::StaticMeshComponent mesh; mesh.vao = renderer::NxRenderer3D::getSphereVAO(nbSubdivision); - auto material = std::make_unique(); - material->albedoColor = color; + auto material = std::make_unique(); + material->albedoColor = color; const auto materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::SphereMatFlatColor@_internal"), - std::move(material)); + assets::AssetLocation("_internal::SphereMatFlatColor@_internal"), std::move(material)); components::MaterialComponent matComponent; matComponent.material = materialRef; @@ -372,7 +363,7 @@ namespace nexo const components::Material& material, const unsigned int nbSubdivision) { components::TransformComponent transform{}; - transform.pos = pos; + transform.pos = pos; transform.size = size; transform.quat = glm::quat(glm::radians(rotation)); @@ -380,8 +371,7 @@ namespace nexo mesh.vao = renderer::NxRenderer3D::getSphereVAO(nbSubdivision); const auto materialRef = assets::AssetCatalog::getInstance().createAsset( - assets::AssetLocation("_internal::SphereMat@_internal"), - std::make_unique(material)); + assets::AssetLocation("_internal::SphereMat@_internal"), std::make_unique(material)); components::MaterialComponent matComponent; matComponent.material = materialRef; @@ -396,17 +386,16 @@ namespace nexo return newSphere; } - ecs::Entity EntityFactory3D::createModel(assets::AssetRef model, glm::vec3 pos, glm::vec3 size, + ecs::Entity EntityFactory3D::createModel(const assets::AssetRef& model, glm::vec3 pos, glm::vec3 size, glm::vec3 rotation) { auto modelAsset = model.lock(); - if (!modelAsset || !modelAsset->getData()) - return ecs::INVALID_ENTITY; + if (!modelAsset || !modelAsset->getData()) return ecs::INVALID_ENTITY; ecs::Entity rootEntity = Application::m_coordinator->createEntity(); components::TransformComponent rootTransform; - rootTransform.pos = pos; + rootTransform.pos = pos; rootTransform.size = size; rootTransform.quat = glm::quat(glm::radians(rotation)); @@ -423,10 +412,10 @@ namespace nexo // Process model nodes to create entity hierarchy const assets::MeshNode& rootNode = *modelAsset->getData(); - int childCount = processModelNode(rootEntity, rootNode); + int childCount = processModelNode(rootEntity, rootNode); // Update child count in root component - auto &storedRoot = Application::m_coordinator->getComponent(rootEntity); + auto& storedRoot = Application::m_coordinator->getComponent(rootEntity); storedRoot.childCount = childCount; components::UuidComponent uuid; Application::m_coordinator->addComponent(rootEntity, uuid); @@ -450,30 +439,26 @@ namespace nexo nexo::math::decomposeTransformQuat(node.transform, translation, rotation, scale); components::TransformComponent transform; - transform.pos = translation; + transform.pos = translation; transform.size = scale; transform.quat = rotation; Application::m_coordinator->addComponent(nodeEntity, transform); - components::ParentComponent parentComponent; + components::ParentComponent parentComponent{}; parentComponent.parent = parentEntity; Application::m_coordinator->addComponent(nodeEntity, parentComponent); - auto parentTransform = Application::m_coordinator->tryGetComponent< - components::TransformComponent>(parentEntity); - if (parentTransform) - parentTransform->get().children.push_back(nodeEntity); - + auto parentTransform = + Application::m_coordinator->tryGetComponent(parentEntity); + if (parentTransform) parentTransform->get().children.push_back(nodeEntity); - if (!node.name.empty()) - { + if (!node.name.empty()) { components::NameComponent nameComponent; nameComponent.name = node.name; Application::m_coordinator->addComponent(nodeEntity, nameComponent); } - for (const auto& mesh : node.meshes) - { + for (const auto& mesh : node.meshes) { ecs::Entity meshEntity = Application::m_coordinator->createEntity(); totalChildrenCreated++; @@ -481,7 +466,7 @@ namespace nexo Application::m_coordinator->addComponent(meshEntity, meshUuid); components::TransformComponent meshTransform; - meshTransform.pos = glm::vec3(0.0f); + meshTransform.pos = glm::vec3(0.0f); meshTransform.size = glm::vec3(1.0f); meshTransform.quat = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); // Centroid @@ -492,39 +477,35 @@ namespace nexo components::RenderComponent renderComponent; renderComponent.isRendered = true; - renderComponent.type = components::PrimitiveType::MESH; + renderComponent.type = components::PrimitiveType::MESH; Application::m_coordinator->addComponent(meshEntity, meshTransform); Application::m_coordinator->addComponent(meshEntity, staticMesh); Application::m_coordinator->addComponent(meshEntity, renderComponent); - if (!mesh.name.empty()) - { + if (!mesh.name.empty()) { components::NameComponent nameComponent; nameComponent.name = mesh.name; Application::m_coordinator->addComponent(meshEntity, nameComponent); } - if (mesh.material) - { + if (mesh.material) { components::MaterialComponent materialComponent; materialComponent.material = mesh.material; Application::m_coordinator->addComponent(meshEntity, materialComponent); } - components::ParentComponent meshParentComponent; + components::ParentComponent meshParentComponent{}; meshParentComponent.parent = nodeEntity; Application::m_coordinator->addComponent(meshEntity, meshParentComponent); - auto nodeTransform = Application::m_coordinator->tryGetComponent< - components::TransformComponent>(nodeEntity); - if (nodeTransform) - nodeTransform->get().children.push_back(meshEntity); + auto nodeTransform = + Application::m_coordinator->tryGetComponent(nodeEntity); + if (nodeTransform) nodeTransform->get().children.push_back(meshEntity); } - for (const auto& childNode : node.children) - totalChildrenCreated += processModelNode(nodeEntity, childNode); + for (const auto& childNode : node.children) totalChildrenCreated += processModelNode(nodeEntity, childNode); return totalChildrenCreated; } -} +} // namespace nexo diff --git a/engine/src/EntityFactory3D.hpp b/engine/src/EntityFactory3D.hpp index 30d9f56a6..1a9685bba 100644 --- a/engine/src/EntityFactory3D.hpp +++ b/engine/src/EntityFactory3D.hpp @@ -19,17 +19,15 @@ #include "components/Components.hpp" #include "components/Model.hpp" -namespace nexo -{ - - enum Primitives - { - CUBE, - TETRAHEDRON, - PYRAMID, - CYLINDER, - SPHERE, - }; +namespace nexo { + + enum Primitives { + CUBE, + TETRAHEDRON, + PYRAMID, + CYLINDER, + SPHERE, + }; /** * @brief Factory class for creating 3D entities. @@ -37,9 +35,8 @@ namespace nexo * Provides static methods to create simple 3D entities such as cubes or models, * by setting up the required components (TransformComponent, RenderComponent, UuidComponent, etc.). */ - class EntityFactory3D - { - public: + class EntityFactory3D { + public: /** * @brief Creates a cube entity with a specified color. * @@ -68,11 +65,52 @@ namespace nexo static ecs::Entity createCube(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, const components::Material& material); - static ecs::Entity createModel(assets::AssetRef modelAsset, glm::vec3 pos, glm::vec3 size, glm::vec3 rotation); - static int processModelNode(ecs::Entity parentEntity, const assets::MeshNode& node); + /** + * @brief Creates a model entity from a given model asset. + * Constructs a model entity at the specified position, with the given size and rotation. + * @param model The asset reference to the model to be used. + * @param pos The position of the model in 3D space. + * @param size The dimensions (width, height, depth) of the model. + * @param rotation The rotation of the model (in Euler angles). + * @return ecs::Entity The newly created model entity. + */ + static ecs::Entity createModel(const assets::AssetRef& model, glm::vec3 pos, glm::vec3 size, + glm::vec3 rotation); + /** + * @brief Recursively processes a model node and its children to create entities. + * + * This function creates an entity for the given model node, sets up its transform and parent components, + * and recursively processes its child nodes. It also creates entities for each mesh associated with the node. + * + * @param parentEntity The parent entity to which the new entity will be attached. + * @param node The model node to process. + * @return int The total number of child entities created (including meshes and child nodes). + */ + static int processModelNode(ecs::Entity parentEntity, const assets::MeshNode& node); + + /** + * @brief Creates a billboard entity with a specified color. + * + * Constructs a billboard at the given position, with the specified size and color. + * + * @param pos The position of the billboard. + * @param size The dimensions (width, height) of the billboard. + * @param color The color of the billboard's material. + * @return ecs::Entity The newly created billboard entity. + */ static ecs::Entity createBillboard(const glm::vec3& pos, const glm::vec3& size, const glm::vec4& color); + /** + * @brief Creates a billboard entity with a specified material. + * + * Constructs a billboard at the given position, with the specified size and material. + * + * @param pos The position of the billboard. + * @param size The dimensions (width, height) of the billboard. + * @param material The material to apply to the billboard. + * @return ecs::Entity The newly created billboard entity. + */ static ecs::Entity createBillboard(const glm::vec3& pos, const glm::vec3& size, const components::Material& material); @@ -91,20 +129,19 @@ namespace nexo glm::vec4 color = {1.0f, 0.0f, 0.0f, 1.0f}); /** - * @brief Creates a tetrahedron entity with a specified material. - * - * Constructs a tetrahedron at the given position, with the specified size, rotation, and material. - * - * @param pos The position of the tetrahedron. - * @param size The dimensions (width, height, depth) of the tetrahedron. - * @param rotation The rotation of the tetrahedron (in Euler angles). - * @param material The material to apply to the tetrahedron. - * @return ecs::Entity The newly created tetrahedron entity. - */ + * @brief Creates a tetrahedron entity with a specified material. + * + * Constructs a tetrahedron at the given position, with the specified size, rotation, and material. + * + * @param pos The position of the tetrahedron. + * @param size The dimensions (width, height, depth) of the tetrahedron. + * @param rotation The rotation of the tetrahedron (in Euler angles). + * @param material The material to apply to the tetrahedron. + * @return ecs::Entity The newly created tetrahedron entity. + */ static ecs::Entity createTetrahedron(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, const components::Material& material); - /** * @brief Creates a pyramid entity with a specified material. * @@ -146,8 +183,7 @@ namespace nexo * @return ecs::Entity The newly created cylinder entity. */ static ecs::Entity createCylinder(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, - glm::vec4 color = {1.0f, 0.0f, 0.0f, 1.0f}, - unsigned int nbSegment = 12); + glm::vec4 color = {1.0f, 0.0f, 0.0f, 1.0f}, unsigned int nbSegment = 12); /** * @brief Creates a cylinder entity with a specified material. @@ -162,8 +198,7 @@ namespace nexo * @return ecs::Entity The newly created cylinder entity. */ static ecs::Entity createCylinder(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, - const components::Material& material, - unsigned int nbSegment = 12); + const components::Material& material, unsigned int nbSegment = 12); /** * @brief Creates a sphere entity with a specified material. @@ -178,8 +213,7 @@ namespace nexo * @return ecs::Entity The newly created sphere entity. */ static ecs::Entity createSphere(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, - glm::vec4 color = {1.0f, 0.0f, 0.0f, 1.0f}, - unsigned int nbSubdivision = 2); + glm::vec4 color = {1.0f, 0.0f, 0.0f, 1.0f}, unsigned int nbSubdivision = 2); /** * @brief Creates a sphere entity with a specified material. @@ -194,7 +228,6 @@ namespace nexo * @return ecs::Entity The newly created sphere entity. */ static ecs::Entity createSphere(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, - const components::Material& material, - unsigned int nbSubdivision = 2); + const components::Material& material, unsigned int nbSubdivision = 2); }; -} +} // namespace nexo diff --git a/engine/src/LightFactory.cpp b/engine/src/LightFactory.cpp index a256c6587..0114352d7 100644 --- a/engine/src/LightFactory.cpp +++ b/engine/src/LightFactory.cpp @@ -20,50 +20,52 @@ #include "components/Uuid.hpp" namespace nexo { - ecs::Entity LightFactory::createAmbientLight(const glm::vec3 color) - { - const ecs::Entity newAmbientLight = Application::m_coordinator->createEntity(); - const components::AmbientLightComponent newAmbientLightComponent{color}; - Application::m_coordinator->addComponent(newAmbientLight, newAmbientLightComponent); - const components::UuidComponent uuid; + ecs::Entity LightFactory::createAmbientLight(const glm::vec3 color) + { + const ecs::Entity newAmbientLight = Application::m_coordinator->createEntity(); + const components::AmbientLightComponent newAmbientLightComponent{color}; + Application::m_coordinator->addComponent(newAmbientLight, + newAmbientLightComponent); + const components::UuidComponent uuid; Application::m_coordinator->addComponent(newAmbientLight, uuid); - return newAmbientLight; - } + return newAmbientLight; + } - ecs::Entity LightFactory::createDirectionalLight(const glm::vec3 lightDir, const glm::vec3 color) - { - const ecs::Entity newDirectionalLight = Application::m_coordinator->createEntity(); - const components::DirectionalLightComponent newDirectionalLightComponent(lightDir, color); - Application::m_coordinator->addComponent(newDirectionalLight, newDirectionalLightComponent); - const components::UuidComponent uuid; + ecs::Entity LightFactory::createDirectionalLight(const glm::vec3 lightDir, const glm::vec3 color) + { + const ecs::Entity newDirectionalLight = Application::m_coordinator->createEntity(); + const components::DirectionalLightComponent newDirectionalLightComponent(lightDir, color); + Application::m_coordinator->addComponent(newDirectionalLight, + newDirectionalLightComponent); + const components::UuidComponent uuid; Application::m_coordinator->addComponent(newDirectionalLight, uuid); - return newDirectionalLight; - } + return newDirectionalLight; + } - ecs::Entity LightFactory::createPointLight(const glm::vec3 position, const glm::vec3 color, const float linear, const float quadratic) - { - const ecs::Entity newPointLight = Application::m_coordinator->createEntity(); - const components::TransformComponent transformComponent{position}; - Application::m_coordinator->addComponent(newPointLight, transformComponent); - const components::PointLightComponent newPointLightComponent{color, linear, quadratic}; - Application::m_coordinator->addComponent(newPointLight, newPointLightComponent); - const components::UuidComponent uuid; + ecs::Entity LightFactory::createPointLight(const glm::vec3 position, const glm::vec3 color, const float linear, + const float quadratic) + { + const ecs::Entity newPointLight = Application::m_coordinator->createEntity(); + const components::TransformComponent transformComponent{position}; + Application::m_coordinator->addComponent(newPointLight, transformComponent); + const components::PointLightComponent newPointLightComponent{color, linear, quadratic}; + Application::m_coordinator->addComponent(newPointLight, + newPointLightComponent); + const components::UuidComponent uuid; Application::m_coordinator->addComponent(newPointLight, uuid); - return newPointLight; - } + return newPointLight; + } - ecs::Entity LightFactory::createSpotLight(glm::vec3 position, glm::vec3 direction, - glm::vec3 color, float linear, - float quadratic, float cutOff, - float outerCutOff) - { - ecs::Entity newSpotLight = Application::m_coordinator->createEntity(); - components::TransformComponent transformComponent{position}; - Application::m_coordinator->addComponent(newSpotLight, transformComponent); - components::SpotLightComponent newSpotLightComponent{direction, color, cutOff, outerCutOff, linear, quadratic}; - Application::m_coordinator->addComponent(newSpotLight, newSpotLightComponent); - components::UuidComponent uuid; + ecs::Entity LightFactory::createSpotLight(glm::vec3 position, glm::vec3 direction, glm::vec3 color, float linear, + float quadratic, float cutOff, float outerCutOff) + { + ecs::Entity newSpotLight = Application::m_coordinator->createEntity(); + components::TransformComponent transformComponent{position}; + Application::m_coordinator->addComponent(newSpotLight, transformComponent); + components::SpotLightComponent newSpotLightComponent{direction, color, cutOff, outerCutOff, linear, quadratic}; + Application::m_coordinator->addComponent(newSpotLight, newSpotLightComponent); + components::UuidComponent uuid; Application::m_coordinator->addComponent(newSpotLight, uuid); - return newSpotLight; - } -} + return newSpotLight; + } +} // namespace nexo diff --git a/engine/src/LightFactory.hpp b/engine/src/LightFactory.hpp index 709069770..6af52f1b1 100644 --- a/engine/src/LightFactory.hpp +++ b/engine/src/LightFactory.hpp @@ -19,84 +19,88 @@ namespace nexo { - /** - * @brief A static factory class for creating light entities. - * - * The LightFactory provides methods to create different types of lights (ambient, directional, - * point, and spot lights) by setting up the necessary components (such as the corresponding - * LightComponent and UuidComponent) for each light entity. - * - * @note Each created light entity will have a components::UuidComponent attached automatically. - */ - class LightFactory { - public: - /** - * @brief Creates an ambient light entity. - * - * This function creates a new entity and attaches an AmbientLightComponent with the given color. - * - * @param color The RGB color of the ambient light. - * @return ecs::Entity The newly created ambient light entity. - * - * @note Required Components added: - * - components::AmbientLightComponent - * - components::UuidComponent - */ - static ecs::Entity createAmbientLight(glm::vec3 color); + /** + * @brief A static factory class for creating light entities. + * + * The LightFactory provides methods to create different types of lights (ambient, directional, + * point, and spotlights) by setting up the necessary components (such as the corresponding + * LightComponent and UuidComponent) for each light entity. + * + * @note Each created light entity will have a components::UuidComponent attached automatically. + */ + class LightFactory { + public: + /** + * @brief Creates an ambient light entity. + * + * This function creates a new entity and attaches an AmbientLightComponent with the given color. + * + * @param color The RGB color of the ambient light. + * @return ecs::Entity The newly created ambient light entity. + * + * @note Required Components added: + * - components::AmbientLightComponent + * - components::UuidComponent + */ + static ecs::Entity createAmbientLight(glm::vec3 color); - /** - * @brief Creates a directional light entity. - * - * This function creates a new entity and attaches a DirectionalLightComponent configured with - * the specified light direction and color. - * - * @param lightDir The direction vector of the directional light. - * @param color The RGB color of the directional light (default is white). - * @return ecs::Entity The newly created directional light entity. - * - * @note Required Components added: - * - components::DirectionalLightComponent - * - components::UuidComponent - */ - static ecs::Entity createDirectionalLight(glm::vec3 lightDir, glm::vec3 color = {1.0f, 1.0f, 1.0f}); + /** + * @brief Creates a directional light entity. + * + * This function creates a new entity and attaches a DirectionalLightComponent configured with + * the specified light direction and color. + * + * @param lightDir The direction vector of the directional light. + * @param color The RGB color of the directional light (default is white). + * @return ecs::Entity The newly created directional light entity. + * + * @note Required Components added: + * - components::DirectionalLightComponent + * - components::UuidComponent + */ + static ecs::Entity createDirectionalLight(glm::vec3 lightDir, glm::vec3 color = {1.0f, 1.0f, 1.0f}); - /** - * @brief Creates a point light entity. - * - * This function creates a new entity and attaches a PointLightComponent configured with - * the specified position, color, and attenuation parameters. - * - * @param position The position of the point light. - * @param color The RGB color of the point light (default is white). - * @param linear The linear attenuation factor (default is 0.09f). - * @param quadratic The quadratic attenuation factor (default is 0.032f). - * @return ecs::Entity The newly created point light entity. - * - * @note Required Components added: - * - components::PointLightComponent - * - components::UuidComponent - */ - static ecs::Entity createPointLight(glm::vec3 position, glm::vec3 color = {1.0f, 1.0f, 1.0f}, float linear = 0.09f, float quadratic = 0.032f); + /** + * @brief Creates a point light entity. + * + * This function creates a new entity and attaches a PointLightComponent configured with + * the specified position, color, and attenuation parameters. + * + * @param position The position of the point light. + * @param color The RGB color of the point light (default is white). + * @param linear The linear attenuation factor (default is 0.09f). + * @param quadratic The quadratic attenuation factor (default is 0.032f). + * @return ecs::Entity The newly created point light entity. + * + * @note Required Components added: + * - components::PointLightComponent + * - components::UuidComponent + */ + static ecs::Entity createPointLight(glm::vec3 position, glm::vec3 color = {1.0f, 1.0f, 1.0f}, + float linear = 0.09f, float quadratic = 0.032f); - /** - * @brief Creates a spot light entity. - * - * This function creates a new entity and attaches a SpotLightComponent configured with - * the specified position, direction, color, and cutoff parameters. - * - * @param position The position of the spot light. - * @param direction The direction vector of the spot light. - * @param color The RGB color of the spot light (default is white). - * @param linear The linear attenuation factor (default is 0.09f). - * @param quadratic The quadratic attenuation factor (default is 0.032f). - * @param cutOff The cosine of the inner cutoff angle (default is cos(12.5°)). - * @param outerCutOff The cosine of the outer cutoff angle (default is cos(15.0°)). - * @return ecs::Entity The newly created spot light entity. - * - * @note Required Components added: - * - components::SpotLightComponent - * - components::UuidComponent - */ - static ecs::Entity createSpotLight(glm::vec3 position, glm::vec3 direction, glm::vec3 color = {1.0f, 1.0f, 1.0f}, float linear = 0.09f, float quadratic = 0.032f, float cutOff = glm::cos(glm::radians(12.5f)), float outerCutOff = glm::cos(glm::radians(15.0f))); - }; -} + /** + * @brief Creates a spotlight entity. + * + * This function creates a new entity and attaches a SpotLightComponent configured with + * the specified position, direction, color, and cutoff parameters. + * + * @param position The position of the spotlight. + * @param direction The direction vector of the spotlight. + * @param color The RGB color of the spotlight (default is white). + * @param linear The linear attenuation factor (default is 0.09f). + * @param quadratic The quadratic attenuation factor (default is 0.032f). + * @param cutOff The cosine of the inner cutoff angle (default is cos(12.5°)). + * @param outerCutOff The cosine of the outer cutoff angle (default is cos(15.0°)). + * @return ecs::Entity The newly created spotlight entity. + * + * @note Required Components added: + * - components::SpotLightComponent + * - components::UuidComponent + */ + static ecs::Entity createSpotLight(glm::vec3 position, glm::vec3 direction, + glm::vec3 color = {1.0f, 1.0f, 1.0f}, float linear = 0.09f, + float quadratic = 0.032f, float cutOff = glm::cos(glm::radians(12.5f)), + float outerCutOff = glm::cos(glm::radians(15.0f))); + }; +} // namespace nexo diff --git a/engine/src/Nexo.cpp b/engine/src/Nexo.cpp index ef18a5b6c..b697c189c 100644 --- a/engine/src/Nexo.cpp +++ b/engine/src/Nexo.cpp @@ -11,10 +11,10 @@ // Description: Main source file for nexo // /////////////////////////////////////////////////////////////////////////////// + #include "Nexo.hpp" namespace nexo { - Application &init() { Application &app = Application::getInstance(); @@ -34,4 +34,4 @@ namespace nexo { app.run(sceneInfo); } -} +} // namespace nexo diff --git a/engine/src/Nexo.hpp b/engine/src/Nexo.hpp index efdb18a6b..3bb51325a 100644 --- a/engine/src/Nexo.hpp +++ b/engine/src/Nexo.hpp @@ -14,35 +14,69 @@ #pragma once #include "Application.hpp" -#include "core/event/KeyCodes.hpp" -#include "Timestep.hpp" -#include "Timer.hpp" -#include "Logger.hpp" - -// Renderer -#include "renderer/Renderer.hpp" -#include "renderer/RenderCommand.hpp" - -#include "renderer/Buffer.hpp" -#include "renderer/Framebuffer.hpp" -#include "renderer/Shader.hpp" -#include "renderer/Texture.hpp" -#include "renderer/VertexArray.hpp" - -#include "components/Components.hpp" +// #include "Logger.hpp" +// #include "Timer.hpp" +// #include "Timestep.hpp" +// #include "core/event/KeyCodes.hpp" +// +// // Renderer +// #include "renderer/RenderCommand.hpp" +// #include "renderer/Renderer.hpp" +// +// #include "renderer/Buffer.hpp" +// #include "renderer/Framebuffer.hpp" +// #include "renderer/Shader.hpp" +// #include "renderer/Texture.hpp" +// #include "renderer/VertexArray.hpp" +// +// #include "components/Components.hpp" namespace nexo { - - - template - void useApp(Args &&... args) + /** + * @brief Sets the application instance to a derived class of Application. + * + * This function allows you to specify a custom application class that inherits from + * the base Application class. It forwards any constructor arguments to the derived + * class's constructor and sets it as the singleton instance of the Application. + * + * @tparam DerivedApp The type of the derived application class. + * @tparam Args The types of the constructor arguments for the derived class. + * @param args The constructor arguments to be forwarded to the derived class. + */ + template + void useApp(Args &&...args) { Application::setInstance(std::forward(args)...); } + /** + * @brief Initializes the Nexo engine and returns the main application instance. + * + * This function initializes the singleton instance of the Application class, + * setting up necessary components and systems for the engine to function. + * It logs a message indicating successful initialization. + * + * @return Reference to the initialized Application instance. + */ Application &init(); + /** + * @brief Retrieves the singleton instance of the Application class. + * + * This function provides access to the main application instance, allowing + * other parts of the code to interact with the application state and functionality. + * + * @return Reference to the singleton Application instance. + */ Application &getApp(); + /** + * @brief Runs the main application loop with the specified scene. + * + * This function retrieves the singleton instance of the Application class + * and invokes its run method, passing in the provided SceneInfo object. + * + * @param sceneInfo Information about the scene to be rendered and updated. + */ void runEngine(const Application::SceneInfo &sceneInfo); -} +} // namespace nexo diff --git a/engine/src/Types.hpp b/engine/src/Types.hpp index c0e41fccd..90dc03597 100644 --- a/engine/src/Types.hpp +++ b/engine/src/Types.hpp @@ -14,13 +14,7 @@ #pragma once namespace nexo { - enum class RenderingType { - WINDOW, - FRAMEBUFFER - }; + enum class RenderingType { WINDOW, FRAMEBUFFER }; - enum class SceneType { - EDITOR, - GAME - }; -} + enum class SceneType { EDITOR, GAME }; +} // namespace nexo diff --git a/engine/src/WorldState.hpp b/engine/src/WorldState.hpp index a2a5f4b4d..1ba579b85 100644 --- a/engine/src/WorldState.hpp +++ b/engine/src/WorldState.hpp @@ -15,7 +15,7 @@ #pragma once -#include "Application.hpp" +// #include "Application.hpp" namespace nexo { @@ -30,4 +30,4 @@ namespace nexo { } stats; }; -} // namespace nexo::scripting \ No newline at end of file +} // namespace nexo diff --git a/engine/src/assets/Asset.cpp b/engine/src/assets/Asset.cpp index 29e78e9d8..014697994 100644 --- a/engine/src/assets/Asset.cpp +++ b/engine/src/assets/Asset.cpp @@ -14,6 +14,4 @@ #include "Asset.hpp" -namespace nexo::assets { - -} // namespace nexo::assets +namespace nexo::assets {} // namespace nexo::assets diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index 377b4d8f5..2be9684f2 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -14,12 +14,11 @@ #pragma once -#include -#include -#include -#include #include #include +#include +#include +#include #include "AssetLocation.hpp" #include "AssetRef.hpp" @@ -36,39 +35,17 @@ namespace nexo::assets { * @note The order of the enum is important, as it is used to index into the AssetTypeNames array. * Make sure to update the array if you add new asset types. */ - enum class AssetType { - UNKNOWN, - TEXTURE, - MATERIAL, - MODEL, - SOUND, - MUSIC, - FONT, - SHADER, - SCRIPT, - _COUNT - }; + enum class AssetType { UNKNOWN, TEXTURE, MATERIAL, MODEL, SOUND, MUSIC, FONT, SHADER, SCRIPT, _COUNT }; /** * @brief Array of asset type names * @note The order of the array must match the order of the AssetType enum. */ - constexpr const char *AssetTypeNames[] = { - "UNKNOWN", - "TEXTURE", - "MATERIAL", - "MODEL", - "SOUND", - "MUSIC", - "FONT", - "SHADER", - "SCRIPT" - }; + constexpr const char* AssetTypeNames[] = {"UNKNOWN", "TEXTURE", "MATERIAL", "MODEL", "SOUND", + "MUSIC", "FONT", "SHADER", "SCRIPT"}; - static_assert( - static_cast(AssetType::_COUNT) == std::size(AssetTypeNames), - "AssetTypeNames array size must match AssetType enum size" - ); + static_assert(static_cast(AssetType::_COUNT) == std::size(AssetTypeNames), + "AssetTypeNames array size must match AssetType enum size"); /** * @brief Retrieves the name corresponding to the specified asset type. @@ -78,7 +55,8 @@ namespace nexo::assets { * @param type The asset type value. * @return const char* The name of the asset type. */ - constexpr const char *getAssetTypeName(AssetType type) { + constexpr const char* getAssetTypeName(AssetType type) + { return AssetTypeNames[static_cast(type)]; } @@ -91,7 +69,8 @@ namespace nexo::assets { * @param j JSON object to receive the serialized asset type. * @param type The AssetType enum value to convert. */ - inline void to_json(nlohmann::json& j, AssetType type) { + inline void to_json(nlohmann::json& j, AssetType type) + { j = getAssetTypeName(type); } @@ -105,7 +84,8 @@ namespace nexo::assets { * @param j JSON object containing the asset type as a string. * @param type Output parameter to store the resulting AssetType. */ - inline void from_json(const nlohmann::json& j, AssetType& type) { + inline void from_json(const nlohmann::json& j, AssetType& type) + { for (int i = 0; i < static_cast(AssetType::_COUNT); ++i) { if (j == AssetTypeNames[i]) { type = static_cast(i); @@ -121,21 +101,17 @@ namespace nexo::assets { */ using AssetID = boost::uuids::uuid; - enum class AssetStatus { - UNLOADED, - LOADED, - ERROR - }; + enum class AssetStatus { UNLOADED, LOADED, ERROR }; class AssetCatalog; class AssetImporter; struct AssetMetadata { - AssetType type; //< Asset type - AssetStatus status; //< Asset status - uint64_t referenceCount; //< Number of references to the asset - AssetID id; //< Unique identifier - AssetLocation location; //< Location of the asset + AssetType type; //< Asset type + AssetStatus status; //< Asset status + uint64_t referenceCount; //< Number of references to the asset + AssetID id; //< Unique identifier + AssetLocation location; //< Location of the asset }; /** @@ -144,36 +120,39 @@ namespace nexo::assets { class IAsset { friend class AssetCatalog; friend class AssetImporter; - public: - virtual ~IAsset() = default; - - [[nodiscard]] virtual const AssetMetadata& getMetadata() const = 0; - [[nodiscard]] virtual AssetType getType() const = 0; - [[nodiscard]] virtual AssetID getID() const = 0; - [[nodiscard]] virtual AssetStatus getStatus() const = 0; - - [[nodiscard]] virtual bool isLoaded() const = 0; - [[nodiscard]] virtual bool isErrored() const = 0; - - protected: - explicit IAsset() - : m_metadata({ - .type = AssetType::UNKNOWN, - .status = AssetStatus::UNLOADED, - .referenceCount = 0, - .id = boost::uuids::nil_uuid(), - .location = AssetLocation("default"), - }) - { - } - - public: - AssetMetadata m_metadata; - /** - * @brief Get the metadata of the asset (for modification) - */ - [[nodiscard]] AssetMetadata& getMetadata() { return m_metadata; } + public: + virtual ~IAsset() = default; + + [[nodiscard]] virtual const AssetMetadata& getMetadata() const = 0; + [[nodiscard]] virtual AssetType getType() const = 0; + [[nodiscard]] virtual AssetID getID() const = 0; + [[nodiscard]] virtual AssetStatus getStatus() const = 0; + + [[nodiscard]] virtual bool isLoaded() const = 0; + [[nodiscard]] virtual bool isErrored() const = 0; + + protected: + explicit IAsset() + : m_metadata({ + .type = AssetType::UNKNOWN, + .status = AssetStatus::UNLOADED, + .referenceCount = 0, + .id = boost::uuids::nil_uuid(), + .location = AssetLocation("default"), + }) + {} + + public: + AssetMetadata m_metadata; + + /** + * @brief Get the metadata of the asset (for modification) + */ + [[nodiscard]] AssetMetadata& getMetadata() + { + return m_metadata; + } }; template @@ -181,58 +160,78 @@ namespace nexo::assets { friend class AssetCatalog; friend class AssetRef; - public: - // SFINAE definitions - using AssetDataType = TAssetData; - static constexpr AssetType TYPE = TAssetType; + public: + // SFINAE definitions + using AssetDataType = TAssetData; + static constexpr AssetType TYPE = TAssetType; - ~Asset() override = default; + ~Asset() override = default; - [[nodiscard]] const AssetMetadata& getMetadata() const override { return m_metadata; } - [[nodiscard]] AssetType getType() const override { return getMetadata().type; } - [[nodiscard]] AssetID getID() const override { return getMetadata().id; } - [[nodiscard]] AssetStatus getStatus() const override { return getMetadata().status; } + [[nodiscard]] const AssetMetadata& getMetadata() const override + { + return m_metadata; + } + [[nodiscard]] AssetType getType() const override + { + return getMetadata().type; + } + [[nodiscard]] AssetID getID() const override + { + return getMetadata().id; + } + [[nodiscard]] AssetStatus getStatus() const override + { + return getMetadata().status; + } - [[nodiscard]] bool isLoaded() const override { return getStatus() == AssetStatus::LOADED; } - [[nodiscard]] bool isErrored() const override { return getStatus() == AssetStatus::ERROR; } + [[nodiscard]] bool isLoaded() const override + { + return getStatus() == AssetStatus::LOADED; + } + [[nodiscard]] bool isErrored() const override + { + return getStatus() == AssetStatus::ERROR; + } - [[nodiscard]] const std::unique_ptr& getData() const { return data; } - Asset& setData(std::unique_ptr newData); + [[nodiscard]] const std::unique_ptr& getData() const + { + return data; + } + Asset& setData(std::unique_ptr newData); - protected: - explicit Asset() : data(nullptr) - { - m_metadata.type = TAssetType; - } + protected: + explicit Asset() : data(nullptr) + { + m_metadata.type = TAssetType; + } - explicit Asset(TAssetData* data) : data(data) - { - m_metadata.type = TAssetType; - m_metadata.status = AssetStatus::LOADED; - } + explicit Asset(TAssetData* data) : data(data) + { + m_metadata.type = TAssetType; + m_metadata.status = AssetStatus::LOADED; + } - std::unique_ptr data; + std::unique_ptr data; - private: - /*virtual AssetStatus load() = 0; - virtual AssetStatus unload() = 0;*/ + private: + /*virtual AssetStatus load() = 0; + virtual AssetStatus unload() = 0;*/ }; - template concept IsAsset = requires { typename T::AssetDataType; - { T::TYPE } -> std::convertible_to; - } && std::derived_from> - && std::is_base_of_v, T> - && std::is_base_of_v; - + { + T::TYPE + } -> std::convertible_to; + } && std::derived_from> && + std::is_base_of_v, T> && std::is_base_of_v; template Asset& Asset::setData(std::unique_ptr newData) { - data.reset(); // Clean up existing data + data.reset(); // Clean up existing data if (newData == nullptr) { m_metadata.status = AssetStatus::UNLOADED; } else { @@ -241,4 +240,4 @@ namespace nexo::assets { data = std::move(newData); return *this; } -} // namespace nexo::editor +} // namespace nexo::assets diff --git a/engine/src/assets/AssetCatalog.hpp b/engine/src/assets/AssetCatalog.hpp index 4532e9072..f3c1fc5a6 100644 --- a/engine/src/assets/AssetCatalog.hpp +++ b/engine/src/assets/AssetCatalog.hpp @@ -89,14 +89,14 @@ namespace nexo::assets { * @param asset The asset to rename. * @param newName The new name for the asset. */ - bool renameAsset(const GenericAssetRef& asset, const std::string& newName) const; + [[nodiscard]] bool renameAsset(const GenericAssetRef& asset, const std::string& newName) const; /** * @brief Renames an asset in the catalog. * @param id The ID of the asset to rename. * @param newName The new name for the asset. */ - bool renameAsset(AssetID id, const std::string& newName) const; + [[nodiscard]] bool renameAsset(AssetID id, const std::string& newName) const; /** * @brief Checks if a name is valid for an asset. diff --git a/engine/src/assets/AssetImporter.cpp b/engine/src/assets/AssetImporter.cpp index 5436270f7..f9ffcdb59 100644 --- a/engine/src/assets/AssetImporter.cpp +++ b/engine/src/assets/AssetImporter.cpp @@ -13,8 +13,8 @@ /////////////////////////////////////////////////////////////////////////////// #include "AssetImporter.hpp" -#include "AssetImporterBase.hpp" #include "AssetCatalog.hpp" +#include "AssetImporterBase.hpp" #include "Assets/Model/Model.hpp" #include "Assets/Model/ModelImporter.hpp" @@ -32,70 +32,68 @@ namespace nexo::assets { AssetImporter::~AssetImporter() { - for (const auto& importers: m_importers | std::views::values) { - for (const auto& importer: importers) { + for (const auto& importers : m_importers | std::views::values) { + for (const auto& importer : importers) { delete importer; } } } - GenericAssetRef AssetImporter::importAssetAuto(const AssetLocation& location, const ImporterInputVariant& inputVariant) + GenericAssetRef AssetImporter::importAssetAuto(const AssetLocation& location, + const ImporterInputVariant& inputVariant) { // Temp fix to use all importers in priority order // TODO: change the way we store importers, maybe stop using a map - std::vector allImporters; + std::vector allImporters; std::vector allImportersDetails; - for (const auto& typeIdx: m_importers | std::views::keys) { - const auto& importers = m_importers.at(typeIdx); + for (const auto& typeIdx : m_importers | std::views::keys) { + const auto& importers = m_importers.at(typeIdx); const auto& importerDetails = m_importersDetails.at(typeIdx); for (int importerIdx = 0; importerIdx < static_cast(importers.size()); ++importerIdx) { const auto& details = importerDetails[importerIdx]; - const int priority = details.priority; - size_t k = 0; - for (; k < allImporters.size() && priority <= allImportersDetails[k].priority ; ++k); + const int priority = details.priority; + size_t k = 0; + for (; k < allImporters.size() && priority <= allImportersDetails[k].priority; ++k) + ; allImporters.insert(allImporters.begin() + static_cast(k), importers[importerIdx]); allImportersDetails.insert(allImportersDetails.begin() + static_cast(k), details); } } - if (const auto asset = importAssetTryImporters(location, inputVariant, allImporters)) - return asset; + if (const auto asset = importAssetTryImporters(location, inputVariant, allImporters)) return asset; return GenericAssetRef::null(); } GenericAssetRef AssetImporter::importAssetUsingImporter(const AssetLocation& location, - const ImporterInputVariant& inputVariant, AssetImporterBase* importer) const + const ImporterInputVariant& inputVariant, + AssetImporterBase* importer) const { AssetImporterContext* ctx = m_customCtx; AssetImporterContext ctxOnStack; if (!m_customCtx) { - ctx = &ctxOnStack; - ctx->input = inputVariant; + ctx = &ctxOnStack; + ctx->input = inputVariant; ctx->location = location; } - importer->import(*ctx); auto asset = ctx->releaseMainAsset(); - if (!asset) - return GenericAssetRef::null(); - if (asset->getID().is_nil()) - asset->m_metadata.id = boost::uuids::random_generator()(); - if (asset->m_metadata.location == AssetLocation("default")) - asset->m_metadata.location = location; + if (!asset) return GenericAssetRef::null(); + if (asset->getID().is_nil()) asset->m_metadata.id = boost::uuids::random_generator()(); + if (asset->m_metadata.location == AssetLocation("default")) asset->m_metadata.location = location; return AssetCatalog::getInstance().registerAsset(location, std::move(asset)); } GenericAssetRef AssetImporter::importAssetTryImporters(const AssetLocation& location, - const ImporterInputVariant& inputVariant, const std::vector& importers) const + const ImporterInputVariant& inputVariant, + const std::vector& importers) const { - std::vector untriedImporters; + std::vector untriedImporters; for (const auto& importer : importers) { if (importer->canRead(inputVariant)) { auto asset = importAssetUsingImporter(location, inputVariant, importer); - if (asset) - return asset; + if (asset) return asset; } else { untriedImporters.push_back(importer); } @@ -103,17 +101,15 @@ namespace nexo::assets { // If "compatibles" importers failed, try even "incompatibles" ones for (const auto& importer : untriedImporters) { auto asset = importAssetUsingImporter(location, inputVariant, importer); - if (asset) - return asset; + if (asset) return asset; } return GenericAssetRef::null(); } - const std::vector& AssetImporter:: - getImportersForType(const std::type_index& typeIdx) const + const std::vector& AssetImporter::getImportersForType(const std::type_index& typeIdx) const { - if (const auto it = m_importers.find(typeIdx) ; it == m_importers.end()) { - static std::vector empty; + if (const auto it = m_importers.find(typeIdx); it == m_importers.end()) { + static std::vector empty; return empty; } return m_importers.at(typeIdx); diff --git a/engine/src/assets/AssetImporter.hpp b/engine/src/assets/AssetImporter.hpp index fa53de4cb..447b39cf3 100644 --- a/engine/src/assets/AssetImporter.hpp +++ b/engine/src/assets/AssetImporter.hpp @@ -14,11 +14,11 @@ #pragma once +#include +#include #include #include -#include #include -#include #include "Asset.hpp" #include "AssetImporterInput.hpp" @@ -36,215 +36,231 @@ namespace nexo::assets { * @brief Registry for asset importers, allowing to import assets from various sources. */ class AssetImporter { - public: - using ImporterMap = std::map>; - - AssetImporter(); - ~AssetImporter(); - - template - requires std::derived_from - AssetRef importAsset(const AssetLocation& location, const ImporterInputVariant& inputVariant); - - /** - * @brief Automatically imports an asset using available importers. - * - * Iterates through all registered importer groups, invoking each group's importer(s) - * to attempt importing an asset from the specified location and input data variant. - * Returns the first successfully imported asset, or a null reference if none succeed. - * - * @param location The location of the asset to be imported. - * @param inputVariant The input data variant providing information for asset import. - * @return GenericAssetRef A reference to the imported asset, or GenericAssetRef::null() if import fails. - */ - GenericAssetRef importAssetAuto(const AssetLocation& location, const ImporterInputVariant& inputVariant); - - /** - * @brief Imports an asset using a specified importer and registers it. - * - * This function attempts to import an asset by invoking the given importer. It utilizes a custom import context if one - * is configured (see setCustomContext()); otherwise, it creates a temporary context initialized with the provided input data and location. - * After importing, if the asset is valid, the function ensures that the asset has a unique identifier and updates its location - * metadata if it is set to "default". The asset is then registered in the AssetCatalog. If the import fails, a null asset reference is returned. - * - * @param location The asset location used for input configuration and asset registration. - * @param inputVariant Input data required by the importer for the asset import operation. - * @param importer The importer instance responsible for performing the asset import. - * @return GenericAssetRef A reference to the imported and registered asset, or a null reference if the import fails. - */ - GenericAssetRef importAssetUsingImporter(const AssetLocation& location, const ImporterInputVariant& inputVariant, AssetImporterBase *importer) const; - - /** - * @brief Attempts to import an asset using a prioritized list of importers. - * - * This function iterates over the provided importers in two phases: - * 1. In the first phase, it attempts to import the asset - * using importers that are capable of reading the given input variant. (checking with canRead()) - * 2. If none of these succeed, it then tries the remaining - * importers regardless of compatibility. The function returns immediately - * upon the first successful import, or a null asset reference if all attempts fail. - * - * @param location The asset's location information. - * @param inputVariant A variant that encapsulates the data and configuration for asset import. - * @param importers A list of asset importers to attempt the import operation with. - * @return GenericAssetRef A reference to the successfully imported asset, or a null reference if the import fails. - */ - [[nodiscard]] GenericAssetRef importAssetTryImporters(const AssetLocation& location, const ImporterInputVariant& inputVariant, const std::vector& - importers) const; - - /** - * @brief Get all registered importers for an asset type - * - * @tparam AssetType The type of asset - * @return Vector of importers in priority order (highest first) - */ - template - requires std::derived_from - [[nodiscard]] const std::vector& getImportersForType() const; - - /** - * @brief Get all registered importers for an asset type - * - * @param typeIdx The type index of the asset - * @return Vector of importers in priority order (highest first) - */ - [[nodiscard]] const std::vector& getImportersForType(const std::type_index& typeIdx) const; - - /** - * @brief Get all registered importers - * - * @return Map of importers by asset type - */ - [[nodiscard]] const ImporterMap& getImporters() const { return m_importers; } - - /** - * @brief Check if any importers are registered for an asset type - * - * @tparam AssetType The type of asset - * @return True if importers are registered, false otherwise - */ - template - requires std::derived_from - [[nodiscard]] bool hasImportersForType() const; - - /** - * @brief Check if any importers are registered for an asset type - * - * @param typeIdx The type index of the asset - * @return True if importers are registered, false otherwise - */ - [[nodiscard]] bool hasImportersForType(const std::type_index& typeIdx) const; - - void setCustomContext(AssetImporterContext *ctx) { m_customCtx = ctx; } - - /** - * @brief Clears the custom context. - * - * Resets the internal custom context pointer to indicate that no custom context is in use. - */ - void clearCustomContext() { m_customCtx = nullptr; } - - /** - * @brief Retrieves the current custom asset importer context. - * - * This function returns a pointer to the custom context associated with the asset importer. - * The returned context can provide custom configurations or behaviors if one has been set. - * - * @return A pointer to the current AssetImporterContext, or nullptr if no custom context is set. - */ - [[nodiscard]] AssetImporterContext *getCustomContext() const { return m_customCtx; } - - void setParameters(const json& params); - - - protected: - - /** - * @brief Constructs an AssetImporter with a custom context. - * - * Initializes the AssetImporter using the provided custom context, allowing - * derived classes and unit tests to override the default importer context. - * - * @param ctx Pointer to the custom AssetImporterContext used for asset importing. - */ - explicit AssetImporter(AssetImporterContext *ctx) : m_customCtx(ctx) - { - } - - /** - * @brief Instantiates and registers a new importer. - * - * This templated function creates a new importer instance of type ImporterType using default construction and - * registers it to handle assets of type AssetType with the provided priority. For equal priority values, - * the order of registration determines the processing sequence. - * - * @tparam AssetType The asset type associated with the importer. - * @tparam ImporterType The type of the importer to be registered. - * @param priority The registration priority. Importers with equal priorities retain the order in which they were added. - */ - template - requires std::derived_from - && std::derived_from - void registerImporter(int priority = 0); - - /** - * @brief Registers an importer instance for a specific asset type. - * - * This function inserts the provided importer for the asset type into the internal registry, - * maintaining a descending order based on the importers' priority. Importers with higher priority - * are placed before those with lower priority, and if equal priorities exist, the new importer is - * appended after previously registered ones. - * - * @tparam AssetType The asset type associated with the importer. - * @param importer Pointer to the importer instance. - * @param priority An integer representing the importer's priority; higher values denote higher precedence. If equal, insertion order. - */ - template - requires std::derived_from - void registerImporter(AssetImporterBase *importer, int priority = 0); - - /** - * @brief Unregisters all importers associated with a specific asset type. - * - * Determines the runtime type index of the asset type using RTTI and delegates the unregistration - * to the overload that handles type index unregistration. - * - * @tparam AssetType The asset type for which all registered importers will be unregistered. - */ - template - requires std::derived_from - void unregisterAllImportersForType(); - - /** - * @brief Unregister all importers for an asset type - * - * @param typeIdx The type index of the asset - */ - void unregisterAllImportersForType(const std::type_index& typeIdx); - - - struct ImporterDetails { - int priority; - }; - using ImporterDetailsMap = std::map>; - - // Map from asset type to prioritized list of importers - ImporterMap m_importers; - ImporterDetailsMap m_importersDetails; - - AssetImporterContext *m_customCtx = nullptr; + public: + using ImporterMap = std::map>; + + AssetImporter(); + ~AssetImporter(); + + template + requires std::derived_from + AssetRef importAsset(const AssetLocation& location, const ImporterInputVariant& inputVariant); + + /** + * @brief Automatically imports an asset using available importers. + * + * Iterates through all registered importer groups, invoking each group's importer(s) + * to attempt importing an asset from the specified location and input data variant. + * Returns the first successfully imported asset, or a null reference if none succeed. + * + * @param location The location of the asset to be imported. + * @param inputVariant The input data variant providing information for asset import. + * @return GenericAssetRef A reference to the imported asset, or GenericAssetRef::null() if import fails. + */ + GenericAssetRef importAssetAuto(const AssetLocation& location, const ImporterInputVariant& inputVariant); + + /** + * @brief Imports an asset using a specified importer and registers it. + * + * This function attempts to import an asset by invoking the given importer. It utilizes a custom import context + * if one is configured (see setCustomContext()); otherwise, it creates a temporary context initialized with the + * provided input data and location. After importing, if the asset is valid, the function ensures that the asset + * has a unique identifier and updates its location metadata if it is set to "default". The asset is then + * registered in the AssetCatalog. If the import fails, a null asset reference is returned. + * + * @param location The asset location used for input configuration and asset registration. + * @param inputVariant Input data required by the importer for the asset import operation. + * @param importer The importer instance responsible for performing the asset import. + * @return GenericAssetRef A reference to the imported and registered asset, or a null reference if the import + * fails. + */ + GenericAssetRef importAssetUsingImporter(const AssetLocation& location, + const ImporterInputVariant& inputVariant, + AssetImporterBase* importer) const; + + /** + * @brief Attempts to import an asset using a prioritized list of importers. + * + * This function iterates over the provided importers in two phases: + * 1. In the first phase, it attempts to import the asset + * using importers that are capable of reading the given input variant. (checking with canRead()) + * 2. If none of these succeed, it then tries the remaining + * importers regardless of compatibility. The function returns immediately + * upon the first successful import, or a null asset reference if all attempts fail. + * + * @param location The asset's location information. + * @param inputVariant A variant that encapsulates the data and configuration for asset import. + * @param importers A list of asset importers to attempt the import operation with. + * @return GenericAssetRef A reference to the successfully imported asset, or a null reference if the import + * fails. + */ + [[nodiscard]] GenericAssetRef importAssetTryImporters(const AssetLocation& location, + const ImporterInputVariant& inputVariant, + const std::vector& importers) const; + + /** + * @brief Get all registered importers for an asset type + * + * @tparam AssetType The type of asset + * @return Vector of importers in priority order (highest first) + */ + template + requires std::derived_from + [[nodiscard]] const std::vector& getImportersForType() const; + + /** + * @brief Get all registered importers for an asset type + * + * @param typeIdx The type index of the asset + * @return Vector of importers in priority order (highest first) + */ + [[nodiscard]] const std::vector& getImportersForType(const std::type_index& typeIdx) const; + + /** + * @brief Get all registered importers + * + * @return Map of importers by asset type + */ + [[nodiscard]] const ImporterMap& getImporters() const + { + return m_importers; + } + + /** + * @brief Check if any importers are registered for an asset type + * + * @tparam AssetType The type of asset + * @return True if importers are registered, false otherwise + */ + template + requires std::derived_from + [[nodiscard]] bool hasImportersForType() const; + + /** + * @brief Check if any importers are registered for an asset type + * + * @param typeIdx The type index of the asset + * @return True if importers are registered, false otherwise + */ + [[nodiscard]] bool hasImportersForType(const std::type_index& typeIdx) const; + + void setCustomContext(AssetImporterContext* ctx) + { + m_customCtx = ctx; + } + + /** + * @brief Clears the custom context. + * + * Resets the internal custom context pointer to indicate that no custom context is in use. + */ + void clearCustomContext() + { + m_customCtx = nullptr; + } + + /** + * @brief Retrieves the current custom asset importer context. + * + * This function returns a pointer to the custom context associated with the asset importer. + * The returned context can provide custom configurations or behaviors if one has been set. + * + * @return A pointer to the current AssetImporterContext, or nullptr if no custom context is set. + */ + [[nodiscard]] AssetImporterContext* getCustomContext() const + { + return m_customCtx; + } + + void setParameters(const json& params); + + protected: + /** + * @brief Constructs an AssetImporter with a custom context. + * + * Initializes the AssetImporter using the provided custom context, allowing + * derived classes and unit tests to override the default importer context. + * + * @param ctx Pointer to the custom AssetImporterContext used for asset importing. + */ + explicit AssetImporter(AssetImporterContext* ctx) : m_customCtx(ctx) + {} + + /** + * @brief Instantiates and registers a new importer. + * + * This templated function creates a new importer instance of type ImporterType using default construction and + * registers it to handle assets of type AssetType with the provided priority. For equal priority values, + * the order of registration determines the processing sequence. + * + * @tparam AssetType The asset type associated with the importer. + * @tparam ImporterType The type of the importer to be registered. + * @param priority The registration priority. Importers with equal priorities retain the order in which they + * were added. + */ + template + requires std::derived_from && std::derived_from + void registerImporter(int priority = 0); + + /** + * @brief Registers an importer instance for a specific asset type. + * + * This function inserts the provided importer for the asset type into the internal registry, + * maintaining a descending order based on the importers' priority. Importers with higher priority + * are placed before those with lower priority, and if equal priorities exist, the new importer is + * appended after previously registered ones. + * + * @tparam AssetType The asset type associated with the importer. + * @param importer Pointer to the importer instance. + * @param priority An integer representing the importer's priority; higher values denote higher precedence. If + * equal, insertion order. + */ + template + requires std::derived_from + void registerImporter(AssetImporterBase* importer, int priority = 0); + + /** + * @brief Unregisters all importers associated with a specific asset type. + * + * Determines the runtime type index of the asset type using RTTI and delegates the unregistration + * to the overload that handles type index unregistration. + * + * @tparam AssetType The asset type for which all registered importers will be unregistered. + */ + template + requires std::derived_from + void unregisterAllImportersForType(); + + /** + * @brief Unregister all importers for an asset type + * + * @param typeIdx The type index of the asset + */ + void unregisterAllImportersForType(const std::type_index& typeIdx); + + struct ImporterDetails { + int priority; + }; + using ImporterDetailsMap = std::map>; + + // Map from asset type to prioritized list of importers + ImporterMap m_importers; + ImporterDetailsMap m_importersDetails; + + AssetImporterContext* m_customCtx = nullptr; }; - template requires std::derived_from && std:: - derived_from + template + requires std::derived_from && std::derived_from void AssetImporter::registerImporter(int priority) { auto importer = new ImporterType(); registerImporter(importer, priority); } - template requires std::derived_from - void AssetImporter::registerImporter(AssetImporterBase *importer, const int priority) + template + requires std::derived_from + void AssetImporter::registerImporter(AssetImporterBase* importer, const int priority) { const auto typeIdx = std::type_index(typeid(AssetType)); @@ -255,18 +271,20 @@ namespace nexo::assets { m_importersDetails[typeIdx] = {}; } - auto& importersVec = m_importers[typeIdx]; + auto& importersVec = m_importers[typeIdx]; auto& importersDetailsVec = m_importersDetails[typeIdx]; size_t i = 0; - for (; i < importersVec.size() && priority <= importersDetailsVec[i].priority ; ++i); + for (; i < importersVec.size() && priority <= importersDetailsVec[i].priority; ++i) + ; importersVec.insert(importersVec.begin() + static_cast(i), importer); importersDetailsVec.insert(importersDetailsVec.begin() + static_cast(i), {priority}); } - template requires std::derived_from + template + requires std::derived_from AssetRef AssetImporter::importAsset(const AssetLocation& location, - const ImporterInputVariant& inputVariant) + const ImporterInputVariant& inputVariant) { auto importers = getImportersForType(); if (importers.empty()) { @@ -276,22 +294,25 @@ namespace nexo::assets { return importAssetTryImporters(location, inputVariant, importers).template as(); } - template requires std::derived_from - const std::vector& AssetImporter::getImportersForType() const + template + requires std::derived_from + const std::vector& AssetImporter::getImportersForType() const { const auto typeIdx = std::type_index(typeid(AssetType)); return getImportersForType(typeIdx); } - template requires std::derived_from + template + requires std::derived_from bool AssetImporter::hasImportersForType() const { const auto typeIdx = std::type_index(typeid(AssetType)); return hasImportersForType(typeIdx); } - template requires std::derived_from + template + requires std::derived_from void AssetImporter::unregisterAllImportersForType() { const auto typeIdx = std::type_index(typeid(AssetType)); diff --git a/engine/src/assets/AssetImporterBase.hpp b/engine/src/assets/AssetImporterBase.hpp index 1a7305c10..4f9e52467 100644 --- a/engine/src/assets/AssetImporterBase.hpp +++ b/engine/src/assets/AssetImporterBase.hpp @@ -28,62 +28,60 @@ namespace nexo::assets { * @brief Interface for importing assets into the engine. */ class AssetImporterBase { - public: - AssetImporterBase() = default; - virtual ~AssetImporterBase() = default; + public: + AssetImporterBase() = default; + virtual ~AssetImporterBase() = default; - /** - * @brief Checks if the importer can read the file at the given path. - * - * Implementations should open the file and check if related importer is compatible. - * @param[in] inputVariant The input variant to check. Can be a file path or memory buffer. - * @return True if the importer can read the file, false otherwise. - */ - virtual bool canRead(const ImporterInputVariant& inputVariant) = 0; + /** + * @brief Checks if the importer can read the file at the given path. + * + * Implementations should open the file and check if related importer is compatible. + * @param[in] inputVariant The input variant to check. Can be a file path or memory buffer. + * @return True if the importer can read the file, false otherwise. + */ + virtual bool canRead(const ImporterInputVariant& inputVariant) = 0; - /** - * @brief Imports an asset from a file. - * - * This method should be overridden by the derived class to do the actual import. - * @warning Implementation MUST set the main asset data using AssetImporterContext::setMainAssetData() before return - * - * @param[in,out] ctx The context for the import. - */ - virtual void importImpl(AssetImporterContext& ctx) = 0; + /** + * @brief Imports an asset from a file. + * + * This method should be overridden by the derived class to do the actual import. + * @warning Implementation MUST set the main asset data using AssetImporterContext::setMainAssetData() before + * return + * + * @param[in,out] ctx The context for the import. + */ + virtual void importImpl(AssetImporterContext& ctx) = 0; - /** - * @brief Imports an asset from a file. - * - * This method is not intended to be overridden. Implement importImpl() to do the import. - * This method is a wrapper of importImpl() that for example catches exceptions thrown by importImpl(). - * - * @param[in,out] ctx The context for the import. - */ - void import(AssetImporterContext& ctx) noexcept - { - try { - importImpl(ctx); - if (ctx.getMainAsset() == nullptr) { - LOG(NEXO_ERROR, "Importer did not set main asset data in context"); - return; - } - } catch (const std::exception& e) { - // Log the error - if (std::holds_alternative(ctx.input)) - LOG(NEXO_ERROR, "Failed to import asset {} from file {}: {}", - std::quoted(ctx.location.getFullLocation()), - std::quoted(std::get(ctx.input).filePath.generic_string()), - e.what()); - else if (std::holds_alternative(ctx.input)) - LOG(NEXO_ERROR, "Failed to import asset {} from memory: {}", - std::quoted(ctx.location.getFullLocation()), - e.what()); - else - LOG(NEXO_ERROR, "Failed to import asset {}: {}", - std::quoted(ctx.location.getFullLocation()), - e.what()); + /** + * @brief Imports an asset from a file. + * + * This method is not intended to be overridden. Implement importImpl() to do the import. + * This method is a wrapper of importImpl() that for example catches exceptions thrown by importImpl(). + * + * @param[in,out] ctx The context for the import. + */ + void import(AssetImporterContext& ctx) noexcept + { + try { + importImpl(ctx); + if (ctx.getMainAsset() == nullptr) { + LOG(NEXO_ERROR, "Importer did not set main asset data in context"); + return; } + } catch (const std::exception& e) { + // Log the error + if (std::holds_alternative(ctx.input)) + LOG(NEXO_ERROR, "Failed to import asset {} from file {}: {}", + std::quoted(ctx.location.getFullLocation()), + std::quoted(std::get(ctx.input).filePath.generic_string()), e.what()); + else if (std::holds_alternative(ctx.input)) + LOG(NEXO_ERROR, "Failed to import asset {} from memory: {}", + std::quoted(ctx.location.getFullLocation()), e.what()); + else + LOG(NEXO_ERROR, "Failed to import asset {}: {}", std::quoted(ctx.location.getFullLocation()), + e.what()); } + } }; } // namespace nexo::assets diff --git a/engine/src/assets/AssetImporterContext.cpp b/engine/src/assets/AssetImporterContext.cpp index aa1c6581a..ca7946430 100644 --- a/engine/src/assets/AssetImporterContext.cpp +++ b/engine/src/assets/AssetImporterContext.cpp @@ -21,7 +21,7 @@ namespace nexo::assets { m_mainAsset = std::move(asset); } - const std::unique_ptr& AssetImporterContext::getMainAsset() const + const std::unique_ptr& AssetImporterContext::getMainAsset() const { return m_mainAsset; } @@ -31,7 +31,6 @@ namespace nexo::assets { return std::move(m_mainAsset); } - void AssetImporterContext::addDependency(const GenericAssetRef& dependency) { m_dependencies.push_back(dependency); diff --git a/engine/src/assets/AssetImporterContext.hpp b/engine/src/assets/AssetImporterContext.hpp index 29fb35ff8..303bc0d66 100644 --- a/engine/src/assets/AssetImporterContext.hpp +++ b/engine/src/assets/AssetImporterContext.hpp @@ -15,9 +15,9 @@ #pragma once #include -#include -#include #include +#include +#include #include "Asset.hpp" #include "AssetCatalog.hpp" @@ -36,131 +36,132 @@ namespace nexo::assets { * @brief Context class for asset importers. */ struct AssetImporterContext { - public: - ImporterInputVariant input; //< Input data for the importer - AssetLocation location = AssetLocation("default"); //< Future location of the asset in the catalog - - AssetImporterContext() = default; - ~AssetImporterContext() = default; - - /** - * @brief Set the main asset for this context - * @param asset The main asset - * @note This method must be called by the importer to set the main asset data - */ - void setMainAsset(std::unique_ptr asset); - - /** - * @brief Get the main asset data for this context - * @return The main asset data - */ - [[nodiscard]] const std::unique_ptr& getMainAsset() const; - - /** - * @brief Release the main asset data for this context - * @warning This function will take ownership of the mainAsset ptr - * The mainAsset ptr will become NULL in the context - * @return The main asset data - */ - [[nodiscard]] std::unique_ptr releaseMainAsset(); - - /** - * @brief Add dependency to main asset. - * - * Main asset will be considered parent of these dependencies. - * @param dependency The dependency to add - */ - void addDependency(const GenericAssetRef& dependency); - - /** - * @brief Get a vector of all dependencies for this context - * @return A vector of all dependencies' asset references - */ - [[nodiscard]] const std::vector& getDependencies() const; - - template - requires JSONSerializable - void setParameters(const ParamType& params); - - void setParameters(const json& params); - - template - requires JSONSerializable - [[nodiscard]] ParamType getParameters() const; - - [[nodiscard]] json getParameters() const; - - - /** - * @brief Generates a unique dependency asset location. - * - * This method creates a candidate asset location by using the current location as a base, then sets a unique name - * by incrementing an internal dependency counter and formatting the name with a dedicated formatting function. - * If the generated location already exists in the asset catalog, the method continues to update the candidate location - * until a unique one is found or the maximum allowed dependency count is exceeded. In the latter case, an error is logged, - * and the last candidate is returned. - * - * @tparam AssetType The type of the asset for which the location is being generated. - * @return AssetLocation A unique location for the dependency asset. - */ - template - requires std::derived_from - AssetLocation genUniqueDependencyLocation(); - - /** - * @brief Formats a unique asset name. - * - * Constructs a unique asset name by combining a base name, the asset type name, and a unique identifier. - * The resulting format is: `_`, where the asset type name is - * derived from the provided asset type. - * - * @param name The base name of the asset. - * @param type The type of the asset. - * @param id A unique identifier appended to ensure the name is distinct. - * @return AssetName The uniquely formatted asset name. - */ - static AssetName formatUniqueName(const std::string& name, const AssetType type, unsigned int id) - { - return {std::format("{}_{}{}", name, getAssetTypeName(type), id)}; - } + public: + ImporterInputVariant input; //< Input data for the importer + AssetLocation location = AssetLocation("default"); //< Future location of the asset in the catalog + + AssetImporterContext() = default; + ~AssetImporterContext() = default; + + /** + * @brief Set the main asset for this context + * @param asset The main asset + * @note This method must be called by the importer to set the main asset data + */ + void setMainAsset(std::unique_ptr asset); + + /** + * @brief Get the main asset data for this context + * @return The main asset data + */ + [[nodiscard]] const std::unique_ptr& getMainAsset() const; + + /** + * @brief Release the main asset data for this context + * @warning This function will take ownership of the mainAsset ptr + * The mainAsset ptr will become NULL in the context + * @return The main asset data + */ + [[nodiscard]] std::unique_ptr releaseMainAsset(); + + /** + * @brief Add dependency to main asset. + * + * Main asset will be considered parent of these dependencies. + * @param dependency The dependency to add + */ + void addDependency(const GenericAssetRef& dependency); + + /** + * @brief Get a vector of all dependencies for this context + * @return A vector of all dependencies' asset references + */ + [[nodiscard]] const std::vector& getDependencies() const; + + template + requires JSONSerializable + void setParameters(const ParamType& params); + + void setParameters(const json& params); + + template + requires JSONSerializable + [[nodiscard]] ParamType getParameters() const; + + [[nodiscard]] json getParameters() const; + + /** + * @brief Generates a unique dependency asset location. + * + * This method creates a candidate asset location by using the current location as a base, then sets a unique + * name by incrementing an internal dependency counter and formatting the name with a dedicated formatting + * function. If the generated location already exists in the asset catalog, the method continues to update the + * candidate location until a unique one is found or the maximum allowed dependency count is exceeded. In the + * latter case, an error is logged, and the last candidate is returned. + * + * @tparam AssetType The type of the asset for which the location is being generated. + * @return AssetLocation A unique location for the dependency asset. + */ + template + requires std::derived_from + AssetLocation genUniqueDependencyLocation(); + + /** + * @brief Formats a unique asset name. + * + * Constructs a unique asset name by combining a base name, the asset type name, and a unique identifier. + * The resulting format is: `_`, where the asset type name is + * derived from the provided asset type. + * + * @param name The base name of the asset. + * @param type The type of the asset. + * @param id A unique identifier appended to ensure the name is distinct. + * @return AssetName The uniquely formatted asset name. + */ + static AssetName formatUniqueName(const std::string& name, const AssetType type, unsigned int id) + { + return {std::format("{}_{}{}", name, getAssetTypeName(type), id)}; + } - private: - std::unique_ptr m_mainAsset = nullptr; //< Main asset being imported, resulting asset (MUST be set by importer) - std::vector m_dependencies; //< Dependencies to import - json m_jsonParameters; //< JSON parameters for the importer - unsigned int m_depUniqueId = 0; //< Unique ID for the dependency name + private: + std::unique_ptr m_mainAsset = + nullptr; //< Main asset being imported, resulting asset (MUST be set by importer) + std::vector m_dependencies; //< Dependencies to import + json m_jsonParameters; //< JSON parameters for the importer + unsigned int m_depUniqueId = 0; //< Unique ID for the dependency name }; - - template + template requires std::derived_from AssetLocation AssetImporterContext::genUniqueDependencyLocation() { auto depLoc = AssetLocation(location.getFullLocation()); depLoc.setName(formatUniqueName(location.getName().data(), AssetType::TYPE, ++m_depUniqueId)); - if (!AssetCatalog::getInstance().getAsset(depLoc)) - return depLoc; + if (!AssetCatalog::getInstance().getAsset(depLoc)) return depLoc; // If the location already exists, we need to generate a new one while (AssetCatalog::getInstance().getAsset(depLoc)) { depLoc.setName(formatUniqueName(location.getName().data(), AssetType::TYPE, ++m_depUniqueId)); if (m_depUniqueId > ASSET_MAX_DEPENDENCIES) { // Prevent infinite loop - LOG(NEXO_ERROR, "Failed to generate unique name for asset: {}: couldn't find unique id", depLoc.getFullLocation()); + LOG(NEXO_ERROR, "Failed to generate unique name for asset: {}: couldn't find unique id", + depLoc.getFullLocation()); break; } } return depLoc; } - template requires JSONSerializable + template + requires JSONSerializable void AssetImporterContext::setParameters(const ParamType& params) { to_json(m_jsonParameters, params); } - template requires JSONSerializable + template + requires JSONSerializable ParamType AssetImporterContext::getParameters() const { ParamType params; @@ -170,6 +171,4 @@ namespace nexo::assets { return params; } - - } // namespace nexo::assets diff --git a/engine/src/assets/AssetImporterInput.hpp b/engine/src/assets/AssetImporterInput.hpp index a3e641cce..bba685a84 100644 --- a/engine/src/assets/AssetImporterInput.hpp +++ b/engine/src/assets/AssetImporterInput.hpp @@ -14,9 +14,9 @@ #pragma once +#include #include #include -#include namespace nexo::assets { @@ -27,8 +27,8 @@ namespace nexo::assets { // Import from memory buffer, importer should read from the buffer struct ImporterMemoryInput { - std::vector memoryData; //< Memory buffer - std::string formatHint; //< For format detection with memory sources (can be ARGB8888, .obj, etc.) + std::vector memoryData; //< Memory buffer + std::string formatHint; //< For format detection with memory sources (can be ARGB8888, .obj, etc.) }; using ImporterInputVariant = std::variant; diff --git a/engine/src/assets/AssetLocation.cpp b/engine/src/assets/AssetLocation.cpp index d7a63d63e..6bdc4edd7 100644 --- a/engine/src/assets/AssetLocation.cpp +++ b/engine/src/assets/AssetLocation.cpp @@ -17,14 +17,11 @@ namespace nexo::assets { - void AssetLocation::setLocation( - const AssetName& name, - const std::string& path, - const std::optional>& packName - ) + void AssetLocation::setLocation(const AssetName& name, const std::string& path, + const std::optional>& packName) { - _name = name; - _path = normalizePathAndRemovePrefixSlash(path); + _name = name; + _path = normalizePathAndRemovePrefixSlash(path); _packName = packName; } } // namespace nexo::assets diff --git a/engine/src/assets/AssetLocation.hpp b/engine/src/assets/AssetLocation.hpp index 8d3fe9ef4..d3a0cfe9b 100644 --- a/engine/src/assets/AssetLocation.hpp +++ b/engine/src/assets/AssetLocation.hpp @@ -26,12 +26,10 @@ namespace nexo::assets { class InvalidAssetLocation final : public Exception { - public: - explicit InvalidAssetLocation( - std::string_view assetLocation, - std::string_view message, - const std::source_location loc = std::source_location::current() - ) : Exception(std::format("Invalid asset location '{}': {}", assetLocation, message), loc) {}; + public: + explicit InvalidAssetLocation(std::string_view assetLocation, std::string_view message, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Invalid asset location '{}': {}", assetLocation, message), loc){}; }; /** @@ -40,210 +38,211 @@ namespace nexo::assets { * It is used to apply our own rules on naming */ class AssetLocation { - public: - explicit AssetLocation(const std::string& fullLocation) - { - setLocation(fullLocation); + public: + explicit AssetLocation(const std::string& fullLocation) + { + setLocation(fullLocation); + } + + AssetLocation(const AssetLocation&) = default; + AssetLocation& operator=(const AssetLocation&) = default; + + AssetLocation(AssetLocation&&) noexcept = default; + AssetLocation& operator=(AssetLocation&&) noexcept = default; + + AssetLocation& setName(const AssetName& name) + { + _name = name; + return *this; + } + + /** + * @brief Sets the asset path. + * + * Assigns the given path string to the asset location. + * + * @param path The new asset path. + * @return A reference to this AssetLocation instance for chaining. + */ + AssetLocation& setPath(const std::string& path) + { + _path = normalizePathAndRemovePrefixSlash(path); + return *this; + } + + /** + * @brief Sets the asset's pack name. + * + * Assigns the provided pack name to the current asset location and returns a reference to + * the modified object, allowing for method chaining. + * + * @param packName The pack name to associate with the asset. + * @return AssetLocation& Reference to the updated AssetLocation. + */ + AssetLocation& setPackName(const AssetPackName& packName) + { + _packName = packName; + return *this; + } + + /** + * @brief Clears the pack name associated with the asset. + * + * Resets the pack name, effectively marking the asset as not belonging to any pack. + * Returns a reference to the current instance to facilitate method chaining. + * + * @return AssetLocation& A reference to the current AssetLocation instance. + */ + AssetLocation& clearPackName() + { + _packName.reset(); + return *this; + } + + /** + * @brief Get the asset's name + * @return The asset's AssetName + */ + [[nodiscard]] const AssetName& getName() const + { + return _name; + } + + /** + * @brief Get the asset's pack name + * @return The asset's AssetPackName + */ + [[nodiscard]] std::optional> getPackName() const + { + return _packName; + } + + /** + * @brief Get the asset's path + * @return The asset's path + */ + [[nodiscard]] const std::string& getPath() const + { + return _path; + } + + /** + * @brief Get the asset's full location + * @return The asset's full location as string (e.g.: packName::name@path/to/asset) + */ + [[nodiscard]] std::string getFullLocation() const + { + std::string fullLocation; + if (_packName) fullLocation += _packName->data() + "::"; + fullLocation += _name.data(); + if (!_path.empty()) { + fullLocation += "@"; + fullLocation += _path; } - - AssetLocation(const AssetLocation&) = default; - AssetLocation& operator=(const AssetLocation&) = default; - - AssetLocation(AssetLocation&&) noexcept = default; - AssetLocation& operator=(AssetLocation&&) noexcept = default; - - AssetLocation& setName(const AssetName& name) - { - _name = name; - return *this; - } - - /** - * @brief Sets the asset path. - * - * Assigns the given path string to the asset location. - * - * @param path The new asset path. - * @return A reference to this AssetLocation instance for chaining. - */ - AssetLocation& setPath(const std::string& path) - { - _path = normalizePathAndRemovePrefixSlash(path); - return *this; + return fullLocation; + } + + void setLocation(const AssetName& name, const std::string& path, + const std::optional>& packName = std::nullopt); + + /** + * @brief Parses and sets the asset's location from a full location string. + * + * Extracts the asset name, path, and optional pack name from the provided string and updates + * the corresponding internal members. The expected format is "packName::name@path" or "name@path" if + * no pack name is included. If the extracted asset name is invalid, an InvalidAssetLocation + * exception is thrown. + * + * @param fullLocation Full asset location string. + * + * @throws InvalidAssetLocation if the asset name in the provided string is invalid. + */ + void setLocation(const std::string& fullLocation) + { + std::string extractedPackName; + std::string extractedAssetName; + std::string extractedPath; + + parseFullLocation(fullLocation, extractedAssetName, extractedPath, extractedPackName); + extractedPath = normalizePathAndRemovePrefixSlash(extractedPath); + + try { + _name = AssetName(extractedAssetName); + if (!extractedPackName.empty()) _packName = AssetPackName(extractedPackName); + } catch (const InvalidName& e) { + THROW_EXCEPTION(InvalidAssetLocation, fullLocation, e.getMessage()); } - - /** - * @brief Sets the asset's pack name. - * - * Assigns the provided pack name to the current asset location and returns a reference to - * the modified object, allowing for method chaining. - * - * @param packName The pack name to associate with the asset. - * @return AssetLocation& Reference to the updated AssetLocation. - */ - AssetLocation& setPackName(const AssetPackName& packName) - { - _packName = packName; - return *this; + _path = extractedPath; + } + + /** + * @brief Compares two AssetLocation objects for equality. + * + * Determines if this AssetLocation has the same name, pack name, and path as the given object. + * + * @param assetLocation The asset location to compare with. + * @return true if both asset locations contain equivalent values; false otherwise. + */ + bool operator==(const AssetLocation& assetLocation) const + { + return _name == assetLocation._name && _packName == assetLocation._packName && _path == assetLocation._path; + } + + /** + * @brief Compares the current asset location with a full location string. + * + * Parses the provided full location string into its asset name, path, and pack name components and + * compares them with the object's corresponding values. Returns true if all components match, indicating + * that the asset location is equivalent to the provided string. + * + * @param fullLocation The full asset location string, expected to follow the format "packName::name@path". + * @return true if the extracted asset name, pack name, and path match those of this asset location; false + * otherwise. + */ + bool operator==(const std::string& fullLocation) const + { + std::string extractedPackName; + std::string extractedAssetName; + std::string extractedPath; + + parseFullLocation(fullLocation, extractedAssetName, extractedPath, extractedPackName); + + return _name == AssetName(extractedAssetName) && _packName == AssetPackName(extractedPackName) && + _path == extractedPath; + } + + /** + * @brief Parse a full asset location string into its components + * @param fullLocation The full location string to parse + * @param extractedAssetName The extracted asset name + * @param extractedPath The extracted path + * @param extractedPackName The extracted package name + * @note This function is static and can be used to parse a full location string into its components + * @warning Does not validate the extracted names + */ + static void parseFullLocation(std::string_view fullLocation, std::string& extractedAssetName, + std::string& extractedPath, std::string& extractedPackName) + { + if (const auto packNameEndPos = fullLocation.find("::"); packNameEndPos != std::string::npos) { + extractedPackName = fullLocation.substr(0, packNameEndPos); + fullLocation.remove_prefix(packNameEndPos + 2); + } else { + extractedPackName.clear(); } - - /** - * @brief Clears the pack name associated with the asset. - * - * Resets the pack name, effectively marking the asset as not belonging to any pack. - * Returns a reference to the current instance to facilitate method chaining. - * - * @return AssetLocation& A reference to the current AssetLocation instance. - */ - AssetLocation& clearPackName() - { - _packName.reset(); - return *this; + if (const auto pathStartPos = fullLocation.find('@'); pathStartPos != std::string::npos) { + extractedPath = fullLocation.substr(pathStartPos + 1); + fullLocation.remove_suffix(fullLocation.size() - pathStartPos); + } else { + extractedPath.clear(); } - - /** - * @brief Get the asset's name - * @return The asset's AssetName - */ - [[nodiscard]] const AssetName& getName() const { return _name; } - - /** - * @brief Get the asset's pack name - * @return The asset's AssetPackName - */ - [[nodiscard]] std::optional> getPackName() const { return _packName; } - - /** - * @brief Get the asset's path - * @return The asset's path - */ - [[nodiscard]] const std::string& getPath() const { return _path; } - - /** - * @brief Get the asset's full location - * @return The asset's full location as string (e.g.: packName::name@path/to/asset) - */ - [[nodiscard]] std::string getFullLocation() const - { - std::string fullLocation; - if (_packName) - fullLocation += _packName->data() + "::"; - fullLocation += _name.data(); - if (!_path.empty()) { - fullLocation += "@"; - fullLocation += _path; - } - return fullLocation; - } - - void setLocation( - const AssetName& name, - const std::string& path, - const std::optional>& packName = std::nullopt - ); - - /** - * @brief Parses and sets the asset's location from a full location string. - * - * Extracts the asset name, path, and optional pack name from the provided string and updates - * the corresponding internal members. The expected format is "packName::name@path" or "name@path" if - * no pack name is included. If the extracted asset name is invalid, an InvalidAssetLocation - * exception is thrown. - * - * @param fullLocation Full asset location string. - * - * @throws InvalidAssetLocation if the asset name in the provided string is invalid. - */ - void setLocation(const std::string& fullLocation) - { - std::string extractedPackName; - std::string extractedAssetName; - std::string extractedPath; - - parseFullLocation(fullLocation, extractedAssetName, extractedPath, extractedPackName); - extractedPath = normalizePathAndRemovePrefixSlash(extractedPath); - - try { - _name = AssetName(extractedAssetName); - if (!extractedPackName.empty()) - _packName = AssetPackName(extractedPackName); - } catch (const InvalidName& e) { - THROW_EXCEPTION(InvalidAssetLocation, fullLocation, e.getMessage()); - } - _path = extractedPath; - } - - /** - * @brief Compares two AssetLocation objects for equality. - * - * Determines if this AssetLocation has the same name, pack name, and path as the given object. - * - * @param assetLocation The asset location to compare with. - * @return true if both asset locations contain equivalent values; false otherwise. - */ - bool operator==(const AssetLocation& assetLocation) const - { - return _name == assetLocation._name && _packName == assetLocation._packName && _path == assetLocation._path; - } - - /** - * @brief Compares the current asset location with a full location string. - * - * Parses the provided full location string into its asset name, path, and pack name components and - * compares them with the object's corresponding values. Returns true if all components match, indicating - * that the asset location is equivalent to the provided string. - * - * @param fullLocation The full asset location string, expected to follow the format "packName::name@path". - * @return true if the extracted asset name, pack name, and path match those of this asset location; false otherwise. - */ - bool operator==(const std::string& fullLocation) const - { - std::string extractedPackName; - std::string extractedAssetName; - std::string extractedPath; - - parseFullLocation(fullLocation, extractedAssetName, extractedPath, extractedPackName); - - return _name == AssetName(extractedAssetName) && _packName == AssetPackName(extractedPackName) && _path == extractedPath; - } - - /** - * @brief Parse a full asset location string into its components - * @param fullLocation The full location string to parse - * @param extractedAssetName The extracted asset name - * @param extractedPath The extracted path - * @param extractedPackName The extracted package name - * @note This function is static and can be used to parse a full location string into its components - * @warning Does not validate the extracted names - */ - static void parseFullLocation( - std::string_view fullLocation, - std::string& extractedAssetName, - std::string& extractedPath, - std::string& extractedPackName - ) - { - if (const auto packNameEndPos = fullLocation.find("::"); packNameEndPos != std::string::npos) { - extractedPackName = fullLocation.substr(0, packNameEndPos); - fullLocation.remove_prefix(packNameEndPos + 2); - } else { - extractedPackName.clear(); - } - if (const auto pathStartPos = fullLocation.find('@'); pathStartPos != std::string::npos) { - extractedPath = fullLocation.substr(pathStartPos + 1); - fullLocation.remove_suffix(fullLocation.size() - pathStartPos); - } else { - extractedPath.clear(); - } - // TODO: maybe trim spaces? - extractedAssetName = fullLocation; - } - - private: - AssetName _name{"Unnamed"}; //< The name of the asset - std::optional _packName; //< The package containing the asset - std::string _path; //< The path to the asset + // TODO: maybe trim spaces? + extractedAssetName = fullLocation; + } + + private: + AssetName _name{"Unnamed"}; //< The name of the asset + std::optional _packName; //< The package containing the asset + std::string _path; //< The path to the asset }; - } // namespace nexo::assets diff --git a/engine/src/assets/AssetName.hpp b/engine/src/assets/AssetName.hpp index 753aeda27..c49410300 100644 --- a/engine/src/assets/AssetName.hpp +++ b/engine/src/assets/AssetName.hpp @@ -15,8 +15,8 @@ #pragma once -#include "ValidatedName.hpp" #include "FilenameValidator.hpp" +#include "ValidatedName.hpp" namespace nexo::assets { diff --git a/engine/src/assets/AssetPackName.hpp b/engine/src/assets/AssetPackName.hpp index e23ca24f5..8d65cb946 100644 --- a/engine/src/assets/AssetPackName.hpp +++ b/engine/src/assets/AssetPackName.hpp @@ -15,8 +15,8 @@ #pragma once -#include "ValidatedName.hpp" #include "AssetName.hpp" +#include "ValidatedName.hpp" namespace nexo::assets { diff --git a/engine/src/assets/AssetRef.cpp b/engine/src/assets/AssetRef.cpp index c5e02e835..e79bfb30d 100644 --- a/engine/src/assets/AssetRef.cpp +++ b/engine/src/assets/AssetRef.cpp @@ -14,7 +14,4 @@ #include "AssetRef.hpp" -namespace nexo::assets { - - -} // namespace nexo::assets +namespace nexo::assets {} // namespace nexo::assets diff --git a/engine/src/assets/AssetRef.hpp b/engine/src/assets/AssetRef.hpp index 488d76660..12673f350 100644 --- a/engine/src/assets/AssetRef.hpp +++ b/engine/src/assets/AssetRef.hpp @@ -26,17 +26,17 @@ namespace nexo::assets { enum class AssetType; - template + template class Asset; - template + template class AssetRef; /** * @brief A non-templated asset reference for generic asset storage */ class GenericAssetRef { - public: + public: /** * @brief Default constructor creates a null reference */ @@ -46,29 +46,35 @@ namespace nexo::assets { * @brief Construct from a shared_ptr to an asset * @param ptr The shared pointer to the asset */ - explicit GenericAssetRef(const std::shared_ptr& ptr) : m_weakPtr(ptr) {} + explicit GenericAssetRef(const std::shared_ptr& ptr) : m_weakPtr(ptr) + {} /** * @brief Check if the reference is valid * @return true if valid, false if expired */ - [[nodiscard]] bool isValid() const noexcept { + [[nodiscard]] bool isValid() const noexcept + { return !m_weakPtr.expired(); } - [[nodiscard]] bool operator==(const GenericAssetRef& other) const noexcept { + [[nodiscard]] bool operator==(const GenericAssetRef& other) const noexcept + { return m_weakPtr.lock() == other.m_weakPtr.lock(); } - [[nodiscard]] bool operator!=(const GenericAssetRef& other) const noexcept { + [[nodiscard]] bool operator!=(const GenericAssetRef& other) const noexcept + { return !(*this == other); } - [[nodiscard]] bool operator==(const std::nullptr_t) const noexcept { + [[nodiscard]] bool operator==(const std::nullptr_t) const noexcept + { return !isValid(); } - [[nodiscard]] bool operator!=(const std::nullptr_t) const noexcept { + [[nodiscard]] bool operator!=(const std::nullptr_t) const noexcept + { return isValid(); } @@ -77,7 +83,8 @@ namespace nexo::assets { * @return A shared_ptr to the asset, or nullptr if expired */ // ReSharper disable once CppHiddenFunction - [[nodiscard]] std::shared_ptr lock() const noexcept { + [[nodiscard]] std::shared_ptr lock() const noexcept + { return m_weakPtr.lock(); } @@ -87,13 +94,14 @@ namespace nexo::assets { * @return A typed AssetRef */ template - [[nodiscard]] class AssetRef as() const; // Implemented below after AssetRef definition + [[nodiscard]] class AssetRef as() const; // Implemented below after AssetRef definition /** * @brief Boolean conversion operator * @return true if the reference is valid, false otherwise */ - explicit operator bool() const noexcept { + explicit operator bool() const noexcept + { return isValid(); } @@ -101,14 +109,16 @@ namespace nexo::assets { * @brief Creates a null asset reference * @return An empty GenericAssetRef instance */ - [[nodiscard]] static GenericAssetRef null() { + [[nodiscard]] static GenericAssetRef null() + { return {}; } /** * @brief Requests the AssetCatalog to load the asset */ - void load() const { + void load() const + { if (auto ptr = lock()) { // TODO: Implement reloadAsset in AssetCatalog // Example: AssetCatalog::getInstance().reloadAsset(ptr); @@ -119,7 +129,8 @@ namespace nexo::assets { /** * @brief Requests the AssetCatalog to unload the asset but maintain the reference */ - void unload() const { + void unload() const + { if (auto ptr = lock()) { // TODO: Implement unloadAsset in AssetCatalog // Example: AssetCatalog::getInstance().unloadAsset(ptr); @@ -127,15 +138,14 @@ namespace nexo::assets { } } - // Standard copy/move operations - GenericAssetRef(const GenericAssetRef&) = default; - GenericAssetRef& operator=(const GenericAssetRef&) = default; - GenericAssetRef(GenericAssetRef&&) noexcept = default; + GenericAssetRef(const GenericAssetRef&) = default; + GenericAssetRef& operator=(const GenericAssetRef&) = default; + GenericAssetRef(GenericAssetRef&&) noexcept = default; GenericAssetRef& operator=(GenericAssetRef&&) noexcept = default; - virtual ~GenericAssetRef() = default; + virtual ~GenericAssetRef() = default; - protected: + protected: std::weak_ptr m_weakPtr; }; @@ -149,7 +159,7 @@ namespace nexo::assets { */ template class AssetRef final : public GenericAssetRef { - public: + public: /** * @brief Default constructor creates a null reference */ @@ -159,10 +169,11 @@ namespace nexo::assets { * @brief Constructs an AssetRef with the given shared_ptr to asset * @param assetPtr Shared pointer to the asset */ - explicit AssetRef(const std::shared_ptr& assetPtr) - : GenericAssetRef(assetPtr) {} + explicit AssetRef(const std::shared_ptr& assetPtr) : GenericAssetRef(assetPtr) + {} - explicit(false) AssetRef(std::nullptr_t) : GenericAssetRef(nullptr) {} + explicit(false) AssetRef(std::nullptr_t) : GenericAssetRef(nullptr) + {} /** * @brief Locks the asset reference, providing safe access @@ -175,7 +186,8 @@ namespace nexo::assets { * @brief Checks if the asset is fully loaded * @return true if the asset is loaded, false otherwise */ - [[nodiscard]] bool isLoaded() const { + [[nodiscard]] bool isLoaded() const + { if (auto ptr = lock()) { return ptr->isLoaded(); // Assumes TAsset has isLoaded() method } @@ -186,13 +198,15 @@ namespace nexo::assets { * @brief Creates a null asset reference * @return An empty AssetRef instance */ - [[nodiscard]] static AssetRef null() { + [[nodiscard]] static AssetRef null() + { return AssetRef(); } }; template - AssetRef GenericAssetRef::as() const { + AssetRef GenericAssetRef::as() const + { const auto ptr = m_weakPtr.lock(); if (!ptr) { return AssetRef::null(); diff --git a/engine/src/assets/Assets/Material/Material.hpp b/engine/src/assets/Assets/Material/Material.hpp index d89aec3a2..1e7895146 100644 --- a/engine/src/assets/Assets/Material/Material.hpp +++ b/engine/src/assets/Assets/Material/Material.hpp @@ -25,10 +25,10 @@ namespace nexo::assets { * @brief Represents a material asset. */ class Material final : public Asset { - public: - Material() = default; + public: + Material() = default; - ~Material() override = default; + ~Material() override = default; }; -} +} // namespace nexo::assets diff --git a/engine/src/assets/Assets/Model/Model.hpp b/engine/src/assets/Assets/Model/Model.hpp index 6c6367deb..036a0f835 100644 --- a/engine/src/assets/Assets/Model/Model.hpp +++ b/engine/src/assets/Assets/Model/Model.hpp @@ -14,6 +14,7 @@ #pragma once +#include "math/Bounds.hpp" #include "VertexArray.hpp" #include "assets/Asset.hpp" #include "assets/Assets/Material/Material.hpp" @@ -26,6 +27,9 @@ namespace nexo::assets { AssetRef material; glm::vec3 localCenter = {0.0f, 0.0f, 0.0f}; + + math::AABB localBounds{}; + math::BSphere localSphere{}; }; struct MeshNode { @@ -33,6 +37,12 @@ namespace nexo::assets { glm::mat4 transform{}; std::vector meshes; std::vector children; + + math::AABB localBounds{}; + math::BSphere localSphere{}; + + math::AABB modelBounds{}; + math::BSphere modelSphere{}; }; /** @@ -41,10 +51,13 @@ namespace nexo::assets { * @brief Represents a 3D model asset. */ class Model final : public Asset { - public: - Model() = default; + public: + Model() = default; + + ~Model() override = default; - ~Model() override = default; + math::AABB rootBounds{}; + math::BSphere rootSphere{}; }; -} +} // namespace nexo::assets diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index b55364b08..93744f710 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -18,16 +18,17 @@ #include #include -#include #include +#include #include "Buffer.hpp" -#include "VertexArray.hpp" #include "Path.hpp" +#include "VertexArray.hpp" +#include "math/Bounds.hpp" +#include "ModelParameters.hpp" #include "assets/AssetImporterBase.hpp" #include "assets/Assets/Model/Model.hpp" -#include "ModelParameters.hpp" #include "renderer/Renderer3D.hpp" #include "core/exceptions/Exceptions.hpp" @@ -41,7 +42,7 @@ namespace nexo::assets { extension = std::get(inputVariant).filePath.extension().string(); if (std::holds_alternative(inputVariant)) { const auto& mem = std::get(inputVariant); - extension = mem.formatHint; + extension = mem.formatHint; } const Assimp::Importer importer; return importer.IsExtensionSupported(extension); @@ -57,9 +58,8 @@ namespace nexo::assets { { auto model = std::make_unique(); - const auto param = ctx.getParameters(); - constexpr int flags = aiProcess_Triangulate - | aiProcess_GenNormals; + const auto param = ctx.getParameters(); + constexpr int flags = aiProcess_Triangulate | aiProcess_GenNormals; const aiScene* scene = nullptr; if (std::holds_alternative(ctx.input)) scene = m_importer.ReadFile(std::get(ctx.input).filePath.string(), flags); @@ -68,9 +68,8 @@ namespace nexo::assets { scene = m_importer.ReadFileFromMemory(memoryData.data(), memoryData.size(), flags, formatHint.c_str()); } if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { - //log error TODO: improve error handling in importers - if (scene) - m_importer.FreeScene(); + // log error TODO: improve error handling in importers + if (scene) m_importer.FreeScene(); throw core::LoadModelException(ctx.location.getFullLocation(), m_importer.GetErrorString()); } @@ -79,6 +78,8 @@ namespace nexo::assets { auto meshNode = processNode(ctx, scene->mRootNode, scene); model->setData(std::make_unique(meshNode)); + model->rootBounds = meshNode.modelBounds; + model->rootSphere = math::sphereFromAABB(model->rootBounds); return model; } @@ -86,12 +87,11 @@ namespace nexo::assets { { m_textures.reserve(scene->mNumTextures); // Load embedded textures - for (int i = 0; scene->mNumTextures; ++i) { - aiTexture *texture = scene->mTextures[i]; + for (unsigned int i = 0; i < scene->mNumTextures; ++i) { + aiTexture* texture = scene->mTextures[i]; auto loadedTexture = loadEmbeddedTexture(ctx, texture); m_textures.try_emplace(texture->mFilename.C_Str(), loadedTexture); } - } AssetRef ModelImporter::loadEmbeddedTexture(AssetImporterContext& ctx, aiTexture* texture) @@ -100,41 +100,40 @@ namespace nexo::assets { AssetImporter assetImporter; const ImporterInputVariant inputVariant = ImporterMemoryInput{ // Reinterpret cast to uint8_t* because this is raw memory data, not aiTexels, see assimp docs - .memoryData = std::vector(reinterpret_cast(texture->pcData), reinterpret_cast(texture->pcData) + texture->mWidth), - .formatHint = std::string(texture->achFormatHint) - }; + .memoryData = std::vector(reinterpret_cast(texture->pcData), + reinterpret_cast(texture->pcData) + texture->mWidth), + .formatHint = std::string(texture->achFormatHint)}; - return assetImporter.importAsset( - ctx.genUniqueDependencyLocation(), - inputVariant); + return assetImporter.importAsset(ctx.genUniqueDependencyLocation(), inputVariant); } // Uncompressed texture auto& catalog = AssetCatalog::getInstance(); renderer::NxTextureFormat format; if (texture->achFormatHint[0] == '\0') { // if empty, then ARGB888 - renderer::NxTextureFormatConvertArgb8ToRgba8( - reinterpret_cast(texture->pcData), - static_cast(texture->mWidth) * static_cast(texture->mHeight) * sizeof(aiTexel) - ); + renderer::NxTextureFormatConvertArgb8ToRgba8(reinterpret_cast(texture->pcData), + static_cast(texture->mWidth) * + static_cast(texture->mHeight) * + sizeof(aiTexel)); format = renderer::NxTextureFormat::RGBA8; } else { format = convertAssimpHintToNxTextureFormat(texture->achFormatHint); } if (format == renderer::NxTextureFormat::INVALID) { - LOG(NEXO_WARN, "ModelImporter: Model {}: Texture {} has an invalid format hint: {}", std::quoted(ctx.location.getFullLocation()), texture->mFilename.C_Str(), texture->achFormatHint); + LOG(NEXO_WARN, "ModelImporter: Model {}: Texture {} has an invalid format hint: {}", + std::quoted(ctx.location.getFullLocation()), texture->mFilename.C_Str(), texture->achFormatHint); return nullptr; } return catalog.createAsset(ctx.genUniqueDependencyLocation(), - reinterpret_cast(texture->pcData), texture->mWidth, texture->mHeight, format); + reinterpret_cast(texture->pcData), texture->mWidth, + texture->mHeight, format); } renderer::NxTextureFormat ModelImporter::convertAssimpHintToNxTextureFormat(const char achFormatHint[9]) { - if (std::memchr(achFormatHint, '\0', 9) == nullptr - || std::strlen(achFormatHint) != 8) { + if (std::memchr(achFormatHint, '\0', 9) == nullptr || std::strlen(achFormatHint) != 8) { return renderer::NxTextureFormat::INVALID; } @@ -143,7 +142,10 @@ namespace nexo::assets { const std::string_view bits_str(achFormatHint + 4, 4); // Parse active channels and their bit depths - struct ChannelInfo { char code; int bits; }; + struct ChannelInfo { + char code; + int bits; + }; std::vector active_channels; for (int i = 0; i < 4; ++i) { @@ -169,28 +171,22 @@ namespace nexo::assets { // Match channel patterns switch (active_channels.size()) { case 1: - if (active_channels[0].code == 'r') - return renderer::NxTextureFormat::R8; + if (active_channels[0].code == 'r') return renderer::NxTextureFormat::R8; break; case 2: - if (active_channels[0].code == 'r' && - active_channels[1].code == 'g') + if (active_channels[0].code == 'r' && active_channels[1].code == 'g') return renderer::NxTextureFormat::RG8; break; case 3: - if (active_channels[0].code == 'r' && - active_channels[1].code == 'g' && - active_channels[2].code == 'b') + if (active_channels[0].code == 'r' && active_channels[1].code == 'g' && active_channels[2].code == 'b') return renderer::NxTextureFormat::RGB8; break; case 4: - if (active_channels[0].code == 'r' && - active_channels[1].code == 'g' && - active_channels[2].code == 'b' && - active_channels[3].code == 'a') + if (active_channels[0].code == 'r' && active_channels[1].code == 'g' && + active_channels[2].code == 'b' && active_channels[3].code == 'a') return renderer::NxTextureFormat::RGBA8; break; default: @@ -209,26 +205,29 @@ namespace nexo::assets { modelPath = std::get(ctx.input).filePath; else { modelPath = Path::getExecutablePath(); - LOG(NEXO_WARN, "ModelImporter: Model {}: Model path not given (imported from memory), using executable path for texture lookup.", std::quoted(ctx.location.getFullLocation())); + LOG(NEXO_WARN, + "ModelImporter: Model {}: Model path not given (imported from memory), using executable path for " + "texture lookup.", + std::quoted(ctx.location.getFullLocation())); } const std::filesystem::path modelDirectory = modelPath.parent_path(); for (unsigned int matIdx = 0; matIdx < scene->mNumMaterials; ++matIdx) { - aiMaterial const *material = scene->mMaterials[matIdx]; + aiMaterial const* material = scene->mMaterials[matIdx]; auto materialComponent = std::make_unique(); aiColor4D color; if (material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { - materialComponent->albedoColor = { color.r, color.g, color.b, color.a }; + materialComponent->albedoColor = {color.r, color.g, color.b, color.a}; } if (material->Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) { - materialComponent->specularColor = { color.r, color.g, color.b, color.a }; + materialComponent->specularColor = {color.r, color.g, color.b, color.a}; } if (material->Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) { - materialComponent->emissiveColor = { color.r, color.g, color.b }; + materialComponent->emissiveColor = {color.r, color.g, color.b}; } if (float roughness = 0.0f; material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness) == AI_SUCCESS) { @@ -255,15 +254,15 @@ namespace nexo::assets { // Check 2: Transparency factor (inverse of opacity in some formats) float transparencyFactor = 0.0f; - if (material->Get(AI_MATKEY_TRANSPARENCYFACTOR, transparencyFactor) == AI_SUCCESS - && transparencyFactor > TRANSPARENCY_EPSILON) { - materialComponent->isOpaque = false; - } + if (material->Get(AI_MATKEY_TRANSPARENCYFACTOR, transparencyFactor) == AI_SUCCESS && + transparencyFactor > TRANSPARENCY_EPSILON) { + materialComponent->isOpaque = false; + } aiString alphaMode; - //TODO: understand why we cant access it by default - #define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode" - #define AI_MATKEY_GLTF_ALPHACUTOFF "$mat.gltf.alphaCutoff" +// TODO: understand why we cant access it by default +#define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode" +#define AI_MATKEY_GLTF_ALPHACUTOFF "$mat.gltf.alphaCutoff" if (material->Get(AI_MATKEY_GLTF_ALPHAMODE, 0, 0, alphaMode) == AI_SUCCESS) { std::string mode = alphaMode.C_Str(); if (mode == "BLEND") @@ -278,7 +277,10 @@ namespace nexo::assets { // Load Textures auto loadTexture = [&](aiTextureType type) -> AssetRef { if (material->GetTextureCount(type) > 1) { - LOG(NEXO_WARN, "ModelImporter: Model {}: Material {} has more than one texture of type {}, only the first one will be used.", std::quoted(ctx.location.getFullLocation()), matIdx, type); + LOG(NEXO_WARN, + "ModelImporter: Model {}: Material {} has more than one texture of type {}, only the first one " + "will be used.", + std::quoted(ctx.location.getFullLocation()), matIdx, type); } aiString aiStr; @@ -286,22 +288,19 @@ namespace nexo::assets { const char* cStr = aiStr.C_Str(); if (cStr[0] == '*' || scene->GetEmbeddedTexture(cStr)) { // Embedded texture - if (const auto it = m_textures.find(cStr) ; it != m_textures.end()) { + if (const auto it = m_textures.find(cStr); it != m_textures.end()) { return it->second; } } const std::filesystem::path texturePath = (modelDirectory / cStr).lexically_normal(); - const auto texturePathStr = texturePath.string(); - if (const auto it = m_textures.find(texturePathStr.c_str()) ; it != m_textures.end()) { + const auto texturePathStr = texturePath.string(); + if (const auto it = m_textures.find(texturePathStr.c_str()); it != m_textures.end()) { return it->second; } AssetImporter assetImporter; - const ImporterInputVariant inputVariant = ImporterFileInput{ - .filePath = texturePath - }; - auto assetTexture = assetImporter.importAsset( - ctx.genUniqueDependencyLocation(), - inputVariant); + const ImporterInputVariant inputVariant = ImporterFileInput{.filePath = texturePath}; + auto assetTexture = + assetImporter.importAsset(ctx.genUniqueDependencyLocation(), inputVariant); m_textures.try_emplace(texturePathStr.c_str(), assetTexture); return assetTexture; } @@ -309,64 +308,71 @@ namespace nexo::assets { }; materialComponent->albedoTexture = loadTexture(aiTextureType_DIFFUSE); - materialComponent->normalMap = loadTexture(aiTextureType_NORMALS); - materialComponent->metallicMap = loadTexture(aiTextureType_SPECULAR); // Specular can store metallic in some cases + materialComponent->normalMap = loadTexture(aiTextureType_NORMALS); + materialComponent->metallicMap = + loadTexture(aiTextureType_SPECULAR); // Specular can store metallic in some cases materialComponent->roughnessMap = loadTexture(aiTextureType_SHININESS); - materialComponent->emissiveMap = loadTexture(aiTextureType_EMISSIVE); + materialComponent->emissiveMap = loadTexture(aiTextureType_EMISSIVE); LOG(NEXO_INFO, "Loaded material: Diffuse = {}, Normal = {}, Metallic = {}, Roughness = {}", - materialComponent->albedoTexture ? "Yes" : "No", - materialComponent->normalMap ? "Yes" : "No", - materialComponent->metallicMap ? "Yes" : "No", - materialComponent->roughnessMap ? "Yes" : "No"); + materialComponent->albedoTexture ? "Yes" : "No", materialComponent->normalMap ? "Yes" : "No", + materialComponent->metallicMap ? "Yes" : "No", materialComponent->roughnessMap ? "Yes" : "No"); const auto materialRef = AssetCatalog::getInstance().createAsset( - ctx.genUniqueDependencyLocation(), - std::move(materialComponent) - ); + ctx.genUniqueDependencyLocation(), std::move(materialComponent)); m_materials[matIdx] = materialRef; } // end for (int matIdx = 0; matIdx < scene->mNumMaterials; ++matIdx) } - MeshNode ModelImporter::processNode(AssetImporterContext& ctx, aiNode const* node, const aiScene* scene) + MeshNode ModelImporter::processNode(AssetImporterContext& ctx, aiNode const* node, const aiScene* scene, const glm::mat4& parentToRoot) { auto meshNode = MeshNode{}; meshNode.name = node->mName.C_Str(); const glm::mat4 nodeTransform = convertAssimpMatrixToGLM(node->mTransformation); - meshNode.transform = nodeTransform; + meshNode.transform = nodeTransform; + + const glm::mat4 nodeToRoot = parentToRoot * nodeTransform; + meshNode.meshes.reserve(node->mNumMeshes); - for (unsigned int i = 0; i < node->mNumMeshes; i++) - { - aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; - meshNode.meshes.push_back(processMesh(ctx, mesh, scene)); + for (unsigned int i = 0; i < node->mNumMeshes; i++) { + aiMesh* m = scene->mMeshes[node->mMeshes[i]]; + Mesh out = processMesh(ctx, m, scene); + + // local bounds union (meshes only) + meshNode.localBounds = math::aabbUnion(meshNode.localBounds, out.localBounds); + + // model-space union: transform mesh local AABB by this nodeToRoot + const math::AABB w = math::aabbTransform(out.localBounds, nodeToRoot); + meshNode.modelBounds = math::aabbUnion(meshNode.modelBounds, w); + + meshNode.meshes.emplace_back(std::move(out)); } meshNode.children.reserve(node->mNumChildren); - for (unsigned int i = 0; i < node->mNumChildren; i++) - { - auto newNode = processNode(ctx, node->mChildren[i], scene); - meshNode.children.push_back(std::move(newNode)); + for (unsigned int i = 0; i < node->mNumChildren; i++) { + MeshNode child = processNode(ctx, node->mChildren[i], scene, nodeToRoot); + + meshNode.modelBounds = math::aabbUnion(meshNode.modelBounds, child.modelBounds); + meshNode.children.emplace_back(std::move(child)); } + meshNode.modelSphere = math::sphereFromAABB(meshNode.modelBounds); return meshNode; } - Mesh ModelImporter::processMesh(const AssetImporterContext& ctx, aiMesh* mesh, [[maybe_unused]] const aiScene* scene) const + Mesh ModelImporter::processMesh(const AssetImporterContext& ctx, aiMesh* mesh, + [[maybe_unused]] const aiScene* scene) const { std::shared_ptr vao = renderer::createVertexArray(); auto vertexBuffer = renderer::createVertexBuffer(mesh->mNumVertices * sizeof(renderer::NxVertex)); const renderer::NxBufferLayout cubeVertexBufferLayout = { - {renderer::NxShaderDataType::FLOAT3, "aPos"}, - {renderer::NxShaderDataType::FLOAT2, "aTexCoord"}, - {renderer::NxShaderDataType::FLOAT3, "aNormal"}, - {renderer::NxShaderDataType::FLOAT3, "aTangent"}, - {renderer::NxShaderDataType::FLOAT3, "aBiTangent"}, - {renderer::NxShaderDataType::INT, "aEntityID"} - }; + {renderer::NxShaderDataType::FLOAT3, "aPos"}, {renderer::NxShaderDataType::FLOAT2, "aTexCoord"}, + {renderer::NxShaderDataType::FLOAT3, "aNormal"}, {renderer::NxShaderDataType::FLOAT3, "aTangent"}, + {renderer::NxShaderDataType::FLOAT3, "aBiTangent"}, {renderer::NxShaderDataType::INT, "aEntityID"}}; vertexBuffer->setLayout(cubeVertexBufferLayout); std::vector vertices; std::vector indices; @@ -375,9 +381,7 @@ namespace nexo::assets { glm::vec3 minBB(+FLT_MAX, +FLT_MAX, +FLT_MAX); glm::vec3 maxBB(-FLT_MAX, -FLT_MAX, -FLT_MAX); - - for (unsigned int i = 0; i < mesh->mNumVertices; i++) - { + for (unsigned int i = 0; i < mesh->mNumVertices; i++) { renderer::NxVertex vertex{}; vertex.position = {mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z}; @@ -390,7 +394,7 @@ namespace nexo::assets { maxBB.z = std::max(maxBB.z, vertex.position.z); if (mesh->HasNormals()) { - vertex.normal = { mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z }; + vertex.normal = {mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z}; } if (mesh->mTextureCoords[0]) @@ -403,8 +407,12 @@ namespace nexo::assets { glm::vec3 centerLocal = (minBB + maxBB) * 0.5f; - for (unsigned int i = 0; i < mesh->mNumFaces; i++) - { + math::AABB bounds{}; + bounds.min = minBB; + bounds.max = maxBB; + math::BSphere sphere = math::sphereFromAABB(bounds); + + for (unsigned int i = 0; i < mesh->mNumFaces; i++) { const aiFace face = mesh->mFaces[i]; indices.insert(indices.end(), face.mIndices, face.mIndices + face.mNumIndices); } @@ -420,24 +428,22 @@ namespace nexo::assets { if (mesh->mMaterialIndex < m_materials.size()) { materialComponent = m_materials[mesh->mMaterialIndex]; } else { - LOG(NEXO_ERROR, "ModelImporter: Model {}: Mesh {} has invalid material index {}.", std::quoted(ctx.location.getFullLocation()), std::quoted(mesh->mName.C_Str()), mesh->mMaterialIndex); + LOG(NEXO_ERROR, "ModelImporter: Model {}: Mesh {} has invalid material index {}.", + std::quoted(ctx.location.getFullLocation()), std::quoted(mesh->mName.C_Str()), mesh->mMaterialIndex); } if (!materialComponent) { - LOG(NEXO_WARN, "ModelImporter: Model {}: Mesh {} has no material.", std::quoted(ctx.location.getFullLocation()), std::quoted(mesh->mName.C_Str())); + LOG(NEXO_WARN, "ModelImporter: Model {}: Mesh {} has no material.", + std::quoted(ctx.location.getFullLocation()), std::quoted(mesh->mName.C_Str())); } LOG(NEXO_INFO, "Loaded mesh {}", mesh->mName.C_Str()); - return {mesh->mName.C_Str(), vao, materialComponent, centerLocal}; + return {mesh->mName.C_Str(), vao, materialComponent, centerLocal, bounds, sphere}; } glm::mat4 ModelImporter::convertAssimpMatrixToGLM(const aiMatrix4x4& matrix) { - return { - matrix.a1, matrix.b1, matrix.c1, matrix.d1, - matrix.a2, matrix.b2, matrix.c2, matrix.d2, - matrix.a3, matrix.b3, matrix.c3, matrix.d3, - matrix.a4, matrix.b4, matrix.c4, matrix.d4 - }; + return {matrix.a1, matrix.b1, matrix.c1, matrix.d1, matrix.a2, matrix.b2, matrix.c2, matrix.d2, + matrix.a3, matrix.b3, matrix.c3, matrix.d3, matrix.a4, matrix.b4, matrix.c4, matrix.d4}; } } // namespace nexo::assets diff --git a/engine/src/assets/Assets/Model/ModelImporter.hpp b/engine/src/assets/Assets/Model/ModelImporter.hpp index b2115e2f4..46444c5e9 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.hpp +++ b/engine/src/assets/Assets/Model/ModelImporter.hpp @@ -18,38 +18,141 @@ #include #include "assets/AssetImporterBase.hpp" -#include "assets/Assets/Model/Model.hpp" #include "assets/AssetRef.hpp" +#include "assets/Assets/Model/Model.hpp" namespace nexo::assets { - constexpr float OPACITY_THRESHOLD = 0.99f; + constexpr float OPACITY_THRESHOLD = 0.99f; constexpr float TRANSPARENCY_EPSILON = 0.01f; class ModelImporter : public AssetImporterBase { - public: - ModelImporter() = default; - ~ModelImporter() override = default; + public: + ModelImporter() = default; + ~ModelImporter() override = default; + + /** + * @brief Check if the importer can read the given input variant. + * + * This function checks if the Assimp library supports the file extension + * or format hint provided in the input variant. + * + * @param inputVariant The input variant containing either a file path or memory data with a format hint. + * @return true if the importer can read the input variant, false otherwise. + */ + bool canRead(const ImporterInputVariant& inputVariant) override; + + /** + * @brief Import the model from the given context. + * + * This function loads the model using Assimp, processes its nodes and meshes, + * and populates the Model asset with the loaded data. + * + * @param ctx The asset importer context containing input data and parameters. + */ + void importImpl(AssetImporterContext& ctx) override; + + protected: + /** + * @brief Load the model from the given context. + * + * This function uses Assimp to read the model file or memory data, + * processes embedded textures and materials, and constructs the model's + * mesh hierarchy. + * @param ctx The asset importer context containing input data and parameters. + * @return A unique pointer to the loaded Model asset. + * @throws core::LoadModelException if the model cannot be loaded. + */ + std::unique_ptr loadModel(AssetImporterContext& ctx); + + /** + * @brief Load embedded textures from the Assimp scene. + * + * This function iterates over the embedded textures in the Assimp scene + * and loads them into the asset catalog. + * @param ctx The asset importer context. + * @param scene The Assimp scene containing the embedded textures. + */ + void loadSceneEmbeddedTextures(AssetImporterContext& ctx, const aiScene* scene); + + /** + * @brief Load materials from the Assimp scene. + * + * This function iterates over the materials in the Assimp scene, + * extracts their properties and associated textures, and loads them + * into the asset catalog. + * @param ctx The asset importer context. + * @param texture The embedded texture to load. + * @return An AssetRef to the loaded Texture asset. + */ + AssetRef loadEmbeddedTexture(AssetImporterContext& ctx, aiTexture* texture); - bool canRead(const ImporterInputVariant& inputVariant) override; + /** + * @brief Load materials from the Assimp scene. + * + * This function iterates over the materials in the Assimp scene, + * extracts their properties and associated textures, and loads them + * into the asset catalog. + * @param ctx The asset importer context. + * @param scene The Assimp scene containing the materials. + */ + void loadSceneMaterials(AssetImporterContext& ctx, const aiScene* scene); - void importImpl(AssetImporterContext& ctx) override; + /** * @brief Process an Assimp node and its children recursively. + * + * This function processes the given Assimp node, extracts its meshes, + * and recursively processes its child nodes to build a hierarchical + * representation of the model. + * + * @param ctx The asset importer context. + * @param node The Assimp node to process. + * @param scene The Assimp scene containing the node. + * @return A MeshNode representing the processed node and its children. + */ + MeshNode processNode(AssetImporterContext& ctx, aiNode const* node, const aiScene* scene, const glm::mat4& parentToRoot = glm::mat4(1.0f)); - protected: - std::unique_ptr loadModel(AssetImporterContext& ctx); - void loadSceneEmbeddedTextures(AssetImporterContext& ctx, const aiScene* scene); - AssetRef loadEmbeddedTexture(AssetImporterContext& ctx, aiTexture *texture); - void loadSceneMaterials(AssetImporterContext& ctx, const aiScene* scene); + /** + * @brief Process an Assimp mesh and convert it to the engine's Mesh format. + * + * This function extracts vertex data, indices, and material information + * from the given Assimp mesh and constructs a Mesh object compatible with + * the engine's rendering system. + * + * @param ctx The asset importer context. + * @param mesh The Assimp mesh to process. + * @param scene The Assimp scene containing the mesh. + * @return A Mesh representing the processed Assimp mesh. + */ + Mesh processMesh(const AssetImporterContext& ctx, aiMesh* mesh, const aiScene* scene) const; - MeshNode processNode(AssetImporterContext& ctx, aiNode const *node, const aiScene* scene); - Mesh processMesh(const AssetImporterContext& ctx, aiMesh* mesh, const aiScene* scene) const; + /** + * @brief Convert Assimp texture format hint to engine texture format. + * + * This function maps the Assimp texture format hint to the corresponding + * texture format used by the engine's rendering system. + * + * @param achFormatHint The Assimp texture format hint (e.g., "png", "jpg"). + * @return The corresponding NxTextureFormat, or NxTextureFormat::INVALID if unsupported. + */ + static renderer::NxTextureFormat convertAssimpHintToNxTextureFormat(const char achFormatHint[9]); - static renderer::NxTextureFormat convertAssimpHintToNxTextureFormat(const char achFormatHint[9]); - static glm::mat4 convertAssimpMatrixToGLM(const aiMatrix4x4& matrix); + /** + * @brief Convert an Assimp matrix to a GLM matrix. + * + * This function converts a 4x4 matrix from Assimp's aiMatrix4x4 format + * to GLM's glm::mat4 format. + * + * @param matrix The Assimp matrix to convert. + * @return The converted GLM matrix. + */ + static glm::mat4 convertAssimpMatrixToGLM(const aiMatrix4x4& matrix); - Assimp::Importer m_importer; //< Assimp importer instance - std::unordered_map> m_textures; //< Map of textures used in the model, std::string is the texture name (path, or *0, *1, etc. for embedded textures, see assimp) - std::vector> m_materials; //< Map of materials used in the model, index is the assimp material index + Assimp::Importer m_importer; //< Assimp importer instance + std::unordered_map> + m_textures; //< Map of textures used in the model, std::string is the texture name (path, or *0, *1, etc. + // for embedded textures, see assimp) + std::vector> + m_materials; //< Map of materials used in the model, index is the assimp material index }; } // namespace nexo::assets diff --git a/engine/src/assets/Assets/Model/ModelParameters.hpp b/engine/src/assets/Assets/Model/ModelParameters.hpp index 94f7ef1bd..edda40f43 100644 --- a/engine/src/assets/Assets/Model/ModelParameters.hpp +++ b/engine/src/assets/Assets/Model/ModelParameters.hpp @@ -28,9 +28,7 @@ namespace nexo::assets { struct ModelImportParameters { std::vector textureParameters; - NLOHMANN_DEFINE_TYPE_INTRUSIVE(ModelImportParameters, - textureParameters - ) + NLOHMANN_DEFINE_TYPE_INTRUSIVE(ModelImportParameters, textureParameters) }; /** @@ -41,46 +39,29 @@ namespace nexo::assets { bool calculateTangentSpace = false; bool joinIdenticalVertices = true; bool generateSmoothNormals = false; - bool optimizeMeshes = true; - int maxBones = 60; + bool optimizeMeshes = true; + int maxBones = 60; // Scene options bool importAnimations = true; - bool importMaterials = true; - bool importTextures = true; - float globalScale = 1.0f; + bool importMaterials = true; + bool importTextures = true; + float globalScale = 1.0f; // Texture options - enum class TextureQuality { - LOW = 0, - MEDIUM = 1, - HIGH = 2 - }; + enum class TextureQuality { LOW = 0, MEDIUM = 1, HIGH = 2 }; TextureQuality textureQuality = TextureQuality::MEDIUM; - bool convertToUncompressed = false; + bool convertToUncompressed = false; - NLOHMANN_DEFINE_TYPE_INTRUSIVE(ModelImportPostProcessParameters, - calculateTangentSpace, - joinIdenticalVertices, - generateSmoothNormals, - optimizeMeshes, - maxBones, - importAnimations, - importMaterials, - importTextures, - globalScale, - textureQuality, - convertToUncompressed - ) + NLOHMANN_DEFINE_TYPE_INTRUSIVE(ModelImportPostProcessParameters, calculateTangentSpace, joinIdenticalVertices, + generateSmoothNormals, optimizeMeshes, maxBones, importAnimations, + importMaterials, importTextures, globalScale, textureQuality, + convertToUncompressed) }; NLOHMANN_JSON_SERIALIZE_ENUM(ModelImportPostProcessParameters::TextureQuality, - { - {ModelImportPostProcessParameters::TextureQuality::LOW, "LOW"}, - {ModelImportPostProcessParameters::TextureQuality::MEDIUM, "MEDIUM"}, - {ModelImportPostProcessParameters::TextureQuality::HIGH, "HIGH"} - } - ); - + {{ModelImportPostProcessParameters::TextureQuality::LOW, "LOW"}, + {ModelImportPostProcessParameters::TextureQuality::MEDIUM, "MEDIUM"}, + {ModelImportPostProcessParameters::TextureQuality::HIGH, "HIGH"}}); } // namespace nexo::assets diff --git a/engine/src/assets/Assets/Texture/Texture.hpp b/engine/src/assets/Assets/Texture/Texture.hpp index 1ca8faf1a..545d5f3bb 100644 --- a/engine/src/assets/Assets/Texture/Texture.hpp +++ b/engine/src/assets/Assets/Texture/Texture.hpp @@ -16,6 +16,8 @@ #include "assets/Asset.hpp" +#include + namespace nexo::assets { struct TextureData { @@ -28,113 +30,110 @@ namespace nexo::assets { * @brief Represents a texture asset. */ class Texture final : public Asset { - public: - - /** - * @brief Default constructor. - * - * Creates an empty Texture asset without an underlying texture. - * Use one of the provided static factory methods to create a fully initialized texture. - */ - Texture() = default; + public: + /** + * @brief Default constructor. + * + * Creates an empty Texture asset without an underlying texture. + * Use one of the provided static factory methods to create a fully initialized texture. + */ + Texture() = default; - /** - * @brief Constructs a Texture object from a file path. - * - * Creates a texture asset by loading image data from the specified file. - * Supported formats include PNG, JPEG, BMP, GIF, TGA, and more - * (any format supported by stb_image). - * - * @param path The path to the texture file. - * - * @throws NxFileNotFoundException If the file cannot be found or read. - * @throws NxTextureUnsupportedFormat If the image format is not supported. - * @throws NxTextureInvalidSize If the image dimensions exceed the maximum texture size. - */ - explicit Texture(const std::filesystem::path &path) - : Asset() - { - const auto texture = renderer::NxTexture2D::create(path.string()); - auto textureData = std::make_unique(); - textureData->texture = texture; - setData(std::move(textureData)); - } + /** + * @brief Constructs a Texture object from a file path. + * + * Creates a texture asset by loading image data from the specified file. + * Supported formats include PNG, JPEG, BMP, GIF, TGA, and more + * (any format supported by stb_image). + * + * @param path The path to the texture file. + * + * @throws NxFileNotFoundException If the file cannot be found or read. + * @throws NxTextureUnsupportedFormat If the image format is not supported. + * @throws NxTextureInvalidSize If the image dimensions exceed the maximum texture size. + */ + explicit Texture(const std::filesystem::path &path) : Asset() + { + const auto texture = renderer::NxTexture2D::create(path.string()); + auto textureData = std::make_unique(); + textureData->texture = texture; + setData(std::move(textureData)); + } - /** - * @brief Constructs a blank Texture with the specified dimensions. - * - * Creates a new empty texture with the given width and height. - * The texture is initialized with transparent black pixels (all zeros). - * This is useful for creating render targets or textures that will be - * filled with data later. - * - * @param width The width of the texture in pixels. - * @param height The height of the texture in pixels. - * - * @throws NxTextureInvalidSize If the dimensions exceed the maximum texture size. - */ - Texture(const unsigned int width, const unsigned int height) - : Asset() - { - const auto texture = renderer::NxTexture2D::create(width, height); - auto textureData = std::make_unique(); - textureData->texture = texture; - setData(std::move(textureData)); - } + /** + * @brief Constructs a blank Texture with the specified dimensions. + * + * Creates a new empty texture with the given width and height. + * The texture is initialized with transparent black pixels (all zeros). + * This is useful for creating render targets or textures that will be + * filled with data later. + * + * @param width The width of the texture in pixels. + * @param height The height of the texture in pixels. + * + * @throws NxTextureInvalidSize If the dimensions exceed the maximum texture size. + */ + Texture(const unsigned int width, const unsigned int height) : Asset() + { + const auto texture = renderer::NxTexture2D::create(width, height); + auto textureData = std::make_unique(); + textureData->texture = texture; + setData(std::move(textureData)); + } - /** - * @brief Constructs a Texture from raw pixel data in memory. - * - * Creates a texture from a raw pixel buffer with the specified dimensions and format. - * This is useful for creating textures from procedurally generated data or when working - * with raw pixel data from other sources. - * - * @param buffer Pointer to the raw pixel data. The buffer should contain pixel data - * in a format that matches the specified NxTextureFormat. The data consists - * of height scanlines of width pixels, with each pixel consisting of N components - * (where N depends on the format). The first pixel pointed to is bottom-left-most - * in the image. There is no padding between image scanlines or between pixels. - * Each component is an 8-bit unsigned value (uint8_t). - * @param width The width of the texture in pixels. - * @param height The height of the texture in pixels. - * @param format The format of the pixel data (R8, RG8, RGB8, or RGBA8). - * - * @throws NxInvalidValue If the buffer is null. - * @throws NxTextureUnsupportedFormat If the specified format is not supported. - * @throws NxTextureInvalidSize If the dimensions exceed the maximum texture size. - */ - Texture(const uint8_t *buffer, const unsigned int width, const unsigned int height, const renderer::NxTextureFormat format) - : Asset() - { - const auto texture = renderer::NxTexture2D::create(buffer, width, height, format); - auto textureData = std::make_unique(); - textureData->texture = texture; - setData(std::move(textureData)); - } + /** + * @brief Constructs a Texture from raw pixel data in memory. + * + * Creates a texture from a raw pixel buffer with the specified dimensions and format. + * This is useful for creating textures from procedurally generated data or when working + * with raw pixel data from other sources. + * + * @param buffer Pointer to the raw pixel data. The buffer should contain pixel data + * in a format that matches the specified NxTextureFormat. The data consists + * of height scanlines of width pixels, with each pixel consisting of N components + * (where N depends on the format). The first pixel pointed to is bottom-left-most + * in the image. There is no padding between image scanlines or between pixels. + * Each component is an 8-bit unsigned value (uint8_t). + * @param width The width of the texture in pixels. + * @param height The height of the texture in pixels. + * @param format The format of the pixel data (R8, RG8, RGB8, or RGBA8). + * + * @throws NxInvalidValue If the buffer is null. + * @throws NxTextureUnsupportedFormat If the specified format is not supported. + * @throws NxTextureInvalidSize If the dimensions exceed the maximum texture size. + */ + Texture(const uint8_t *buffer, const unsigned int width, const unsigned int height, + const renderer::NxTextureFormat format) + : Asset() + { + const auto texture = renderer::NxTexture2D::create(buffer, width, height, format); + auto textureData = std::make_unique(); + textureData->texture = texture; + setData(std::move(textureData)); + } - /** - * @brief Constructs a Texture from image data in memory. - * - * Creates a texture by loading image data from a memory buffer. - * The buffer should contain a complete image file (e.g., PNG, JPEG) - * that will be decoded using stb_image. - * - * @param buffer Pointer to the image file data in memory. - * @param size Size of the buffer in bytes. - * - * @throws NxTextureUnsupportedFormat If the image format is not supported. - * @throws NxTextureInvalidSize If the image dimensions exceed the maximum texture size. - */ - Texture(const uint8_t* buffer, const unsigned int size) - : Asset() - { - const auto texture = renderer::NxTexture2D::create(buffer, size); - auto textureData = std::unique_ptr(); - textureData->texture = texture; - setData(std::move(textureData)); - } + /** + * @brief Constructs a Texture from image data in memory. + * + * Creates a texture by loading image data from a memory buffer. + * The buffer should contain a complete image file (e.g., PNG, JPEG) + * that will be decoded using stb_image. + * + * @param buffer Pointer to the image file data in memory. + * @param size Size of the buffer in bytes. + * + * @throws NxTextureUnsupportedFormat If the image format is not supported. + * @throws NxTextureInvalidSize If the image dimensions exceed the maximum texture size. + */ + Texture(const uint8_t *buffer, const unsigned int size) : Asset() + { + const auto texture = renderer::NxTexture2D::create(buffer, size); + auto textureData = std::unique_ptr(); + textureData->texture = texture; + setData(std::move(textureData)); + } - ~Texture() override = default; + ~Texture() override = default; }; -} +} // namespace nexo::assets diff --git a/engine/src/assets/Assets/Texture/TextureImporter.cpp b/engine/src/assets/Assets/Texture/TextureImporter.cpp index c59894379..f7c275aec 100644 --- a/engine/src/assets/Assets/Texture/TextureImporter.cpp +++ b/engine/src/assets/Assets/Texture/TextureImporter.cpp @@ -15,10 +15,10 @@ #include "TextureImporter.hpp" #include +#include #include #include "assets/AssetImporterBase.hpp" #include "assets/Assets/Texture/Texture.hpp" -#include namespace nexo::assets { @@ -34,7 +34,8 @@ namespace nexo::assets { void TextureImporter::importImpl(AssetImporterContext& ctx) { - // TODO: we need to import textures independently from graphics API back end renderer::NxTexture2D::create implementation + // TODO: we need to import textures independently from graphics API back end renderer::NxTexture2D::create + // implementation auto asset = std::make_unique(); std::shared_ptr rendererTexture; if (std::holds_alternative(ctx.input)) @@ -43,7 +44,7 @@ namespace nexo::assets { const auto data = std::get(ctx.input).memoryData; rendererTexture = renderer::NxTexture2D::create(data.data(), static_cast(data.size())); } - auto assetData = std::make_unique(); + auto assetData = std::make_unique(); assetData->texture = rendererTexture; asset->m_metadata.id = boost::uuids::random_generator()(); @@ -57,14 +58,15 @@ namespace nexo::assets { if (input.formatHint == "ARGB8888") { // Special case for ARGB8888 format (from assimp model textures) return true; } - const int ok = stbi_info_from_memory(input.memoryData.data(), static_cast(input.memoryData.size()), nullptr, nullptr, nullptr); - return ok; + const int infoResult = stbi_info_from_memory(input.memoryData.data(), static_cast(input.memoryData.size()), + nullptr, nullptr, nullptr); + return infoResult; } bool TextureImporter::canReadFile(const ImporterFileInput& input) { - const int ok = stbi_info(input.filePath.string().c_str(), nullptr, nullptr, nullptr); - return ok; + const int infoResult = stbi_info(input.filePath.string().c_str(), nullptr, nullptr, nullptr); + return infoResult; } } // namespace nexo::assets diff --git a/engine/src/assets/Assets/Texture/TextureImporter.hpp b/engine/src/assets/Assets/Texture/TextureImporter.hpp index 9b1b4aadb..492d3c951 100644 --- a/engine/src/assets/Assets/Texture/TextureImporter.hpp +++ b/engine/src/assets/Assets/Texture/TextureImporter.hpp @@ -19,15 +19,43 @@ namespace nexo::assets { class TextureImporter final : public AssetImporterBase { - public: - TextureImporter() = default; - ~TextureImporter() override = default; - - bool canRead(const ImporterInputVariant& inputVariant) override; - void importImpl(AssetImporterContext& ctx) override; - protected: - static bool canReadMemory(const ImporterMemoryInput& input); - static bool canReadFile(const ImporterFileInput& input); + public: + TextureImporter() = default; + ~TextureImporter() override = default; + + /** + * @brief Check if the importer can read the given input variant. + * This function checks if the input variant is a file with a supported image extension + * or memory data with a recognized image format. + * @param inputVariant The input variant containing either a file path or memory data with a format hint. + * @return true if the importer can read the input variant, false otherwise. + */ + bool canRead(const ImporterInputVariant& inputVariant) override; + + /** + * @brief Import the texture from the given context. + * This function loads the texture using stb_image, creates a Texture asset, + * and populates it with the loaded texture data. + * @param ctx The asset importer context containing input data and parameters. + */ + void importImpl(AssetImporterContext& ctx) override; + + private: + /** + * @brief Check if the importer can read texture data from memory. + * This function checks if the format hint is recognized or if stb_image can identify the image format. + * @param input The memory input containing the image data and format hint. + * @return true if the importer can read the memory data, false otherwise. + */ + static bool canReadMemory(const ImporterMemoryInput& input); + + /** + * @brief Check if the importer can read texture data from a file. + * This function checks if the file extension is one of the supported image formats. + * @param input The file input containing the file path. + * @return true if the importer can read the file, false otherwise. + */ + static bool canReadFile(const ImporterFileInput& input); }; } // namespace nexo::assets diff --git a/engine/src/assets/Assets/Texture/TextureParameters.hpp b/engine/src/assets/Assets/Texture/TextureParameters.hpp index e2845e743..d16319576 100644 --- a/engine/src/assets/Assets/Texture/TextureParameters.hpp +++ b/engine/src/assets/Assets/Texture/TextureParameters.hpp @@ -23,37 +23,30 @@ namespace nexo::assets { */ struct TextureImportParameters { bool generateMipmaps = true; - bool convertToSRGB = true; - bool flipVertically = true; + bool convertToSRGB = true; + bool flipVertically = true; + /** @brief Texture format options */ enum class Format { - Preserve, // Keep original format - RGB, // Convert to RGB - RGBA, // Convert to RGBA with alpha - BC1, // Block compression (DXT1) - BC3, // Block compression (DXT5) - BC7 // High quality block compression + Preserve, // Keep original format + RGB, // Convert to RGB + RGBA, // Convert to RGBA with alpha + BC1, // Block compression (DXT1) + BC3, // Block compression (DXT5) + BC7 // High quality block compression }; Format format = Format::Preserve; - int maxSize = 4096; // Max texture dimension + int maxSize = 4096; // Max texture dimension float compressionQuality = 0.9f; - NLOHMANN_DEFINE_TYPE_INTRUSIVE(TextureImportParameters, - generateMipmaps, - convertToSRGB, - flipVertically, - format, - maxSize, - compressionQuality - ) + NLOHMANN_DEFINE_TYPE_INTRUSIVE(TextureImportParameters, generateMipmaps, convertToSRGB, flipVertically, format, + maxSize, compressionQuality) }; /** * @brief Post-process import parameters for textures */ - struct TexturesImportPostProcessParameters { - }; - + struct TexturesImportPostProcessParameters {}; } // namespace nexo::assets diff --git a/engine/src/assets/FilenameValidator.hpp b/engine/src/assets/FilenameValidator.hpp index afb74cd22..09979a53e 100644 --- a/engine/src/assets/FilenameValidator.hpp +++ b/engine/src/assets/FilenameValidator.hpp @@ -15,36 +15,28 @@ #pragma once -#include #include +#include #include namespace nexo::assets { struct FilenameValidator { static inline const auto ValidationRegex = std::regex("^[a-zA-Z0-9._-]*$"); - static constexpr auto MaxLength = 255; - static constexpr auto ForbiddenKeywords = { - "CON", "PRN", "AUX", "NUL", - "COM1", "COM2", "COM3", "COM4", - "COM5", "COM6", "COM7", "COM8", - "COM9", "LPT1", "LPT2", "LPT3", - "LPT4", "LPT5", "LPT6", "LPT7", - "LPT8", "LPT9" - }; + static constexpr auto MaxLength = 255; + static constexpr auto ForbiddenKeywords = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", + "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", + "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}; [[nodiscard]] static std::optional validate(const std::string_view name) { - if (name.empty()) - return "Cannot be empty."; - if (name.size() > MaxLength) - return "Cannot exceed 255 characters."; + if (name.empty()) return "Cannot be empty."; + if (name.size() > MaxLength) return "Cannot exceed 255 characters."; if (!std::regex_match(name.data(), ValidationRegex)) return "Allowed characters are 0-9, a-z, A-Z, '.', '_', and '-'."; for (const auto& keyword : ForbiddenKeywords) { - if (name == keyword) - return "Cannot be a reserved keyword."; + if (name == keyword) return "Cannot be a reserved keyword."; } return std::nullopt; // Valid name } }; -} +} // namespace nexo::assets diff --git a/engine/src/assets/ValidatedName.cpp b/engine/src/assets/ValidatedName.cpp index 403f129cd..8f7978b29 100644 --- a/engine/src/assets/ValidatedName.cpp +++ b/engine/src/assets/ValidatedName.cpp @@ -14,10 +14,4 @@ #include "ValidatedName.hpp" -namespace nexo::assets { - - - - - -} // namespace nexo::assets +namespace nexo::assets {} // namespace nexo::assets diff --git a/engine/src/assets/ValidatedName.hpp b/engine/src/assets/ValidatedName.hpp index 002f3345f..3715e0c7b 100644 --- a/engine/src/assets/ValidatedName.hpp +++ b/engine/src/assets/ValidatedName.hpp @@ -1,10 +1,10 @@ #pragma once #include +#include +#include #include #include -#include -#include #include "Exception.hpp" @@ -16,17 +16,18 @@ namespace nexo::assets { * @brief Exception thrown when a name fails validation. */ class InvalidName final : public Exception { - public: - explicit InvalidName( - std::string_view name, - std::string_view message, - const std::source_location loc = std::source_location::current() - ) : Exception(std::format("Invalid name '{}': {}", name, message), loc) {} + public: + explicit InvalidName(std::string_view name, std::string_view message, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Invalid name '{}': {}", name, message), loc) + {} }; - template + template concept Validator = requires(std::string_view name) { - { T::validate(name) } -> std::same_as>; //< The validation method. + { + T::validate(name) + } -> std::same_as>; //< The validation method. }; /** @@ -34,71 +35,94 @@ namespace nexo::assets { * * @brief Base class for validated names. */ - template + template class ValidatedName { - public: - virtual ~ValidatedName() = default; - - // Constructors - explicit ValidatedName(const std::string_view name) : _value(name) { - if (auto errorMessage = TValidator::validate(name); errorMessage.has_value()) { - _value = "Unnamed"; - THROW_EXCEPTION(InvalidName, name, errorMessage.value()); - } + public: + virtual ~ValidatedName() = default; + + // Constructors + explicit ValidatedName(const std::string_view name) : _value(name) + { + if (auto errorMessage = TValidator::validate(name); errorMessage.has_value()) { + _value = "Unnamed"; + THROW_EXCEPTION(InvalidName, name, errorMessage.value()); } - explicit(false) ValidatedName(const std::string& name) : ValidatedName(std::string_view(name)) {} - explicit(false) ValidatedName(const char* name) : ValidatedName(std::string_view(name)) {} - - - - - /** - * @brief Returns the size of the name. - */ - [[nodiscard]] std::size_t size() const { return _value.size(); } - - /** - * @brief Implicit conversions for convenience. - */ - - explicit operator std::string() const { return _value; } - explicit operator std::string_view() const { return _value; } - explicit operator const char*() const { return _value.c_str(); } - - /** - * @brief Returns the underlying name as a string. - */ - [[nodiscard]] const std::string& data() const { return _value; } - [[nodiscard]] const char* c_str() const { return _value.c_str(); } - - - /** - * @brief Equality and inequality operators. - */ - bool operator==(const ValidatedName& other) const { return _value == other._value; } - bool operator!=(const ValidatedName& other) const { return !(*this == other); } - - ValidatedName& operator=(const ValidatedName& other) = default; - ValidatedName& operator=(std::string_view name); - ValidatedName& operator=(const std::string& name); - - ValidatedName& operator=(const char* name); - - std::optional rename(std::string_view name); - - /** - * @brief Validates a name. - * @param name The name to validate. - * @return An error message if the name is invalid, or std::nullopt if it is valid. - * @note This function is static and can be used to validate a name without creating an instance. - */ - static std::optional validate(std::string_view name) { - return TValidator::validate(name); - } - - private: - - std::string _value; //< The validated name value. + } + explicit(false) ValidatedName(const std::string& name) : ValidatedName(std::string_view(name)) + {} + explicit(false) ValidatedName(const char* name) : ValidatedName(std::string_view(name)) + {} + + /** + * @brief Returns the size of the name. + */ + [[nodiscard]] std::size_t size() const + { + return _value.size(); + } + + /** + * @brief Implicit conversions for convenience. + */ + + explicit operator std::string() const + { + return _value; + } + explicit operator std::string_view() const + { + return _value; + } + explicit operator const char*() const + { + return _value.c_str(); + } + + /** + * @brief Returns the underlying name as a string. + */ + [[nodiscard]] const std::string& data() const + { + return _value; + } + [[nodiscard]] const char* c_str() const + { + return _value.c_str(); + } + + /** + * @brief Equality and inequality operators. + */ + bool operator==(const ValidatedName& other) const + { + return _value == other._value; + } + bool operator!=(const ValidatedName& other) const + { + return !(*this == other); + } + + ValidatedName& operator=(const ValidatedName& other) = default; + ValidatedName& operator=(std::string_view name); + ValidatedName& operator=(const std::string& name); + + ValidatedName& operator=(const char* name); + + std::optional rename(std::string_view name); + + /** + * @brief Validates a name. + * @param name The name to validate. + * @return An error message if the name is invalid, or std::nullopt if it is valid. + * @note This function is static and can be used to validate a name without creating an instance. + */ + static std::optional validate(std::string_view name) + { + return TValidator::validate(name); + } + + private: + std::string _value; //< The validated name value. }; template @@ -127,8 +151,7 @@ namespace nexo::assets { template std::optional ValidatedName::rename(const std::string_view name) { - if (auto errorMessage = validate(name); errorMessage.has_value()) - return errorMessage; + if (auto errorMessage = validate(name); errorMessage.has_value()) return errorMessage; _value = name; return std::nullopt; } diff --git a/engine/src/components/BillboardMesh.hpp b/engine/src/components/BillboardMesh.hpp index f12582e58..fc9f47754 100644 --- a/engine/src/components/BillboardMesh.hpp +++ b/engine/src/components/BillboardMesh.hpp @@ -15,8 +15,8 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "VertexArray.hpp" #include +#include "VertexArray.hpp" namespace nexo::components { enum class BillboardType { @@ -27,7 +27,7 @@ namespace nexo::components { struct BillboardComponent { BillboardType type = BillboardType::FULL; - glm::vec3 axis = {0.0f, 1.0f, 0.0f}; // For AXIS_CUSTOM type + glm::vec3 axis = {0.0f, 1.0f, 0.0f}; // For AXIS_CUSTOM type std::shared_ptr vao; }; -} +} // namespace nexo::components diff --git a/engine/src/components/Camera.cpp b/engine/src/components/Camera.cpp index 30d656633..c94147055 100644 --- a/engine/src/components/Camera.cpp +++ b/engine/src/components/Camera.cpp @@ -18,22 +18,23 @@ namespace nexo::components { [[nodiscard]] glm::mat4 CameraComponent::getProjectionMatrix() const { if (type == CameraType::PERSPECTIVE) { - return glm::perspective(glm::radians(fov), static_cast(width) / static_cast(height), nearPlane, farPlane); + return glm::perspective(glm::radians(fov), static_cast(width) / static_cast(height), + nearPlane, farPlane); } return glm::ortho(0.0f, static_cast(width), static_cast(height), 0.0f, nearPlane, farPlane); } - [[nodiscard]] glm::mat4 CameraComponent::getViewMatrix(const TransformComponent &transf) const + [[nodiscard]] glm::mat4 CameraComponent::getViewMatrix(const TransformComponent& transf) { const glm::vec3 forward = transf.quat * glm::vec3(0.0f, 0.0f, -1.0f); - const glm::vec3 upVec = transf.quat * glm::vec3(0.0f, 1.0f, 0.0f); + const glm::vec3 upVec = transf.quat * glm::vec3(0.0f, 1.0f, 0.0f); return glm::lookAt(transf.pos, transf.pos + forward, upVec); } void CameraComponent::resize(const unsigned int newWidth, const unsigned int newHeight) { - width = newWidth; - height = newHeight; + width = newWidth; + height = newHeight; resizing = true; if (m_renderTarget) { m_renderTarget->resize(newWidth, newHeight); @@ -41,37 +42,25 @@ namespace nexo::components { pipeline.resize(newWidth, newHeight); } - void CameraComponent::restore(const CameraComponent::Memento& memento) { if (width != memento.width || height != memento.height) { - width = memento.width; + width = memento.width; height = memento.height; resize(width, height); } viewportLocked = memento.viewportLocked; - fov = memento.fov; - nearPlane = memento.nearPlane; - farPlane = memento.farPlane; - type = memento.type; - clearColor = memento.clearColor; - main = memento.main; + fov = memento.fov; + nearPlane = memento.nearPlane; + farPlane = memento.farPlane; + type = memento.type; + clearColor = memento.clearColor; + main = memento.main; m_renderTarget = memento.renderTarget; } [[nodiscard]] CameraComponent::Memento CameraComponent::save() const { - return { - width, - height, - viewportLocked, - fov, - nearPlane, - farPlane, - type, - clearColor, - main, - m_renderTarget - }; + return {width, height, viewportLocked, fov, nearPlane, farPlane, type, clearColor, main, m_renderTarget}; } -} +} // namespace nexo::components diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 4448ef59d..1116f496b 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -13,21 +13,18 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include #include "Transform.hpp" #include "core/event/Input.hpp" -#include "renderer/Framebuffer.hpp" #include "ecs/Definitions.hpp" +#include "renderer/Framebuffer.hpp" #include "renderer/RenderPipeline.hpp" -#include namespace nexo::components { - enum class CameraType { - PERSPECTIVE, - ORTHOGRAPHIC - }; + enum class CameraType { PERSPECTIVE, ORTHOGRAPHIC }; - /** + /** * @brief Represents the camera component. * * Contains settings for viewport dimensions, field-of-view, @@ -35,20 +32,21 @@ namespace nexo::components { * Also stores the render target and flags for active state and resizing. */ struct CameraComponent { - unsigned int width; ///< Width of the camera's viewport. - unsigned int height; ///< Height of the camera's viewport. - bool viewportLocked = false; ///< If true, the viewport dimensions are locked. - float fov = 45.0f; ///< Field of view (in degrees) for perspective cameras. - float nearPlane = 0.1f; ///< Near clipping plane distance. - float farPlane = 1000.0f; ///< Far clipping plane distance. - CameraType type = CameraType::PERSPECTIVE; ///< The type of the camera (perspective or orthographic). - - glm::vec4 clearColor = {37.0f/255.0f, 35.0f/255.0f, 50.0f/255.0f, 111.0f/255.0f}; ///< Background clear color. - - bool active = true; ///< Indicates if the camera is active. - bool render = false; ///< Indicates if the camera has to be rendered. - bool main = true; ///< Indicates if the camera is the main camera. - bool resizing = false; ///< Internal flag indicating if the camera is resizing. + unsigned int width; ///< Width of the camera's viewport. + unsigned int height; ///< Height of the camera's viewport. + bool viewportLocked = false; ///< If true, the viewport dimensions are locked. + float fov = 45.0f; ///< Field of view (in degrees) for perspective cameras. + float nearPlane = 0.1f; ///< Near clipping plane distance. + float farPlane = 1000.0f; ///< Far clipping plane distance. + CameraType type = CameraType::PERSPECTIVE; ///< The type of the camera (perspective or orthographic). + + glm::vec4 clearColor = {37.0f / 255.0f, 35.0f / 255.0f, 50.0f / 255.0f, + 111.0f / 255.0f}; ///< Background clear color. + + bool active = true; ///< Indicates if the camera is active. + bool render = false; ///< Indicates if the camera has to be rendered. + bool main = true; ///< Indicates if the camera is the main camera. + bool resizing = false; ///< Internal flag indicating if the camera is resizing. std::shared_ptr m_renderTarget = nullptr; ///< The render target framebuffer. @@ -73,7 +71,7 @@ namespace nexo::components { * @param transf The transform component of the camera. * @return glm::mat4 The view matrix. */ - [[nodiscard]] glm::mat4 getViewMatrix(const TransformComponent &transf) const; + [[nodiscard]] static glm::mat4 getViewMatrix(const TransformComponent& transf) ; /** * @brief Resizes the camera's viewport. @@ -111,12 +109,15 @@ namespace nexo::components { * yaw, and pitch. */ struct PerspectiveCameraController { - PerspectiveCameraController() { lastMousePosition = event::getMousePosition(); } + PerspectiveCameraController() + { + lastMousePosition = event::getMousePosition(); + } glm::vec2 lastMousePosition{}; ///< Last recorded mouse position. - float mouseSensitivity = 0.1f;///< Sensitivity factor for mouse movement. - float translationSpeed = 5.0f; ///< Camera speed - bool wasMouseReleased = true; + float mouseSensitivity = 0.1f; ///< Sensitivity factor for mouse movement. + float translationSpeed = 5.0f; ///< Camera speed + bool wasMouseReleased = true; bool wasActiveLastFrame = true; struct Memento { @@ -132,10 +133,7 @@ namespace nexo::components { [[nodiscard]] Memento save() const { - return { - mouseSensitivity, - translationSpeed - }; + return {mouseSensitivity, translationSpeed}; } }; @@ -145,10 +143,10 @@ namespace nexo::components { * This component allows the camera to orbit around a target entity based on mouse input. */ struct PerspectiveCameraTarget { - glm::vec2 lastMousePosition = event::getMousePosition(); ///< Last recorded mouse position. - float mouseSensitivity = 0.1f; ///< Sensitivity factor for mouse movement. - float distance = 5.0f; ///< Distance from the camera to the target entity. - ecs::Entity targetEntity; ///< The target entity the camera is focusing on. + glm::vec2 lastMousePosition = event::getMousePosition(); ///< Last recorded mouse position. + float mouseSensitivity = 0.1f; ///< Sensitivity factor for mouse movement. + float distance = 5.0f; ///< Distance from the camera to the target entity. + ecs::Entity targetEntity; ///< The target entity the camera is focusing on. struct Memento { float mouseSensitivity; @@ -159,17 +157,13 @@ namespace nexo::components { void restore(const Memento& memento) { mouseSensitivity = memento.mouseSensitivity; - distance = memento.distance; - targetEntity = memento.targetEntity; + distance = memento.distance; + targetEntity = memento.targetEntity; } [[nodiscard]] Memento save() const { - return { - mouseSensitivity, - distance, - targetEntity - }; + return {mouseSensitivity, distance, targetEntity}; } }; @@ -180,10 +174,10 @@ namespace nexo::components { * and the render target used for rendering. */ struct CameraContext { - glm::mat4 viewProjectionMatrix; ///< Combined view and projection matrix. - glm::vec3 cameraPosition; ///< The position of the camera. - glm::vec4 clearColor; ///< Clear color used for rendering. + glm::mat4 viewProjectionMatrix; ///< Combined view and projection matrix. + glm::vec3 cameraPosition; ///< The position of the camera. + glm::vec4 clearColor; ///< Clear color used for rendering. std::shared_ptr renderTarget; ///< The render target framebuffer. renderer::RenderPipeline pipeline; }; -} +} // namespace nexo::components diff --git a/engine/src/components/Editor.hpp b/engine/src/components/Editor.hpp index 2414f2649..da2cbd8cb 100644 --- a/engine/src/components/Editor.hpp +++ b/engine/src/components/Editor.hpp @@ -15,4 +15,4 @@ namespace nexo::components { struct SelectedTag {}; -} +} // namespace nexo::components diff --git a/engine/src/components/Light.hpp b/engine/src/components/Light.hpp index a9c400178..edd37871b 100644 --- a/engine/src/components/Light.hpp +++ b/engine/src/components/Light.hpp @@ -13,14 +13,14 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include #include #include -#include #include "ecs/Definitions.hpp" constexpr unsigned int MAX_POINT_LIGHTS = 10; -constexpr unsigned int MAX_SPOT_LIGHTS = 10; +constexpr unsigned int MAX_SPOT_LIGHTS = 10; namespace nexo::components { @@ -40,15 +40,13 @@ namespace nexo::components { { return {color}; } - - }; struct DirectionalLightComponent { - DirectionalLightComponent() = default; - explicit DirectionalLightComponent(const glm::vec3 &lightDirection, - const glm::vec3 &lightColor = {1.0f, 1.0f, 1.0f}) : - direction(lightDirection), color(lightColor) {}; + DirectionalLightComponent() = default; + explicit DirectionalLightComponent(const glm::vec3& lightDirection, + const glm::vec3& lightColor = {1.0f, 1.0f, 1.0f}) + : direction(lightDirection), color(lightColor){}; glm::vec3 direction{}; glm::vec3 color{}; @@ -61,7 +59,7 @@ namespace nexo::components { void restore(const Memento& memento) { direction = memento.direction; - color = memento.color; + color = memento.color; } [[nodiscard]] Memento save() const @@ -75,7 +73,7 @@ namespace nexo::components { float linear{}; float quadratic{}; float maxDistance = 50.0f; - float constant = 1.0f; + float constant = 1.0f; struct Memento { glm::vec3 color{}; @@ -87,11 +85,11 @@ namespace nexo::components { void restore(const Memento& memento) { - color = memento.color; - linear = memento.linear; - quadratic = memento.quadratic; + color = memento.color; + linear = memento.linear; + quadratic = memento.quadratic; maxDistance = memento.maxDistance; - constant = memento.constant; + constant = memento.constant; } [[nodiscard]] Memento save() const @@ -108,7 +106,7 @@ namespace nexo::components { float linear{}; float quadratic{}; float maxDistance = 325.0f; - float constant = 1.0f; + float constant = 1.0f; struct Memento { glm::vec3 direction{}; @@ -123,14 +121,14 @@ namespace nexo::components { void restore(const Memento& memento) { - direction = memento.direction; - color = memento.color; - cutOff = memento.cutOff; + direction = memento.direction; + color = memento.color; + cutOff = memento.cutOff; outerCutoff = memento.outerCutoff; - linear = memento.linear; - quadratic = memento.quadratic; + linear = memento.linear; + quadratic = memento.quadratic; maxDistance = memento.maxDistance; - constant = memento.constant; + constant = memento.constant; } [[nodiscard]] Memento save() const @@ -147,4 +145,4 @@ namespace nexo::components { unsigned int spotLightCount = 0; DirectionalLightComponent dirLight; }; -} +} // namespace nexo::components diff --git a/engine/src/components/MaterialComponent.hpp b/engine/src/components/MaterialComponent.hpp index 973326425..a6c4d8757 100644 --- a/engine/src/components/MaterialComponent.hpp +++ b/engine/src/components/MaterialComponent.hpp @@ -32,4 +32,4 @@ namespace nexo::components { return {material}; } }; -} +} // namespace nexo::components diff --git a/engine/src/components/Model.hpp b/engine/src/components/Model.hpp index 647c8b520..ef4b11a42 100644 --- a/engine/src/components/Model.hpp +++ b/engine/src/components/Model.hpp @@ -22,4 +22,4 @@ namespace nexo::components { struct ModelComponent { assets::AssetRef model; }; -} +} // namespace nexo::components diff --git a/engine/src/components/Name.hpp b/engine/src/components/Name.hpp index 6183b7f0a..ce2575247 100644 --- a/engine/src/components/Name.hpp +++ b/engine/src/components/Name.hpp @@ -33,4 +33,4 @@ namespace nexo::components { return {name}; } }; -} +} // namespace nexo::components diff --git a/engine/src/components/Parent.hpp b/engine/src/components/Parent.hpp index ab0ebf3c6..0f9dce69c 100644 --- a/engine/src/components/Parent.hpp +++ b/engine/src/components/Parent.hpp @@ -13,9 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "ecs/Definitions.hpp" #include "assets/AssetRef.hpp" #include "assets/Assets/Model/Model.hpp" +#include "ecs/Definitions.hpp" namespace nexo::components { @@ -50,8 +50,8 @@ namespace nexo::components { void restore(const Memento& memento) { - name = memento.name; - modelRef = memento.model; + name = memento.name; + modelRef = memento.model; childCount = memento.childCount; } @@ -61,4 +61,4 @@ namespace nexo::components { } }; -} +} // namespace nexo::components diff --git a/engine/src/components/PhysicsBodyComponent.hpp b/engine/src/components/PhysicsBodyComponent.hpp index 15963a129..9bbf30300 100644 --- a/engine/src/components/PhysicsBodyComponent.hpp +++ b/engine/src/components/PhysicsBodyComponent.hpp @@ -20,22 +20,24 @@ namespace nexo::components { struct PhysicsBodyComponent { enum class Type { Static, Dynamic }; - + struct Memento { - JPH::BodyID bodyID; - Type type; + JPH::BodyID bodyID{}; + Type type{Type::Static}; }; - void restore(const Memento &memento) { + void restore(const Memento &memento) + { bodyID = memento.bodyID; - type = memento.type; + type = memento.type; } - - [[nodiscard]] Memento save() const { + + [[nodiscard]] Memento save() const + { return Memento{bodyID, type}; } JPH::BodyID bodyID; Type type{}; }; -} +} // namespace nexo::components diff --git a/engine/src/components/Render.hpp b/engine/src/components/Render.hpp index 70f8f886d..270feefcf 100644 --- a/engine/src/components/Render.hpp +++ b/engine/src/components/Render.hpp @@ -13,19 +13,11 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -namespace nexo::components -{ - enum class PrimitiveType - { - UNKNOWN, - CUBE, - MESH, - BILLBOARD, - _COUNT - }; +namespace nexo::components { + enum class PrimitiveType { UNKNOWN, CUBE, MESH, BILLBOARD, _COUNT }; struct RenderComponent { - bool isRendered = true; + bool isRendered = true; PrimitiveType type = PrimitiveType::MESH; RenderComponent() = default; @@ -38,15 +30,12 @@ namespace nexo::components void restore(const Memento &memento) { isRendered = memento.isRendered; - type = memento.type; + type = memento.type; } [[nodiscard]] Memento save() const { - return { - isRendered, - type - }; + return {isRendered, type}; } }; -} +} // namespace nexo::components diff --git a/engine/src/components/Render3D.hpp b/engine/src/components/Render3D.hpp index eef740a73..1617dfdd6 100644 --- a/engine/src/components/Render3D.hpp +++ b/engine/src/components/Render3D.hpp @@ -21,22 +21,22 @@ namespace nexo::components { struct Material { - glm::vec4 albedoColor = glm::vec4(1.0f); + glm::vec4 albedoColor = glm::vec4(1.0f); glm::vec4 specularColor = glm::vec4(1.0f); glm::vec3 emissiveColor = glm::vec3(0.0f); bool isOpaque = true; - float roughness = 0.0f; // 0 = smooth, 1 = rough - float metallic = 0.0f; // 0 = non-metal, 1 = fully metallic - float opacity = 1.0f; // 1 = opaque, 0 = fully transparent + float roughness = 0.0f; // 0 = smooth, 1 = rough + float metallic = 0.0f; // 0 = non-metal, 1 = fully metallic + float opacity = 1.0f; // 1 = opaque, 0 = fully transparent assets::AssetRef albedoTexture = nullptr; - assets::AssetRef normalMap = nullptr; - assets::AssetRef metallicMap = nullptr; - assets::AssetRef roughnessMap = nullptr; - assets::AssetRef emissiveMap = nullptr; + assets::AssetRef normalMap = nullptr; + assets::AssetRef metallicMap = nullptr; + assets::AssetRef roughnessMap = nullptr; + assets::AssetRef emissiveMap = nullptr; std::string shader = "Phong"; }; -} +} // namespace nexo::components diff --git a/engine/src/components/RenderContext.hpp b/engine/src/components/RenderContext.hpp index 09ff0bf67..85fde7ebe 100644 --- a/engine/src/components/RenderContext.hpp +++ b/engine/src/components/RenderContext.hpp @@ -14,20 +14,21 @@ #pragma once #include "Camera.hpp" -#include "Types.hpp" #include "Light.hpp" +#include "Types.hpp" namespace nexo::components { struct RenderContext { - int sceneRendered = -1; + int sceneRendered = -1; SceneType sceneType = SceneType::GAME; - bool isChildWindow = false; //<< Is the current scene embedded in a sub window ? - glm::vec2 viewportBounds[2]{}; //<< Viewport bounds in absolute coordinates (if the window viewport is embedded in the window), this is used for mouse coordinates + bool isChildWindow = false; //<< Is the current scene embedded in a sub window ? + glm::vec2 viewportBounds[2]{}; //<< Viewport bounds in absolute coordinates (if the window viewport is embedded + //in the window), this is used for mouse coordinates struct GridParams { - bool enabled = true; - float gridSize = 100.0f; + bool enabled = true; + float gridSize = 100.0f; float minPixelsBetweenCells = 2.0f; - float cellSize = 0.025f; + float cellSize = 0.025f; }; GridParams gridParams; std::vector cameras; @@ -36,12 +37,11 @@ namespace nexo::components { RenderContext() = default; // Delete copy constructor to enforce singleton semantics - RenderContext(const RenderContext&) = delete; + RenderContext(const RenderContext&) = delete; RenderContext& operator=(const RenderContext&) = delete; RenderContext(RenderContext&& other) noexcept - : sceneRendered(other.sceneRendered), sceneType{}, - cameras(std::move(other.cameras)), + : sceneRendered(other.sceneRendered), sceneType{}, cameras(std::move(other.cameras)), sceneLights(other.sceneLights) {} @@ -52,15 +52,15 @@ namespace nexo::components { void reset() { - sceneRendered = -1; - isChildWindow = false; + sceneRendered = -1; + isChildWindow = false; viewportBounds[0] = glm::vec2{}; viewportBounds[1] = glm::vec2{}; cameras.clear(); - sceneLights.ambientLight = glm::vec3(0.0f); + sceneLights.ambientLight = glm::vec3(0.0f); sceneLights.pointLightCount = 0; - sceneLights.spotLightCount = 0; - sceneLights.dirLight = DirectionalLightComponent{}; + sceneLights.spotLightCount = 0; + sceneLights.dirLight = DirectionalLightComponent{}; } }; -} +} // namespace nexo::components diff --git a/engine/src/components/SceneComponents.hpp b/engine/src/components/SceneComponents.hpp index c797a394b..10c1e59b4 100644 --- a/engine/src/components/SceneComponents.hpp +++ b/engine/src/components/SceneComponents.hpp @@ -18,7 +18,7 @@ namespace nexo::components { struct SceneTag { unsigned int id{}; - bool isActive = true; + bool isActive = true; bool isRendered = true; struct Memento { @@ -29,8 +29,8 @@ namespace nexo::components { void restore(const Memento &memento) { - id = memento.id; - isActive = memento.isActive; + id = memento.id; + isActive = memento.isActive; isRendered = memento.isRendered; } @@ -40,4 +40,4 @@ namespace nexo::components { } }; -} +} // namespace nexo::components diff --git a/engine/src/components/StaticMesh.hpp b/engine/src/components/StaticMesh.hpp index db3746186..ecd2633a0 100644 --- a/engine/src/components/StaticMesh.hpp +++ b/engine/src/components/StaticMesh.hpp @@ -38,4 +38,4 @@ namespace nexo::components { } }; -} +} // namespace nexo::components diff --git a/engine/src/components/Transform.cpp b/engine/src/components/Transform.cpp index 670bcc9e9..2b60ceb7c 100644 --- a/engine/src/components/Transform.cpp +++ b/engine/src/components/Transform.cpp @@ -19,12 +19,12 @@ namespace nexo::components { void TransformComponent::restore(const TransformComponent::Memento &memento) { - pos = memento.position; - quat = memento.rotation; - size = memento.scale; + pos = memento.position; + quat = memento.rotation; + size = memento.scale; localMatrix = memento.localMatrix; localCenter = memento.localCenter; - children = memento.children; + children = memento.children; } [[nodiscard]] TransformComponent::Memento TransformComponent::save() const @@ -34,8 +34,7 @@ namespace nexo::components { void TransformComponent::addChild(const ecs::Entity childEntity) { - if (std::ranges::find(children, childEntity) != children.end()) - return; + if (std::ranges::find(children, childEntity) != children.end()) return; children.push_back(childEntity); } @@ -43,4 +42,4 @@ namespace nexo::components { { children.erase(std::ranges::remove(children, childEntity).begin(), children.end()); } -} +} // namespace nexo::components diff --git a/engine/src/components/Transform.hpp b/engine/src/components/Transform.hpp index 9806626e0..f5c5746ed 100644 --- a/engine/src/components/Transform.hpp +++ b/engine/src/components/Transform.hpp @@ -50,4 +50,4 @@ namespace nexo::components { std::vector children{}; }; -} +} // namespace nexo::components diff --git a/engine/src/components/Uuid.hpp b/engine/src/components/Uuid.hpp index 1dbdddc29..82a538b2a 100644 --- a/engine/src/components/Uuid.hpp +++ b/engine/src/components/Uuid.hpp @@ -13,29 +13,30 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include #include +#include namespace nexo::components { - //TODO: Implement a safer uuid generator - inline std::string genUuid() - { - static std::random_device dev; - static std::mt19937 rng(dev()); - - std::uniform_int_distribution dist(0, 15); - - constexpr bool dash[] = { false, false, false, false, true, false, true, false, true, false, true, false, false, false, false, false }; - - std::string res; - for (const bool i : dash) { - const auto v = "0123456789abcdef"; - if (i) res += "-"; - res += v[dist(rng)]; - res += v[dist(rng)]; - } - return res; - } + // TODO: Implement a safer uuid generator + inline std::string genUuid() + { + static std::random_device dev; + static std::mt19937 rng(dev()); + + std::uniform_int_distribution dist(0, 15); + + constexpr bool dash[] = {false, false, false, false, true, false, true, false, + true, false, true, false, false, false, false, false}; + + std::string res; + for (const bool i : dash) { + const auto v = "0123456789abcdef"; + if (i) res += "-"; + res += v[dist(rng)]; + res += v[dist(rng)]; + } + return res; + } struct UuidComponent { struct Memento { @@ -54,4 +55,4 @@ namespace nexo::components { std::string uuid = genUuid(); }; -} +} // namespace nexo::components diff --git a/engine/src/components/Video.cpp b/engine/src/components/Video.cpp new file mode 100644 index 000000000..2c43d13da --- /dev/null +++ b/engine/src/components/Video.cpp @@ -0,0 +1,185 @@ +//// Video.cpp //////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzz zzzzz +// zzz zzz zzz zzzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzz zzzz zzzz zzzz +// zzz zzz zzz zzzz zzzz zzzz zzzz zzzz +// zzz zzzzz zzzzzzzzzzzzz zzzz zzzz zzzzz zzzzz +// +// Author: Marie GIACOMEL +// Date: 05/09/2025 +// Description: Class Video +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Video.hpp" +#include +#include +#include +#include "assets/AssetCatalog.hpp" + +namespace nexo::components { + + static std::unique_ptr createTextureFromMat(const cv::Mat &frame) + { + cv::Mat tmp; + + if (frame.channels() == 3) { + cv::cvtColor(frame, tmp, cv::COLOR_BGR2RGB); + } else if (frame.channels() == 4) { + cv::cvtColor(frame, tmp, cv::COLOR_BGRA2RGBA); + } else { + tmp = frame.clone(); + } + + cv::flip(tmp, tmp, 0); + + if (!tmp.isContinuous()) tmp = tmp.clone(); + + const auto width = static_cast(tmp.cols); + const auto height = static_cast(tmp.rows); + // const size_t byteCount = tmp.total() * tmp.elemSize(); + + // std::vector buffer(byteCount); + // std::memcpy(buffer.data(), tmp.data, byteCount); + + renderer::NxTextureFormat format; + if (tmp.channels() == 1) + format = renderer::NxTextureFormat::R8; + else if (tmp.channels() == 3) + format = renderer::NxTextureFormat::RGB8; + else + format = renderer::NxTextureFormat::RGBA8; + + return std::make_unique(tmp.data, width, height, format); + } + + bool VideoComponent::loadVideoFrames(const std::string &videoPath) + { + cv::VideoCapture cap(videoPath); + + if (!cap.isOpened()) { + LOG_ONCE(NEXO_ERROR, "Failed to open the video {}", videoPath); + return false; + } + int frameNumber = static_cast(cap.get(cv::VideoCaptureProperties::CAP_PROP_FRAME_COUNT)); // Get total + frameRate = cap.get(cv::VideoCaptureProperties::CAP_PROP_FPS); // Get FPS + + cv::Mat frame; + frames.reserve(frameNumber); + size_t frameCount = 0; + auto &catalog = assets::AssetCatalog::getInstance(); + + while (true) { + cap >> frame; + if (frame.empty()) break; + + auto texturePtr = createTextureFromMat(frame); + + auto texture = catalog.registerAsset( + assets::AssetLocation(std::format("_internal::frame_{}@_internal", frameCount)), std::move(texturePtr)); + + components::Material material; + material.albedoTexture = texture.as(); + material.albedoColor = glm::vec4(1.0f); // White to show texture colors + material.isOpaque = false; + material.shader = "Albedo unshaded transparent"; + + frames.push_back(catalog.createAsset( + assets::AssetLocation(std::format("_internal::frame_mat_{}@_internal", frameCount)), + std::make_unique(material))); + + ++frameCount; + } + + cap.release(); + + path = videoPath; + nbFrame = frameCount; + isLoaded = true; + return true; + } + + void VideoComponent::updateFrame() + { + auto secondsPassed = std::chrono::duration_cast>( + std::chrono::high_resolution_clock::now() - lastFrameTime); + + auto ¤tKeyframe = keyframes[currentKeyframeIndex]; + if (secondsPassed.count() >= (currentKeyframe.end - currentKeyframe.start)) { + switch (currentKeyframe.keyframeType) { + case KeyframeType::LOOP: + lastFrameTime = std::chrono::high_resolution_clock::now(); + setCurrentFrame(static_cast(currentKeyframe.start * frameRate)); + break; + case KeyframeType::TRANSITION: + skipToNextKeyframe(); + break; + default: + // lastFrameTime = std::chrono::high_resolution_clock::now(); + setCurrentFrame(static_cast(currentKeyframe.end * frameRate)); + break; + } + return; + } + + setCurrentFrame(static_cast(secondsPassed.count() * frameRate + currentKeyframe.start * frameRate)); + } + + void VideoComponent::setCurrentKeyframe(size_t keyframeIndex) + { + if (keyframeIndex >= keyframes.size()) { + if (loopVideo) { + currentKeyframeIndex = 0; + } else { + currentKeyframeIndex = keyframes.size() - 1; + } + return; + } + currentKeyframeIndex = keyframeIndex; + auto &keyframe = keyframes[currentKeyframeIndex]; + currentFrameIndex = static_cast(keyframe.start * frameRate); + lastFrameTime = std::chrono::high_resolution_clock::now(); + } + + void VideoComponent::setCurrentFrame(size_t frameIndex) + { + if (frameIndex >= nbFrame) { + if (loopVideo) { + currentFrameIndex = 0; + } else { + currentFrameIndex = nbFrame - 1; + } + return; + } + currentFrameIndex = frameIndex; + } + + void VideoComponent::skipToNextKeyframe() + { + setCurrentKeyframe(currentKeyframeIndex + 1); + } + + void VideoComponent::skipToPreviousKeyframe() + { + if (currentKeyframeIndex == 0) { + if (loopVideo) setCurrentKeyframe(keyframes.size() - 1); + return; + } + auto &previousKeyframe = keyframes[currentKeyframeIndex - 1]; + if (previousKeyframe.keyframeType == KeyframeType::TRANSITION) { + if (currentKeyframeIndex < 2) { + if (loopVideo) setCurrentKeyframe(keyframes.size() - 1); + return; + } + setCurrentKeyframe(currentKeyframeIndex - 2); + return; + } + setCurrentKeyframe(currentKeyframeIndex - 1); + } + + void VideoComponent::restartVideo() + { + setCurrentKeyframe(0); + } +} // namespace nexo::components diff --git a/engine/src/components/Video.hpp b/engine/src/components/Video.hpp new file mode 100644 index 000000000..9dbd9b3ee --- /dev/null +++ b/engine/src/components/Video.hpp @@ -0,0 +1,62 @@ +//// Name.hpp /////////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzz zzzzz +// zzz zzz zzz zzzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzz zzzz zzzz zzzz +// zzz zzz zzz zzzz zzzz zzzz zzzz zzzz +// zzz zzzzz zzzzzzzzzzzzz zzzz zzzz zzzzz zzzzz +// +// Author: Marie GIACOMEL +// Date: 24/06/2025 +// Description: Header file for the video component +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "assets/Assets/Material/Material.hpp" + +namespace nexo::components { + + /** + * @brief Enum representing different types of keyframes in a video. + */ + enum class KeyframeType { NORMAL, LOOP, TRANSITION }; + + /** + * @brief Structure representing a keyframe in a video, including its start and end times and type. + */ + struct VideoKeyframe { + double start; + double end; + KeyframeType keyframeType; + }; + + /** + * @brief Component for handling video playback within an entity. + */ + struct VideoComponent { + std::string path; + std::vector> frames = {}; + double frameRate = 30.0f; // Frames per second + size_t currentFrameIndex = 0; + size_t nbFrame = 0; + std::chrono::time_point lastFrameTime; + + size_t currentKeyframeIndex = 0; + std::vector keyframes = {}; + + bool loopVideo = false; + bool isLoaded = false; + + [[nodiscard]] bool loadVideoFrames(const std::string& videoPath); + + void updateFrame(); + + void setCurrentKeyframe(size_t keyframeIndex); + void setCurrentFrame(size_t frameIndex); + + void skipToNextKeyframe(); + void skipToPreviousKeyframe(); + void restartVideo(); + }; +} // namespace nexo::components diff --git a/engine/src/core/event/Event.cpp b/engine/src/core/event/Event.cpp index 7168b43c2..c3e22e2df 100644 --- a/engine/src/core/event/Event.cpp +++ b/engine/src/core/event/Event.cpp @@ -17,28 +17,27 @@ namespace nexo::event { - void EventManager::dispatchEvents() - { - size_t size = m_eventQueue.size(); - while (size--) { - auto event = m_eventQueue.front(); - m_eventQueue.pop_front(); + void EventManager::dispatchEvents() + { + size_t size = m_eventQueue.size(); + while (size--) { + auto event = m_eventQueue.front(); + m_eventQueue.pop_front(); - // Store a reference to the event to avoid evaluating *event with side effects. - const IEvent &ev = *event; - if (const std::type_index typeIndex(typeid(ev)); m_listeners.contains(typeIndex)) { - for (const auto listener : m_listeners[typeIndex]) { - event->trigger(*listener); - if (event->consumed) - break; - } - } - m_eventQueue.push_back(event); - } - } + // Store a reference to the event to avoid evaluating *event with side effects. + const IEvent &ev = *event; + if (const std::type_index typeIndex(typeid(ev)); m_listeners.contains(typeIndex)) { + for (const auto listener : m_listeners[typeIndex]) { + event->trigger(*listener); + if (event->consumed) break; + } + } + m_eventQueue.push_back(event); + } + } void EventManager::clearEvents() { - m_eventQueue.clear(); + m_eventQueue.clear(); } -} +} // namespace nexo::event diff --git a/engine/src/core/event/Event.hpp b/engine/src/core/event/Event.hpp index 801d9ad4b..84cda4946 100644 --- a/engine/src/core/event/Event.hpp +++ b/engine/src/core/event/Event.hpp @@ -14,12 +14,12 @@ #pragma once -#include -#include +#include #include -#include #include -#include +#include +#include +#include #include "Listener.hpp" #include "Logger.hpp" @@ -28,78 +28,81 @@ namespace nexo::scene { class Scene; } -#define LISTENS_TO(...) public event::Listens<__VA_ARGS__> +#define LISTENS_TO(...) \ + public \ + event::Listens<__VA_ARGS__> namespace nexo::event { template - concept HandlesEvent = - std::derived_from && - requires(L* l, E& e) { - { l->handleEvent(e) } -> std::same_as; - }; - - /** - * @brief Base interface for all events. - * - * All events must inherit from this interface and implement the trigger() method - * to notify listeners when the event is emitted. - */ + concept HandlesEvent = std::derived_from && requires(L* l, E& e) { + { + l->handleEvent(e) + } -> std::same_as; + }; + + /** + * @brief Base interface for all events. + * + * All events must inherit from this interface and implement the trigger() method + * to notify listeners when the event is emitted. + */ class IEvent { - public: - virtual ~IEvent() = default; - - /** - * @brief Triggers the event for the provided listener. - * - * Derived event classes must override this method to dispatch the event to the listener. - * - * @param listener The listener to trigger the event on. - */ - virtual void trigger(BaseListener& listener) = 0; - - bool consumed = false; + public: + virtual ~IEvent() = default; + + /** + * @brief Triggers the event for the provided listener. + * + * Derived event classes must override this method to dispatch the event to the listener. + * + * @param listener The listener to trigger the event on. + */ + virtual void trigger(BaseListener& listener) = 0; + + bool consumed = false; }; /** * @brief Templated event class. * - * Implements the trigger() method to dispatch the event to a listener that can handle the specific DerivedEvent type. + * Implements the trigger() method to dispatch the event to a listener that can handle the specific DerivedEvent + * type. * * @tparam DerivedEvent The derived event type. */ template class Event : public IEvent { - public: - /** - * @brief Triggers the event by calling the appropriate listener handler. - * - * Casts the event to the DerivedEvent type and dispatches it using the helper triggerListener(). - * - * @param listener The listener to trigger the event on. - */ - void trigger(BaseListener &listener) override - { - triggerListener(static_cast(*this), listener); - } - protected: - /** - * @brief Helper function to dispatch the event to a listener. - * - * If the listener can handle the event type, its handleEvent() method is called. - * Otherwise, a warning is logged. - * - * @tparam T The event type. - * @param event The event instance. - * @param listener The listener to dispatch the event to. - */ - template - static void triggerListener(T &event, BaseListener &listener) - { - if (auto *p = dynamic_cast*>(&listener)) - return p->handleEvent(event); - LOG(NEXO_WARN, "Event(triggerListener): Listener {} is missing a handler", listener.getListenerName()); - } + public: + /** + * @brief Triggers the event by calling the appropriate listener handler. + * + * Casts the event to the DerivedEvent type and dispatches it using the helper triggerListener(). + * + * @param listener The listener to trigger the event on. + */ + void trigger(BaseListener& listener) override + { + triggerListener(static_cast(*this), listener); + } + + protected: + /** + * @brief Helper function to dispatch the event to a listener. + * + * If the listener can handle the event type, its handleEvent() method is called. + * Otherwise, a warning is logged. + * + * @tparam T The event type. + * @param event The event instance. + * @param listener The listener to dispatch the event to. + */ + template + static void triggerListener(T& event, BaseListener& listener) + { + if (auto* p = dynamic_cast*>(&listener)) return p->handleEvent(event); + LOG(NEXO_WARN, "Event(triggerListener): Listener {} is missing a handler", listener.getListenerName()); + } }; /** @@ -111,19 +114,19 @@ namespace nexo::event { * different components of the ECS system by using an event-driven approach. */ class EventManager { - public: - - /** - * @brief Registers a listener for a specific event type. - * - * Adds the provided listener to the internal list for the given EventType. - * - * @tparam EventType The event type to listen for. - * @param listener Pointer to the listener. - */ - template - requires HandlesEvent - void registerListener(L* listener) { + public: + /** + * @brief Registers a listener for a specific event type. + * + * Adds the provided listener to the internal list for the given EventType. + * + * @tparam EventType The event type to listen for. + * @param listener Pointer to the listener. + */ + template + requires HandlesEvent + void registerListener(L* listener) + { const std::type_index typeIndex(typeid(EventType)); if (!m_listeners.contains(typeIndex)) { m_listeners[typeIndex] = std::vector(); @@ -141,8 +144,9 @@ namespace nexo::event { * @tparam EventType The event type. * @param listener Pointer to the listener. */ - template - void unregisterListener(BaseListener* listener) { + template + void unregisterListener(BaseListener* listener) + { const std::type_index typeIndex(typeid(EventType)); if (const auto it = m_listeners.find(typeIndex); it != m_listeners.end()) { @@ -150,7 +154,8 @@ namespace nexo::event { if (const auto listenerIt = std::ranges::find(listeners, listener); listenerIt != listeners.end()) { listeners.erase(listenerIt); - LOG(NEXO_DEV, "EventManager(unregisterListener): Unregistered listener {}", listener->getListenerName()); + LOG(NEXO_DEV, "EventManager(unregisterListener): Unregistered listener {}", + listener->getListenerName()); if (listeners.empty()) { m_listeners.erase(it); @@ -171,8 +176,9 @@ namespace nexo::event { * @tparam EventType The event type. * @param event Shared pointer to the event. */ - template - void emitEvent(std::shared_ptr event) { + template + void emitEvent(std::shared_ptr event) + { m_eventQueue.push_back(event); } @@ -185,8 +191,9 @@ namespace nexo::event { * @tparam Args The types of arguments to forward. * @param args Arguments to construct the event. */ - template - void emitEvent(Args&&... args) { + template + void emitEvent(Args&&... args) + { emitEvent(std::make_shared(std::forward(args)...)); } @@ -203,8 +210,8 @@ namespace nexo::event { */ void clearEvents(); - private: - std::unordered_map> m_listeners; + private: + std::unordered_map> m_listeners; std::deque> m_eventQueue; }; -} +} // namespace nexo::event diff --git a/engine/src/core/event/Input.cpp b/engine/src/core/event/Input.cpp index 19bd16ac7..4c6e480fd 100644 --- a/engine/src/core/event/Input.cpp +++ b/engine/src/core/event/Input.cpp @@ -17,21 +17,19 @@ #include "opengl/InputOpenGl.hpp" #endif - namespace nexo::event { std::shared_ptr Input::_instance = nullptr; void Input::init(const std::shared_ptr& window) { - if (_instance) - return; + if (_instance) return; - #ifdef NX_GRAPHICS_API_OPENGL - _instance = std::make_shared(window); - #else - THROW_EXCEPTION(renderer::NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + _instance = std::make_shared(window); +#else + THROW_EXCEPTION(renderer::NxUnknownGraphicsApi, "UNKNOWN"); +#endif } -} +} // namespace nexo::event diff --git a/engine/src/core/event/Input.hpp b/engine/src/core/event/Input.hpp index 7bd9828f1..cdda88e3b 100644 --- a/engine/src/core/event/Input.hpp +++ b/engine/src/core/event/Input.hpp @@ -19,102 +19,121 @@ namespace nexo::event { - /** - * @brief Abstract class for input handling. - * - * Provides an interface for querying keyboard and mouse input states. Implementations - * (e.g., for OpenGL using GLFW) must provide concrete behavior for checking key and mouse - * states as well as retrieving the mouse position. - * - * Usage: - * - Call Input::init(window) to initialize the input instance. - * - Use the static inline functions (e.g., isKeyPressed) for convenience. - */ + /** + * @brief Abstract class for input handling. + * + * Provides an interface for querying keyboard and mouse input states. Implementations + * (e.g., for OpenGL using GLFW) must provide concrete behavior for checking key and mouse + * states as well as retrieving the mouse position. + * + * Usage: + * - Call Input::init(window) to initialize the input instance. + * - Use the static inline functions (e.g., isKeyPressed) for convenience. + */ class Input { - public: - virtual ~Input() = default; - - /** - * @brief Constructs an Input instance bound to a specific window. - * - * @param window Shared pointer to the window for input polling. - */ - explicit Input(const std::shared_ptr &window) : m_window(window) {}; - - /** - * @brief Checks if the specified key is currently pressed. - * - * @param keycode The keycode to check. - * @return true if the key is pressed, false otherwise. - */ - [[nodiscard]] virtual bool isKeyPressed(int keycode) const = 0; - - /** - * @brief Checks if the specified key is currently released. - * - * @param keycode The keycode to check. - * @return true if the key is released, false otherwise. - */ - [[nodiscard]] virtual bool isKeyReleased(int keycode) const = 0; - - /** - * @brief Checks if the specified key is in a repeat state. - * - * @param keycode The keycode to check. - * @return true if the key is repeating, false otherwise. - */ - [[nodiscard]] virtual bool isKeyRepeat(int keycode) const = 0; - - /** - * @brief Checks if the specified mouse button is currently pressed. - * - * @param button The mouse button to check. - * @return true if the mouse button is pressed, false otherwise. - */ - [[nodiscard]] virtual bool isMouseDown(int button) const = 0; - - /** - * @brief Checks if the specified mouse button is currently released. - * - * @param button The mouse button to check. - * @return true if the mouse button is released, false otherwise. - */ - [[nodiscard]] virtual bool isMouseReleased(int button) const = 0; - - /** - * @brief Retrieves the current mouse position. - * - * @return glm::vec2 The (x, y) coordinates of the mouse cursor. - */ - [[nodiscard]] virtual glm::vec2 getMousePosition() const = 0; - - static std::shared_ptr getInstance() - { - return _instance; - } - - /** - * @brief Initializes the input system with the given window. - * - * Must be called before any input queries. - * - * @param window Shared pointer to the window used for input. - */ - static void init(const std::shared_ptr& window); - - protected: - std::shared_ptr m_window; - private: - static std::shared_ptr _instance; + public: + virtual ~Input() = default; + + /** + * @brief Constructs an Input instance bound to a specific window. + * + * @param window Shared pointer to the window for input polling. + */ + explicit Input(const std::shared_ptr& window) : m_window(window){}; + + /** + * @brief Checks if the specified key is currently pressed. + * + * @param keycode The keycode to check. + * @return true if the key is pressed, false otherwise. + */ + [[nodiscard]] virtual bool isKeyPressed(int keycode) const = 0; + + /** + * @brief Checks if the specified key is currently released. + * + * @param keycode The keycode to check. + * @return true if the key is released, false otherwise. + */ + [[nodiscard]] virtual bool isKeyReleased(int keycode) const = 0; + + /** + * @brief Checks if the specified key is in a repeat state. + * + * @param keycode The keycode to check. + * @return true if the key is repeating, false otherwise. + */ + [[nodiscard]] virtual bool isKeyRepeat(int keycode) const = 0; + + /** + * @brief Checks if the specified mouse button is currently pressed. + * + * @param button The mouse button to check. + * @return true if the mouse button is pressed, false otherwise. + */ + [[nodiscard]] virtual bool isMouseDown(int button) const = 0; + + /** + * @brief Checks if the specified mouse button is currently released. + * + * @param button The mouse button to check. + * @return true if the mouse button is released, false otherwise. + */ + [[nodiscard]] virtual bool isMouseReleased(int button) const = 0; + + /** + * @brief Retrieves the current mouse position. + * + * @return glm::vec2 The (x, y) coordinates of the mouse cursor. + */ + [[nodiscard]] virtual glm::vec2 getMousePosition() const = 0; + + static std::shared_ptr getInstance() + { + return _instance; + } + + /** + * @brief Initializes the input system with the given window. + * + * Must be called before any input queries. + * + * @param window Shared pointer to the window used for input. + */ + static void init(const std::shared_ptr& window); + + protected: + std::shared_ptr m_window; + + private: + static std::shared_ptr _instance; }; // Inline convenience functions that delegate to the Input singleton. - inline bool isKeyPressed(const int keycode) { return Input::getInstance()->isKeyPressed(keycode); } - inline bool isKeyReleased(const int keycode) { return Input::getInstance()->isKeyReleased(keycode); } - inline bool isKeyRepeat(const int keycode) { return Input::getInstance()->isKeyRepeat(keycode); } - - inline bool isMouseDown(const int button) { return Input::getInstance()->isMouseDown(button); } - inline bool isMouseReleased(const int button) { return Input::getInstance()->isMouseReleased(button); } - - inline glm::vec2 getMousePosition() { return Input::getInstance()->getMousePosition(); } -} + inline bool isKeyPressed(const int keycode) + { + return Input::getInstance()->isKeyPressed(keycode); + } + inline bool isKeyReleased(const int keycode) + { + return Input::getInstance()->isKeyReleased(keycode); + } + inline bool isKeyRepeat(const int keycode) + { + return Input::getInstance()->isKeyRepeat(keycode); + } + + inline bool isMouseDown(const int button) + { + return Input::getInstance()->isMouseDown(button); + } + inline bool isMouseReleased(const int button) + { + return Input::getInstance()->isMouseReleased(button); + } + + inline glm::vec2 getMousePosition() + { + return Input::getInstance()->getMousePosition(); + } +} // namespace nexo::event diff --git a/engine/src/core/event/KeyCodes.hpp b/engine/src/core/event/KeyCodes.hpp index 51a79e803..690e3bbd8 100644 --- a/engine/src/core/event/KeyCodes.hpp +++ b/engine/src/core/event/KeyCodes.hpp @@ -14,32 +14,32 @@ #pragma once namespace nexo::event { - #define NEXO_KEY_SPACE 32 +#define NEXO_KEY_SPACE 32 - #define NEXO_KEY_1 49 - #define NEXO_KEY_2 50 - #define NEXO_KEY_3 51 +#define NEXO_KEY_1 49 +#define NEXO_KEY_2 50 +#define NEXO_KEY_3 51 - #define NEXO_KEY_Q 65 - #define NEXO_KEY_D 68 - #define NEXO_KEY_E 69 - #define NEXO_KEY_I 73 - #define NEXO_KEY_J 74 - #define NEXO_KEY_K 75 - #define NEXO_KEY_L 76 - #define NEXO_KEY_A 81 - #define NEXO_KEY_S 83 - #define NEXO_KEY_Z 87 +#define NEXO_KEY_Q 65 +#define NEXO_KEY_D 68 +#define NEXO_KEY_E 69 +#define NEXO_KEY_I 73 +#define NEXO_KEY_J 74 +#define NEXO_KEY_K 75 +#define NEXO_KEY_L 76 +#define NEXO_KEY_A 81 +#define NEXO_KEY_S 83 +#define NEXO_KEY_Z 87 - #define NEXO_KEY_TAB 258 +#define NEXO_KEY_TAB 258 - #define NEXO_KEY_RIGHT 262 - #define NEXO_KEY_LEFT 263 - #define NEXO_KEY_DOWN 264 - #define NEXO_KEY_UP 265 +#define NEXO_KEY_RIGHT 262 +#define NEXO_KEY_LEFT 263 +#define NEXO_KEY_DOWN 264 +#define NEXO_KEY_UP 265 - #define NEXO_KEY_SHIFT 340 +#define NEXO_KEY_SHIFT 340 - #define NEXO_MOUSE_LEFT 0 - #define NEXO_MOUSE_RIGHT 1 -} +#define NEXO_MOUSE_LEFT 0 +#define NEXO_MOUSE_RIGHT 1 +} // namespace nexo::event diff --git a/engine/src/core/event/Listener.hpp b/engine/src/core/event/Listener.hpp index 778b4c5cb..8220261a5 100644 --- a/engine/src/core/event/Listener.hpp +++ b/engine/src/core/event/Listener.hpp @@ -22,20 +22,24 @@ namespace nexo::event { namespace nexo::event { - /** - * @brief Base class for all event listeners. - * - * BaseListener provides a common interface for all listener objects, storing a name for identification. - */ + /** + * @brief Base class for all event listeners. + * + * BaseListener provides a common interface for all listener objects, storing a name for identification. + */ class BaseListener { - public: - BaseListener() = default; - explicit BaseListener(std::string name) : m_name(std::move(name)) {}; - virtual ~BaseListener() = default; + public: + BaseListener() = default; + explicit BaseListener(std::string name) : m_name(std::move(name)){}; + virtual ~BaseListener() = default; - [[nodiscard]] std::string getListenerName() const {return m_name; }; - private: - std::string m_name; + [[nodiscard]] std::string getListenerName() const + { + return m_name; + }; + + private: + std::string m_name; }; /** @@ -48,17 +52,17 @@ namespace nexo::event { */ template class Listener { - public: - virtual ~Listener() = default; + public: + virtual ~Listener() = default; - /** - * @brief Handles an event of type T. - * - * This method should be overridden by derived classes to process events of the specified type. - * - * @param event The event to handle. - */ - virtual void handleEvent(T &event) = 0; + /** + * @brief Handles an event of type T. + * + * This method should be overridden by derived classes to process events of the specified type. + * + * @param event The event to handle. + */ + virtual void handleEvent(T &event) = 0; }; /** @@ -71,7 +75,7 @@ namespace nexo::event { */ template class Listens : public BaseListener, public Listener... { - public: - explicit Listens(const std::string &name = "") : BaseListener(name) {}; + public: + explicit Listens(const std::string &name = "") : BaseListener(name){}; }; -} +} // namespace nexo::event diff --git a/engine/src/core/event/SignalEvent.cpp b/engine/src/core/event/SignalEvent.cpp index 7cf6e7fa0..c821749f5 100644 --- a/engine/src/core/event/SignalEvent.cpp +++ b/engine/src/core/event/SignalEvent.cpp @@ -19,8 +19,7 @@ namespace nexo::event { void SignalHandler::signalHandler(const int signal) { emitEventToAll(signal); - switch (signal) - { + switch (signal) { case SIGTERM: emitEventToAll(); break; @@ -39,10 +38,9 @@ namespace nexo::event { } template - void SignalHandler::emitEventToAll(Args &&... args) + void SignalHandler::emitEventToAll(Args &&...args) { - for (const auto &eventManager: getInstance()->m_eventManagers) - { + for (const auto &eventManager : getInstance()->m_eventManagers) { eventManager->emitEvent(std::forward(args)...); } } @@ -59,8 +57,7 @@ namespace nexo::event { std::shared_ptr SignalHandler::getInstance() { - if (!s_instance) - s_instance = std::make_shared(); + if (!s_instance) s_instance = std::make_shared(); return s_instance; } diff --git a/engine/src/core/event/SignalEvent.hpp b/engine/src/core/event/SignalEvent.hpp index cc4ef58fb..2c845345a 100644 --- a/engine/src/core/event/SignalEvent.hpp +++ b/engine/src/core/event/SignalEvent.hpp @@ -22,15 +22,15 @@ namespace nexo::event { class EventAnySignal final : public Event { - public: - explicit EventAnySignal(const int signal) : signal(signal) {}; - int signal; - - friend std::ostream &operator<<(std::ostream &os, const EventAnySignal &event) - { - os << "[EventAnySignal] Signal : " << utils::strsignal(event.signal) << " (" << event.signal << ")"; - return os; - } + public: + explicit EventAnySignal(const int signal) : signal(signal){}; + int signal; + + friend std::ostream &operator<<(std::ostream &os, const EventAnySignal &event) + { + os << "[EventAnySignal] Signal : " << utils::strsignal(event.signal) << " (" << event.signal << ")"; + return os; + } }; /** @@ -51,42 +51,41 @@ namespace nexo::event { // Init signal handling class SignalHandler { - public: - SignalHandler(); - - ~SignalHandler() = default; - - // Singleton - SignalHandler(SignalHandler const &) = delete; + public: + SignalHandler(); - void operator=(SignalHandler const &) = delete; + ~SignalHandler() = default; - void registerEventManager(std::shared_ptr eventManager); + // Singleton + SignalHandler(SignalHandler const &) = delete; - /** - * @brief Register a signal to be handled - * @note This method is in public to allow custom signal handling, in this case, the signal will be emitted as an EventSignal - */ - void registerSignal(int signal); + void operator=(SignalHandler const &) = delete; - static std::shared_ptr getInstance(); + void registerEventManager(std::shared_ptr eventManager); - void initSignals() const; + /** + * @brief Register a signal to be handled + * @note This method is in public to allow custom signal handling, in this case, the signal will be emitted as + * an EventSignal + */ + void registerSignal(int signal); - private: - static void signalHandler(int signal); + static std::shared_ptr getInstance(); - static void defaultSignalHandler(int signal); + void initSignals() const; - template - static void emitEventToAll(Args &&... args); + private: + static void signalHandler(int signal); + static void defaultSignalHandler(int signal); + template + static void emitEventToAll(Args &&...args); - std::vector > m_eventManagers; + std::vector > m_eventManagers; - // Singleton - inline static std::shared_ptr s_instance = nullptr; + // Singleton + inline static std::shared_ptr s_instance = nullptr; }; } // namespace nexo::event diff --git a/engine/src/core/event/Signals.hpp b/engine/src/core/event/Signals.hpp index 63b1790f9..7839eecd2 100644 --- a/engine/src/core/event/Signals.hpp +++ b/engine/src/core/event/Signals.hpp @@ -15,7 +15,7 @@ #pragma once #include -#include +#include // needed for linux namespace nexo::utils { @@ -23,13 +23,20 @@ namespace nexo::utils { constexpr const char *strsignal(const int signal) { switch (signal) { - case SIGABRT: return "SIGABRT"; - case SIGFPE: return "SIGFPE"; - case SIGILL: return "SIGILL"; - case SIGINT: return "SIGINT"; - case SIGSEGV: return "SIGSEGV"; - case SIGTERM: return "SIGTERM"; - default: return "UNKNOWN"; + case SIGABRT: + return "SIGABRT"; + case SIGFPE: + return "SIGFPE"; + case SIGILL: + return "SIGILL"; + case SIGINT: + return "SIGINT"; + case SIGSEGV: + return "SIGSEGV"; + case SIGTERM: + return "SIGTERM"; + default: + return "UNKNOWN"; } } #else @@ -39,4 +46,4 @@ namespace nexo::utils { } #endif -} +} // namespace nexo::utils diff --git a/engine/src/core/event/WindowEvent.cpp b/engine/src/core/event/WindowEvent.cpp index e3542ec86..7d9c27410 100644 --- a/engine/src/core/event/WindowEvent.cpp +++ b/engine/src/core/event/WindowEvent.cpp @@ -17,39 +17,62 @@ namespace nexo::event { std::ostream& operator<<(std::ostream& os, const KeyAction& action) { - switch (action) - { - case PRESSED: os << "PRESSED"; break; - case RELEASED: os << "RELEASED"; break; - case REPEAT: os << "REPEAT"; break; - default: os << "UNKNOWN_ACTION"; break; + switch (action) { + case PRESSED: + os << "PRESSED"; + break; + case RELEASED: + os << "RELEASED"; + break; + case REPEAT: + os << "REPEAT"; + break; + default: + os << "UNKNOWN_ACTION"; + break; } return os; } std::ostream& operator<<(std::ostream& os, const KeyMods& mod) { - switch (mod) - { - case KeyMods::NONE: os << "NONE"; break; - case KeyMods::SHIFT: os << "SHIFT"; break; - case KeyMods::CONTROL: os << "CONTROL"; break; - case KeyMods::ALT: os << "ALT"; break; - default: os << "UNKNOWN_MOD"; break; + switch (mod) { + case KeyMods::NONE: + os << "NONE"; + break; + case KeyMods::SHIFT: + os << "SHIFT"; + break; + case KeyMods::CONTROL: + os << "CONTROL"; + break; + case KeyMods::ALT: + os << "ALT"; + break; + default: + os << "UNKNOWN_MOD"; + break; } return os; } std::ostream& operator<<(std::ostream& os, const MouseButton& button) { - switch (button) - { - case LEFT: os << "LEFT"; break; - case RIGHT: os << "RIGHT"; break; - case MIDDLE: os << "MIDDLE"; break; - default: os << "UNKNOWN_MOD"; break; + switch (button) { + case LEFT: + os << "LEFT"; + break; + case RIGHT: + os << "RIGHT"; + break; + case MIDDLE: + os << "MIDDLE"; + break; + default: + os << "UNKNOWN_MOD"; + break; } return os; } -} +} // namespace nexo::event diff --git a/engine/src/core/event/WindowEvent.hpp b/engine/src/core/event/WindowEvent.hpp index 73d8fc66a..4acc095c4 100644 --- a/engine/src/core/event/WindowEvent.hpp +++ b/engine/src/core/event/WindowEvent.hpp @@ -16,7 +16,6 @@ #include #define GLFW_INCLUDE_NONE #include -#include #include "Event.hpp" @@ -25,24 +24,20 @@ namespace nexo::event { class EventWindowClose final : public Event {}; class EventWindowResize final : public Event { - public: - EventWindowResize(const int width, const int height) : width(width), height(height) {}; + public: + EventWindowResize(const int width, const int height) : width(width), height(height){}; - int width; - int height; + int width; + int height; - friend std::ostream &operator<<(std::ostream &os, const EventWindowResize &event) - { - os << "[RESIZE WINDOW EVENT]: " << event.width << "x" << event.height; - return os; - } + friend std::ostream &operator<<(std::ostream &os, const EventWindowResize &event) + { + os << "[RESIZE WINDOW EVENT]: " << event.width << "x" << event.height; + return os; + } }; - enum KeyAction { - PRESSED, - RELEASED, - REPEAT - }; + enum KeyAction { PRESSED, RELEASED, REPEAT }; std::ostream &operator<<(std::ostream &os, const KeyAction &action); @@ -56,128 +51,114 @@ namespace nexo::event { std::ostream &operator<<(std::ostream &os, const KeyMods &mod); class EventKey final : public Event { - public: - EventKey() = default; - - EventKey(const int keycode, const KeyAction action, const int mods) : keycode(keycode), action(action), - mods(mods) {}; - - [[nodiscard]] bool hasMod(KeyMods mod) const - { - return (mods & static_cast(mod)); + public: + EventKey() = default; + + EventKey(const int keycode, const KeyAction action, const int mods) + : keycode(keycode), action(action), mods(mods){}; + + [[nodiscard]] bool hasMod(KeyMods mod) const + { + return (mods & static_cast(mod)); + } + + int keycode{}; + KeyAction action{}; + int mods{}; + + friend std::ostream &operator<<(std::ostream &os, const EventKey &event) + { + std::string mod; + if (event.hasMod(KeyMods::ALT)) mod.append("ALT"); + if (event.hasMod(KeyMods::CONTROL)) { + if (!mod.empty()) mod.append(" + "); + mod.append("CTRL"); } - - int keycode{}; - KeyAction action{}; - int mods{}; - - friend std::ostream &operator<<(std::ostream &os, const EventKey &event) - { - std::string mod; - if (event.hasMod(KeyMods::ALT)) - mod.append("ALT"); - if (event.hasMod(KeyMods::CONTROL)) - { - if (!mod.empty()) - mod.append(" + "); - mod.append("CTRL"); - } - if (event.hasMod(KeyMods::SHIFT)) - { - if (!mod.empty()) - mod.append(" + "); - mod.append("SHIFT"); - } - os << "[KEYBOARD EVENT] : " << event.keycode << " with action : " << event.action << " " << mod; - return os; + if (event.hasMod(KeyMods::SHIFT)) { + if (!mod.empty()) mod.append(" + "); + mod.append("SHIFT"); } + os << "[KEYBOARD EVENT] : " << event.keycode << " with action : " << event.action << " " << mod; + return os; + } }; - enum MouseButton { - LEFT = 0, - RIGHT = 1, - MIDDLE = 2 - }; + enum MouseButton { LEFT = 0, RIGHT = 1, MIDDLE = 2 }; std::ostream &operator<<(std::ostream &os, const MouseButton &button); class EventMouseClick final : public Event { - public: - EventMouseClick() = default; - - [[nodiscard]] bool hasMod(KeyMods mod) const - { - return (mods & static_cast(mod)); + public: + EventMouseClick() = default; + + [[nodiscard]] bool hasMod(KeyMods mod) const + { + return (mods & static_cast(mod)); + } + + MouseButton button{}; + KeyAction action{}; + int mods{}; + + friend std::ostream &operator<<(std::ostream &os, const EventMouseClick &event) + { + std::string mod; + if (event.hasMod(KeyMods::ALT)) mod.append("ALT"); + if (event.hasMod(KeyMods::CONTROL)) { + if (!mod.empty()) mod.append(" + "); + mod.append("CTRL"); } - - MouseButton button{}; - KeyAction action{}; - int mods{}; - - friend std::ostream &operator<<(std::ostream &os, const EventMouseClick &event) - { - std::string mod; - if (event.hasMod(KeyMods::ALT)) - mod.append("ALT"); - if (event.hasMod(KeyMods::CONTROL)) - { - if (!mod.empty()) - mod.append(" + "); - mod.append("CTRL"); - } - if (event.hasMod(KeyMods::SHIFT)) - { - if (!mod.empty()) - mod.append(" + "); - mod.append("SHIFT"); - } - os << "[MOUSE BUTTON EVENT] : " << event.button << " with action : " << event.action << " " << mod; - return os; + if (event.hasMod(KeyMods::SHIFT)) { + if (!mod.empty()) mod.append(" + "); + mod.append("SHIFT"); } + os << "[MOUSE BUTTON EVENT] : " << event.button << " with action : " << event.action << " " << mod; + return os; + } }; class EventMouseScroll final : public Event { - public: - EventMouseScroll(const float xOffset, const float yOffset) : x(xOffset), y(yOffset) {}; + public: + EventMouseScroll(const float xOffset, const float yOffset) : x(xOffset), y(yOffset){}; - float x; - float y; + float x; + float y; - friend std::ostream &operator<<(std::ostream &os, const EventMouseScroll &scroll) - { - os << "[MOUSE SCROLL EVENT] xOffset : " << scroll.x << " yOffset : " << scroll.y; - return os; - } + friend std::ostream &operator<<(std::ostream &os, const EventMouseScroll &scroll) + { + os << "[MOUSE SCROLL EVENT] xOffset : " << scroll.x << " yOffset : " << scroll.y; + return os; + } }; class EventMouseMove : public Event { - public: - EventMouseMove(const float xpos, const float ypos) : x(xpos), y(ypos) {}; + public: + EventMouseMove(const float xpos, const float ypos) : x(xpos), y(ypos){}; - float x; - float y; + float x; + float y; - friend std::ostream &operator<<(std::ostream &os, const EventMouseMove &mouse) - { - os << "[MOUSE MOVE EVENT] x : " << mouse.x << " y : " << mouse.y; - return os; - } + friend std::ostream &operator<<(std::ostream &os, const EventMouseMove &mouse) + { + os << "[MOUSE MOVE EVENT] x : " << mouse.x << " y : " << mouse.y; + return os; + } }; class EventFileDrop final : public Event { - public: - EventFileDrop(const std::vector& droppedFiles) : files(droppedFiles) {}; - - std::vector files; - - friend std::ostream &operator<<(std::ostream &os, const EventFileDrop &event) - { - os << "[FILE DROP EVENT] " << event.files.size() << " file(s): "; - for (size_t i = 0; i < event.files.size(); ++i) { - if (i > 0) os << ", "; - os << event.files[i]; - } - return os; + public: + EventFileDrop(const std::vector &droppedFiles) : files(droppedFiles){}; + + std::vector files; + + friend std::ostream &operator<<(std::ostream &os, const EventFileDrop &event) + { + os << "[FILE DROP EVENT] " << event.files.size() << " file(s): "; + for (size_t i = 0; i < event.files.size(); ++i) { + if (i > 0) os << ", "; + os << event.files[i]; } + return os; + } }; -} +} // namespace nexo::event diff --git a/engine/src/core/event/opengl/InputOpenGl.cpp b/engine/src/core/event/opengl/InputOpenGl.cpp index 13b801480..f5772204f 100644 --- a/engine/src/core/event/opengl/InputOpenGl.cpp +++ b/engine/src/core/event/opengl/InputOpenGl.cpp @@ -48,4 +48,4 @@ namespace nexo::event { return {static_cast(xpos), static_cast(ypos)}; } -} +} // namespace nexo::event diff --git a/engine/src/core/event/opengl/InputOpenGl.hpp b/engine/src/core/event/opengl/InputOpenGl.hpp index 655375342..47d92ee1d 100644 --- a/engine/src/core/event/opengl/InputOpenGl.hpp +++ b/engine/src/core/event/opengl/InputOpenGl.hpp @@ -13,12 +13,12 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "core/event/Input.hpp" #include "Logger.hpp" +#include "core/event/Input.hpp" namespace nexo::event { class InputOpenGl final : public Input { - public: + public: explicit InputOpenGl(const std::shared_ptr& window) : Input(window) { LOG(NEXO_DEV, "Opengl input handler initialized"); @@ -33,4 +33,4 @@ namespace nexo::event { [[nodiscard]] glm::vec2 getMousePosition() const override; }; -} \ No newline at end of file +} // namespace nexo::event diff --git a/engine/src/core/exceptions/Exceptions.hpp b/engine/src/core/exceptions/Exceptions.hpp index 3ab8870a0..1a8606fc6 100644 --- a/engine/src/core/exceptions/Exceptions.hpp +++ b/engine/src/core/exceptions/Exceptions.hpp @@ -13,46 +13,54 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include -#include #include +#include +#include #include "Exception.hpp" #include "components/Light.hpp" namespace nexo::core { class FileNotFoundException final : public Exception { - public: - explicit FileNotFoundException(const std::string &filePath, - const std::source_location loc = std::source_location::current()) - : Exception("File not found: " + filePath, loc) {} + public: + explicit FileNotFoundException(const std::string &filePath, + const std::source_location loc = std::source_location::current()) + : Exception("File not found: " + filePath, loc) + {} }; class LoadModelException final : public Exception { - public: - explicit LoadModelException(const std::string &filePath, const std::string &errorStr, - const std::source_location loc = std::source_location::current()) : Exception( - "Failure to load model : " + filePath + " : " + errorStr, loc) {}; + public: + explicit LoadModelException(const std::string &filePath, const std::string &errorStr, + const std::source_location loc = std::source_location::current()) + : Exception("Failure to load model : " + filePath + " : " + errorStr, loc){}; }; - class SceneManagerLifecycleException : public Exception { - public: - explicit SceneManagerLifecycleException(const std::string &message, - const std::source_location loc = std::source_location::current()) - : Exception(message, loc) {} + class SceneManagerLifecycleException final : public Exception { + public: + explicit SceneManagerLifecycleException(const std::string &message, + const std::source_location loc = std::source_location::current()) + : Exception(message, loc) + {} }; - class TooManyPointLightsException : public Exception { - public: - explicit TooManyPointLightsException(unsigned int sceneRendered, size_t nbPointLights, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Too many point lights ({} > {}) in scene [{}]", nbPointLights, MAX_POINT_LIGHTS, sceneRendered), loc) {} + class TooManyPointLightsException final : public Exception { + public: + explicit TooManyPointLightsException(unsigned int sceneRendered, size_t nbPointLights, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Too many point lights ({} > {}) in scene [{}]", nbPointLights, MAX_POINT_LIGHTS, + sceneRendered), + loc) + {} }; - class TooManySpotLightsException : public Exception { - public: - explicit TooManySpotLightsException(unsigned int sceneRendered, size_t nbSpotLights, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Too many spot lights ({} > {}) in scene [{}]", nbSpotLights, MAX_SPOT_LIGHTS, sceneRendered), loc) {} + class TooManySpotLightsException final : public Exception { + public: + explicit TooManySpotLightsException(unsigned int sceneRendered, size_t nbSpotLights, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Too many spot lights ({} > {}) in scene [{}]", nbSpotLights, MAX_SPOT_LIGHTS, + sceneRendered), + loc) + {} }; -} +} // namespace nexo::core diff --git a/engine/src/core/scene/Scene.cpp b/engine/src/core/scene/Scene.cpp index c33a5ffa8..4c71dccb9 100644 --- a/engine/src/core/scene/Scene.cpp +++ b/engine/src/core/scene/Scene.cpp @@ -21,20 +21,20 @@ #include "components/Uuid.hpp" namespace nexo::scene { - Scene::Scene(std::string sceneName, const std::shared_ptr &coordinator, const bool editorOnly) : m_sceneName(std::move(sceneName)), m_coordinator(coordinator), isEditor(editorOnly) - { - m_uuid = components::genUuid(); - } + Scene::Scene(std::string sceneName, const std::shared_ptr &coordinator, const bool editorOnly) + : m_sceneName(std::move(sceneName)), m_coordinator(coordinator), isEditor(editorOnly) + { + m_uuid = components::genUuid(); + } - Scene::~Scene() - { - for (const ecs::Entity entity : m_entities) - { - m_coordinator->destroyEntity(entity); - } - } + Scene::~Scene() + { + for (const ecs::Entity entity : m_entities) { + m_coordinator->destroyEntity(entity); + } + } - void Scene::addEntity(const ecs::Entity entity) + void Scene::addEntity(const ecs::Entity entity) { const components::SceneTag tag{m_id, true, true}; m_coordinator->addComponent(entity, tag); @@ -44,15 +44,13 @@ namespace nexo::scene { if (m_coordinator->entityHasComponent(entity)) { const auto &transform = m_coordinator->getComponent(entity); - for (const auto &childEntity : transform.children) - addChildEntityToScene(childEntity); + for (const auto &childEntity : transform.children) addChildEntityToScene(childEntity); } } void Scene::addChildEntityToScene(const ecs::Entity entity) { - if (m_entities.contains(entity)) - return; + if (m_entities.contains(entity)) return; // Add scene tag to this child entity const components::SceneTag tag{m_id, true, true}; @@ -63,36 +61,31 @@ namespace nexo::scene { if (m_coordinator->entityHasComponent(entity)) { const auto &transform = m_coordinator->getComponent(entity); - for (const auto &childEntity : transform.children) - addChildEntityToScene(childEntity); + for (const auto &childEntity : transform.children) addChildEntityToScene(childEntity); } } - void Scene::removeEntity(const ecs::Entity entity) - { - m_coordinator->removeComponent(entity); - m_entities.erase(entity); - } + void Scene::removeEntity(const ecs::Entity entity) + { + m_coordinator->removeComponent(entity); + m_entities.erase(entity); + } - void Scene::setActiveStatus(const bool active) - { - m_active = active; - for (const ecs::Entity entity : m_entities) - { - auto tag = m_coordinator->tryGetComponent(entity); - if (tag) - tag->get().isActive = active; - } - } + void Scene::setActiveStatus(const bool active) + { + m_active = active; + for (const ecs::Entity entity : m_entities) { + auto tag = m_coordinator->tryGetComponent(entity); + if (tag) tag->get().isActive = active; + } + } - void Scene::setRenderStatus(const bool rendered) - { - m_rendered = rendered; - for (const ecs::Entity entity : m_entities) - { - auto tag = m_coordinator->tryGetComponent(entity); - if (tag) - tag->get().isRendered = rendered; - } - } -} + void Scene::setRenderStatus(const bool rendered) + { + m_rendered = rendered; + for (const ecs::Entity entity : m_entities) { + auto tag = m_coordinator->tryGetComponent(entity); + if (tag) tag->get().isRendered = rendered; + } + } +} // namespace nexo::scene diff --git a/engine/src/core/scene/Scene.hpp b/engine/src/core/scene/Scene.hpp index eeae5df50..9ec58bf06 100644 --- a/engine/src/core/scene/Scene.hpp +++ b/engine/src/core/scene/Scene.hpp @@ -13,96 +13,118 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "ecs/Coordinator.hpp" -#include "components/Model.hpp" #include +#include "components/Model.hpp" +#include "ecs/Coordinator.hpp" namespace nexo::scene { - inline unsigned int nextSceneId = 0; + inline unsigned int nextSceneId = 0; + + /** + * @class Scene + * @brief Represents a scene in the engine. + * + * A Scene encapsulates a collection of entities, a unique identifier, a name, + * and active/rendered states. It manages its entities via an ECS Coordinator. + * + * Responsibilities: + * - Adding and removing entities. + * - Managing active and rendered states for the scene and its entities. + * - Providing access to scene metadata (name, ID, UUID). + */ + class Scene { + public: + /** + * @brief Constructs a new Scene. + * + * Initializes the scene with the specified name and ECS coordinator. + * Optionally marks the scene as editor-only. + * + * @param sceneName The name of the scene. + * @param coordinator Shared pointer to the ECS Coordinator managing entities/components. + * @param editorOnly If true, the scene is marked as editor-only. + */ + Scene(std::string sceneName, const std::shared_ptr& coordinator, bool editorOnly = false); + ~Scene(); - /** - * @class Scene - * @brief Represents a scene in the engine. - * - * A Scene encapsulates a collection of entities, a unique identifier, a name, - * and active/rendered states. It manages its entities via an ECS Coordinator. - * - * Responsibilities: - * - Adding and removing entities. - * - Managing active and rendered states for the scene and its entities. - * - Providing access to scene metadata (name, ID, UUID). - */ - class Scene { - public: - /** - * @brief Constructs a new Scene. - * - * Initializes the scene with the specified name and ECS coordinator. - * Optionally marks the scene as editor-only. - * - * @param sceneName The name of the scene. - * @param coordinator Shared pointer to the ECS Coordinator managing entities/components. - * @param editorOnly If true, the scene is marked as editor-only. - */ - Scene(std::string sceneName, const std::shared_ptr& coordinator, bool editorOnly = false); - ~Scene(); + /** + * @brief Adds an entity to the scene. + * + * Attaches a SceneTag component (with the scene ID and default active/rendered state) + * to the entity and stores it in the scene's entity set. + * + * @param entity The entity identifier to add. + */ + void addEntity(ecs::Entity entity); + void addChildEntityToScene(ecs::Entity entity); - /** - * @brief Adds an entity to the scene. - * - * Attaches a SceneTag component (with the scene ID and default active/rendered state) - * to the entity and stores it in the scene's entity set. - * - * @param entity The entity identifier to add. - */ - void addEntity(ecs::Entity entity); - void addChildEntityToScene(ecs::Entity entity); + /** + * @brief Removes an entity from the scene. + * + * Detaches the SceneTag component from the entity and removes it from the scene's entity set. + * + * @param entity The entity identifier to remove. + */ + void removeEntity(ecs::Entity entity); - /** - * @brief Removes an entity from the scene. - * - * Detaches the SceneTag component from the entity and removes it from the scene's entity set. - * - * @param entity The entity identifier to remove. - */ - void removeEntity(ecs::Entity entity); + /** + * @brief Sets the active status for the scene. + * + * Updates the scene's active state and propagates the change to all entities by updating + * their SceneTag components. + * + * @param active True to mark the scene as active, false to deactivate. + */ + void setActiveStatus(bool active); + [[nodiscard]] bool isActive() const + { + return m_active; + } - /** - * @brief Sets the active status for the scene. - * - * Updates the scene's active state and propagates the change to all entities by updating - * their SceneTag components. - * - * @param active True to mark the scene as active, false to deactivate. - */ - void setActiveStatus(bool active); - [[nodiscard]] bool isActive() const { return m_active; } + /** + * @brief Sets the rendered status for the scene. + * + * Updates the scene's rendered state and propagates the change to all entities by updating + * their SceneTag components. + * + * @param rendered True to mark the scene as rendered, false to not render. + */ + void setRenderStatus(bool rendered); + [[nodiscard]] bool isRendered() const + { + return m_rendered; + } - /** - * @brief Sets the rendered status for the scene. - * - * Updates the scene's rendered state and propagates the change to all entities by updating - * their SceneTag components. - * - * @param rendered True to mark the scene as rendered, false to not render. - */ - void setRenderStatus(bool rendered); - [[nodiscard]] bool isRendered() const { return m_rendered; } + [[nodiscard]] const std::string& getName() const + { + return m_sceneName; + }; + void setName(const std::string_view newName) + { + m_sceneName = newName; + } + [[nodiscard]] unsigned int getId() const + { + return m_id; + }; + [[nodiscard]] const std::string& getUuid() const + { + return m_uuid; + } + [[nodiscard]] const std::set& getEntities() const + { + return m_entities; + }; - [[nodiscard]] const std::string& getName() const {return m_sceneName;}; - void setName(const std::string_view newName) { m_sceneName = newName; } - [[nodiscard]] unsigned int getId() const {return m_id;}; - [[nodiscard]] const std::string &getUuid() const {return m_uuid;} - [[nodiscard]] const std::set &getEntities() const {return m_entities;}; - private: - unsigned int m_id = nextSceneId++; - std::string m_sceneName; - std::string m_uuid; - std::set m_entities; - std::shared_ptr m_coordinator; + private: + unsigned int m_id = nextSceneId++; + std::string m_sceneName; + std::string m_uuid; + std::set m_entities; + std::shared_ptr m_coordinator; - bool m_active = true; - bool m_rendered = true; - bool isEditor = false; - }; -} + bool m_active = true; + bool m_rendered = true; + bool isEditor = false; + }; +} // namespace nexo::scene diff --git a/engine/src/core/scene/SceneManager.cpp b/engine/src/core/scene/SceneManager.cpp index 18c3ace63..c246f8879 100644 --- a/engine/src/core/scene/SceneManager.cpp +++ b/engine/src/core/scene/SceneManager.cpp @@ -13,46 +13,44 @@ /////////////////////////////////////////////////////////////////////////////// #include "SceneManager.hpp" +#include #include "Exception.hpp" #include "core/exceptions/Exceptions.hpp" -#include namespace nexo::scene { - SceneManager::SceneManager() = default; - - void SceneManager::setCoordinator(const std::shared_ptr &coordinator) - { - m_coordinator = coordinator; - } - - unsigned int SceneManager::createScene(const std::string &name) - { - if (!m_coordinator) - THROW_EXCEPTION(core::SceneManagerLifecycleException, "Coordinator is not set"); - Scene newScene = {name, m_coordinator}; - m_scenes.try_emplace(newScene.getId(), newScene); - - return newScene.getId(); - } - - unsigned int SceneManager::createEditorScene(const std::string &name) - { - if (!m_coordinator) - THROW_EXCEPTION(core::SceneManagerLifecycleException, "Coordinator is not set"); - Scene newScene = {name, m_coordinator, true}; - m_scenes.try_emplace(newScene.getId(), newScene); - - return newScene.getId(); - } - - void SceneManager::deleteScene(const unsigned int id) - { - m_scenes.erase(id); - } - - Scene &SceneManager::getScene(const unsigned int id) - { - return m_scenes.at(id); - } - -} + SceneManager::SceneManager() = default; + + void SceneManager::setCoordinator(const std::shared_ptr &coordinator) + { + m_coordinator = coordinator; + } + + unsigned int SceneManager::createScene(const std::string &name) + { + if (!m_coordinator) THROW_EXCEPTION(core::SceneManagerLifecycleException, "Coordinator is not set"); + Scene newScene = {name, m_coordinator}; + m_scenes.try_emplace(newScene.getId(), newScene); + + return newScene.getId(); + } + + unsigned int SceneManager::createEditorScene(const std::string &name) + { + if (!m_coordinator) THROW_EXCEPTION(core::SceneManagerLifecycleException, "Coordinator is not set"); + Scene newScene = {name, m_coordinator, true}; + m_scenes.try_emplace(newScene.getId(), newScene); + + return newScene.getId(); + } + + void SceneManager::deleteScene(const unsigned int id) + { + m_scenes.erase(id); + } + + Scene &SceneManager::getScene(const unsigned int id) + { + return m_scenes.at(id); + } + +} // namespace nexo::scene diff --git a/engine/src/core/scene/SceneManager.hpp b/engine/src/core/scene/SceneManager.hpp index 768a26eea..d0e164d93 100644 --- a/engine/src/core/scene/SceneManager.hpp +++ b/engine/src/core/scene/SceneManager.hpp @@ -13,83 +13,82 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "ecs/Coordinator.hpp" #include "Scene.hpp" +#include "ecs/Coordinator.hpp" namespace nexo::scene { - using SceneId = unsigned int; - - /** - * @class SceneManager - * @brief Manages multiple scenes within the engine. - * - * The SceneManager is responsible for creating, storing, and deleting scenes. - * It also provides access to individual scenes by their unique ID. - * - * Responsibilities: - * - Creating both runtime and editor-only scenes. - * - Deleting scenes. - * - Providing access to scenes by ID. - * - * Note: A valid ECS Coordinator must be set via setCoordinator() before creating scenes. - */ - class SceneManager { - public: - SceneManager(); + using SceneId = unsigned int; - /** - * @brief Sets the ECS Coordinator used for scene entity management. - * - * @param coordinator Shared pointer to the ECS Coordinator. - */ - void setCoordinator(const std::shared_ptr &coordinator); + /** + * @class SceneManager + * @brief Manages multiple scenes within the engine. + * + * The SceneManager is responsible for creating, storing, and deleting scenes. + * It also provides access to individual scenes by their unique ID. + * + * Responsibilities: + * - Creating both runtime and editor-only scenes. + * - Deleting scenes. + * - Providing access to scenes by ID. + * + * Note: A valid ECS Coordinator must be set via setCoordinator() before creating scenes. + */ + class SceneManager { + public: + SceneManager(); - /** - * @brief Creates a new runtime scene. - * - * Constructs a new scene with the given name and registers it with the manager. - * - * @param name The name for the new scene. - * @return unsigned int The unique ID of the created scene. - * - * @throws core::SceneManagerLifecycleException if the coordinator is not set. - */ - unsigned int createScene(const std::string &name); + /** + * @brief Sets the ECS Coordinator used for scene entity management. + * + * @param coordinator Shared pointer to the ECS Coordinator. + */ + void setCoordinator(const std::shared_ptr &coordinator); - /** - * @brief Creates a new editor-only scene. - * - * Constructs a new scene intended only for the editor with the given name and registers it. - * - * @param name The name for the new editor scene. - * @return unsigned int The unique ID of the created editor scene. - * - * @throws core::SceneManagerLifecycleException if the coordinator is not set. - */ - unsigned int createEditorScene(const std::string &name); + /** + * @brief Creates a new runtime scene. + * + * Constructs a new scene with the given name and registers it with the manager. + * + * @param name The name for the new scene. + * @return unsigned int The unique ID of the created scene. + * + * @throws core::SceneManagerLifecycleException if the coordinator is not set. + */ + unsigned int createScene(const std::string &name); - /** - * @brief Deletes a scene by its unique ID. - * - * Removes the scene from the manager. - * - * @param id The unique ID of the scene to delete. - */ - void deleteScene(unsigned int id); + /** + * @brief Creates a new editor-only scene. + * + * Constructs a new scene intended only for the editor with the given name and registers it. + * + * @param name The name for the new editor scene. + * @return unsigned int The unique ID of the created editor scene. + * + * @throws core::SceneManagerLifecycleException if the coordinator is not set. + */ + unsigned int createEditorScene(const std::string &name); - /** - * @brief Retrieves a scene by its unique ID. - * - * @param id The unique ID of the scene. - * @return Scene& Reference to the scene. - * - * @throws std::out_of_range if the scene is not found. - */ - Scene &getScene(unsigned int id); + /** + * @brief Deletes a scene by its unique ID. + * + * Removes the scene from the manager. + * + * @param id The unique ID of the scene to delete. + */ + void deleteScene(unsigned int id); - private: - std::shared_ptr m_coordinator = nullptr; - std::unordered_map m_scenes; + /** + * @brief Retrieves a scene by its unique ID. + * + * @param id The unique ID of the scene. + * @return Scene& Reference to the scene. + * + * @throws std::out_of_range if the scene is not found. + */ + Scene &getScene(unsigned int id); - }; -} + private: + std::shared_ptr m_coordinator = nullptr; + std::unordered_map m_scenes; + }; +} // namespace nexo::scene diff --git a/engine/src/ecs/Access.hpp b/engine/src/ecs/Access.hpp index 7ed05c2a0..eee2ccb61 100644 --- a/engine/src/ecs/Access.hpp +++ b/engine/src/ecs/Access.hpp @@ -21,8 +21,8 @@ namespace nexo::ecs { * @brief Access type for components in systems */ enum class AccessType { - Read, ///< Read-only access - Write ///< Read-write access + Read, ///< Read-only access + Write ///< Read-write access }; /** @@ -33,7 +33,7 @@ namespace nexo::ecs { */ template struct ComponentAccess { - using ComponentType = T; + using ComponentType = T; static constexpr AccessType accessType = Access; }; @@ -54,7 +54,7 @@ namespace nexo::ecs { */ template struct ReadSingleton { - using ComponentType = T; + using ComponentType = T; static constexpr AccessType accessType = AccessType::Read; }; @@ -63,7 +63,7 @@ namespace nexo::ecs { */ template struct WriteSingleton { - using ComponentType = T; + using ComponentType = T; static constexpr AccessType accessType = AccessType::Write; }; @@ -118,11 +118,8 @@ namespace nexo::ecs { template void tuple_for_each(Tuple&& tuple, Func&& func) { - tuple_for_each_impl( - std::forward(tuple), - std::forward(func), - std::make_index_sequence>>{} - ); + tuple_for_each_impl(std::forward(tuple), std::forward(func), + std::make_index_sequence>>{}); } /** @@ -148,4 +145,4 @@ namespace nexo::ecs { */ template struct IsSingleton : std::bool_constant::value || IsWriteSingleton::value> {}; -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/ComponentArray.cpp b/engine/src/ecs/ComponentArray.cpp index ad8c2a3f1..1952cf6aa 100644 --- a/engine/src/ecs/ComponentArray.cpp +++ b/engine/src/ecs/ComponentArray.cpp @@ -16,7 +16,8 @@ namespace nexo::ecs { - TypeErasedComponentArray::TypeErasedComponentArray(const size_t componentSize, const size_t initialCapacity): m_componentSize(componentSize), m_capacity(initialCapacity) + TypeErasedComponentArray::TypeErasedComponentArray(const size_t componentSize, const size_t initialCapacity) + : m_componentSize(componentSize), m_capacity(initialCapacity) { if (componentSize == 0) { throw std::invalid_argument("Component size cannot be zero"); @@ -34,8 +35,7 @@ namespace nexo::ecs { void TypeErasedComponentArray::insertRaw(Entity entity, const void* componentData) { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); + if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); ensureSparseCapacity(entity); @@ -55,16 +55,14 @@ namespace nexo::ecs { } // Copy component data - std::memcpy(m_componentData.data() + newIndex * m_componentSize, - componentData, m_componentSize); + std::memcpy(m_componentData.data() + newIndex * m_componentSize, componentData, m_componentSize); ++m_size; } void TypeErasedComponentArray::remove(const Entity entity) { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); size_t indexToRemove = m_sparse[entity]; @@ -103,14 +101,12 @@ namespace nexo::ecs { void TypeErasedComponentArray::entityDestroyed(const Entity entity) { - if (hasComponent(entity)) - remove(entity); + if (hasComponent(entity)) remove(entity); } void TypeErasedComponentArray::duplicateComponent(const Entity sourceEntity, const Entity destEntity) { - if (!hasComponent(sourceEntity)) - THROW_EXCEPTION(ComponentNotFound, sourceEntity); + if (!hasComponent(sourceEntity)) THROW_EXCEPTION(ComponentNotFound, sourceEntity); const void* sourceData = getRawComponent(sourceEntity); insert(destEntity, sourceData); @@ -128,15 +124,13 @@ namespace nexo::ecs { void* TypeErasedComponentArray::getRawComponent(const Entity entity) { - if (!hasComponent(entity)) - return nullptr; + if (!hasComponent(entity)) return nullptr; return m_componentData.data() + m_sparse[entity] * m_componentSize; } const void* TypeErasedComponentArray::getRawComponent(const Entity entity) const { - if (!hasComponent(entity)) - return nullptr; + if (!hasComponent(entity)) return nullptr; return m_componentData.data() + m_sparse[entity] * m_componentSize; } @@ -157,19 +151,16 @@ namespace nexo::ecs { Entity TypeErasedComponentArray::getEntityAtIndex(const size_t index) const { - if (index >= m_size) - THROW_EXCEPTION(OutOfRange, index); + if (index >= m_size) THROW_EXCEPTION(OutOfRange, index); return m_dense[index]; } void TypeErasedComponentArray::addToGroup(const Entity entity) { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); const size_t index = m_sparse[entity]; - if (index < m_groupSize) - return; + if (index < m_groupSize) return; if (index != m_groupSize) { swapComponents(index, m_groupSize); @@ -182,12 +173,10 @@ namespace nexo::ecs { void TypeErasedComponentArray::removeFromGroup(const Entity entity) { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); const size_t index = m_sparse[entity]; - if (index >= m_groupSize) - return; + if (index >= m_groupSize) return; --m_groupSize; if (index != m_groupSize) { @@ -205,19 +194,15 @@ namespace nexo::ecs { size_t TypeErasedComponentArray::memoryUsage() const { - return m_componentData.capacity() - + sizeof(size_t) * m_sparse.capacity() - + sizeof(Entity) * m_dense.capacity(); + return m_componentData.capacity() + sizeof(size_t) * m_sparse.capacity() + sizeof(Entity) * m_dense.capacity(); } void TypeErasedComponentArray::ensureSparseCapacity(const Entity entity) { if (entity >= m_sparse.size()) { size_t newSize = m_sparse.size(); - if (newSize == 0) - newSize = m_capacity; - while (entity >= newSize) - newSize *= 2; + if (newSize == 0) newSize = m_capacity; + while (entity >= newSize) newSize *= 2; m_sparse.resize(newSize, INVALID_ENTITY); } } @@ -240,8 +225,7 @@ namespace nexo::ecs { { if (m_size < m_componentData.capacity() / 4 && m_componentData.capacity() > m_capacity * m_componentSize * 2) { size_t newCapacity = std::max(m_size * 2, m_capacity) * m_componentSize; - if (newCapacity < m_capacity * m_componentSize) - newCapacity = m_capacity * m_componentSize; + if (newCapacity < m_capacity * m_componentSize) newCapacity = m_capacity * m_componentSize; m_componentData.shrink_to_fit(); m_dense.shrink_to_fit(); diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index dc3e81f45..164e18880 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -18,10 +18,10 @@ #include "Exception.hpp" #include "Logger.hpp" -#include -#include #include #include +#include +#include namespace nexo::ecs { /** @@ -32,10 +32,10 @@ namespace nexo::ecs { * allowing type-erased storage and manipulation of components. * * @note This class is not thread-safe. Access to a ComponentArray should be - * synchronized externally when used in multi-threaded contexts. + * synchronized externally when used in multithreaded contexts. */ class IComponentArray { - public: + public: virtual ~IComponentArray() = default; /** @@ -101,7 +101,7 @@ namespace nexo::ecs { * @pre The entity must be a valid entity ID * @pre componentData must point to valid memory of component's size */ - virtual void insertRaw(Entity entity, const void *componentData) = 0; + virtual void insertRaw(Entity entity, const void* componentData) = 0; /** * @brief Removes the component for the given entity. @@ -124,8 +124,8 @@ namespace nexo::ecs { }; #if defined(_MSC_VER) - #pragma warning(push) // ComponentArray - #pragma warning(disable: 4324) // disable msvc warning for added padding bytes because of alignas(64) + #pragma warning(push) // ComponentArray + #pragma warning(disable : 4324) // disable msvc warning for added padding bytes because of alignas(64) #endif /** @@ -140,12 +140,12 @@ namespace nexo::ecs { * @tparam capacity Initial capacity for the sparse array * * @note This class is not thread-safe. Access should be synchronized externally when - * used in multi-threaded contexts. + * used in multithreaded contexts. */ template - requires (capacity >= 1) + requires(capacity >= 1) class alignas(64) ComponentArray final : public IComponentArray { - public: + public: /** * @brief Type alias for the component type */ @@ -171,15 +171,13 @@ namespace nexo::ecs { [[nodiscard]] void* getRawComponent(Entity entity) override { - if (!hasComponent(entity)) - return nullptr; + if (!hasComponent(entity)) return nullptr; return &m_componentArray[m_sparse[entity]]; } [[nodiscard]] const void* getRawComponent(const Entity entity) const override { - if (!hasComponent(entity)) - return nullptr; + if (!hasComponent(entity)) return nullptr; return &m_componentArray[m_sparse[entity]]; } @@ -204,8 +202,7 @@ namespace nexo::ecs { */ void insert(Entity entity, T component) { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); + if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); // Ensure m_sparse can hold this entity index. ensureSparseCapacity(entity); @@ -216,7 +213,7 @@ namespace nexo::ecs { } const size_t newIndex = m_size; - m_sparse[entity] = newIndex; + m_sparse[entity] = newIndex; m_dense.push_back(entity); m_componentArray.push_back(component); @@ -233,10 +230,9 @@ namespace nexo::ecs { * @pre The entity must be a valid entity ID * @pre componentData must point to valid memory of component's size */ - void insertRaw(Entity entity, const void *componentData) override + void insertRaw(Entity entity, const void* componentData) override { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); + if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); ensureSparseCapacity(entity); @@ -246,17 +242,19 @@ namespace nexo::ecs { } const size_t newIndex = m_size; - m_sparse[entity] = newIndex; + m_sparse[entity] = newIndex; m_dense.push_back(entity); // allocate new component in the array m_componentArray.emplace_back(); - // copy the raw data into the new component, if it is trivially copyable, use memcpy, otherwise use placement new + // copy the raw data into the new component, if it is trivially copyable, use memcpy, otherwise use + // placement new if constexpr (std::is_trivially_copyable_v) { std::memcpy(&m_componentArray[newIndex], componentData, sizeof(T)); ++m_size; } else { - THROW_EXCEPTION(InternalError, "Component type is not trivially copyable, raw insertion is not supported"); + THROW_EXCEPTION(InternalError, + "Component type is not trivially copyable, raw insertion is not supported"); } } @@ -273,8 +271,7 @@ namespace nexo::ecs { */ void remove(const Entity entity) override { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); size_t indexToRemove = m_sparse[entity]; @@ -285,7 +282,7 @@ namespace nexo::ecs { if (indexToRemove != groupLastIndex) { std::swap(m_componentArray[indexToRemove], m_componentArray[groupLastIndex]); std::swap(m_dense[indexToRemove], m_dense[groupLastIndex]); - m_sparse[m_dense[indexToRemove]] = indexToRemove; + m_sparse[m_dense[indexToRemove]] = indexToRemove; m_sparse[m_dense[groupLastIndex]] = groupLastIndex; } --m_groupSize; @@ -318,8 +315,7 @@ namespace nexo::ecs { */ [[nodiscard]] T& get(const Entity entity) { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); return m_componentArray[m_sparse[entity]]; } @@ -334,15 +330,13 @@ namespace nexo::ecs { */ [[nodiscard]] const T& get(const Entity entity) const { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); return m_componentArray[m_sparse[entity]]; } void duplicateComponent(const Entity sourceEntity, const Entity destEntity) override { - if (!hasComponent(sourceEntity)) - THROW_EXCEPTION(ComponentNotFound, sourceEntity); + if (!hasComponent(sourceEntity)) THROW_EXCEPTION(ComponentNotFound, sourceEntity); insert(destEntity, get(sourceEntity)); } @@ -365,8 +359,7 @@ namespace nexo::ecs { */ void entityDestroyed(const Entity entity) override { - if (hasComponent(entity)) - remove(entity); + if (hasComponent(entity)) remove(entity); } /** @@ -390,8 +383,7 @@ namespace nexo::ecs { */ [[nodiscard]] Entity getEntityAtIndex(const size_t index) const { - if (index >= m_size) - THROW_EXCEPTION(OutOfRange, index); + if (index >= m_size) THROW_EXCEPTION(OutOfRange, index); return m_dense[index]; } @@ -438,17 +430,15 @@ namespace nexo::ecs { */ void addToGroup(const Entity entity) { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); size_t index = m_sparse[entity]; - if (index < m_groupSize) - return; + if (index < m_groupSize) return; // Swap with the element at the group boundary. if (index != m_groupSize) { std::swap(m_componentArray[index], m_componentArray[m_groupSize]); std::swap(m_dense[index], m_dense[m_groupSize]); - m_sparse[m_dense[index]] = index; + m_sparse[m_dense[index]] = index; m_sparse[m_dense[m_groupSize]] = m_groupSize; } ++m_groupSize; @@ -467,17 +457,15 @@ namespace nexo::ecs { */ void removeFromGroup(const Entity entity) { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); size_t index = m_sparse[entity]; - if (index >= m_groupSize) - return; + if (index >= m_groupSize) return; --m_groupSize; if (index != m_groupSize) { std::swap(m_componentArray[index], m_componentArray[m_groupSize]); std::swap(m_dense[index], m_dense[m_groupSize]); - m_sparse[m_dense[index]] = index; + m_sparse[m_dense[index]] = index; m_sparse[m_dense[m_groupSize]] = m_groupSize; } } @@ -494,11 +482,10 @@ namespace nexo::ecs { */ void forceSetComponentAt(size_t index, const Entity entity, T component) { - if (index >= m_size) - THROW_EXCEPTION(OutOfRange, index); + if (index >= m_size) THROW_EXCEPTION(OutOfRange, index); - m_sparse[entity] = index; - m_dense[index] = entity; + m_sparse[entity] = index; + m_dense[index] = entity; m_componentArray[index] = std::move(component); } @@ -568,12 +555,11 @@ namespace nexo::ecs { */ [[nodiscard]] size_t memoryUsage() const { - return sizeof(T) * m_componentArray.capacity() - + sizeof(size_t) * m_sparse.capacity() - + sizeof(Entity) * m_dense.capacity(); + return sizeof(T) * m_componentArray.capacity() + sizeof(size_t) * m_sparse.capacity() + + sizeof(Entity) * m_dense.capacity(); } - private: + private: // Dense storage for components. std::vector m_componentArray; // Sparse mapping: maps entity ID to index in the dense arrays. @@ -594,10 +580,8 @@ namespace nexo::ecs { { if (entity >= m_sparse.size()) { size_t newSize = m_sparse.size(); - if (newSize == 0) - newSize = capacity; - while (entity >= newSize) - newSize *= 2; + if (newSize == 0) newSize = capacity; + while (entity >= newSize) newSize *= 2; m_sparse.resize(newSize, INVALID_ENTITY); } } @@ -613,8 +597,7 @@ namespace nexo::ecs { if (m_size < m_componentArray.capacity() / 4 && m_componentArray.capacity() > capacity * 2) { // Only shrink if vectors are significantly oversized to avoid frequent reallocations size_t newCapacity = std::max(m_size * 2, capacity); - if (newCapacity < capacity) - newCapacity = capacity; + if (newCapacity < capacity) newCapacity = capacity; m_componentArray.shrink_to_fit(); m_dense.shrink_to_fit(); @@ -626,15 +609,14 @@ namespace nexo::ecs { } }; - #if defined(_MSC_VER) #pragma warning(pop) // ComponentArray - #pragma warning(push) // TypeErasedComponentArray - #pragma warning(disable: 4324) // disable msvc warning for added padding bytes because of alignas(64) + #pragma warning(push) // TypeErasedComponentArray + #pragma warning(disable : 4324) // disable msvc warning for added padding bytes because of alignas(64) #endif - /** + /** * @class TypeErasedComponentArray * @brief A type-erased component array that can store components of any size. * @@ -643,7 +625,7 @@ namespace nexo::ecs { * each component. */ class alignas(64) TypeErasedComponentArray final : public IComponentArray { - public: + public: /** * @brief Constructs a new type-erased component array * @param componentSize Size of each component in bytes @@ -676,24 +658,68 @@ namespace nexo::ecs { */ void remove(Entity entity) override; + /** + * @brief Checks if an entity has a component in this array + * @param entity The entity to check + * @return true if the entity has a component, false otherwise + */ [[nodiscard]] bool hasComponent(Entity entity) const override; + /** + * @brief Removes the component from an entity when it's destroyed + * @param entity The entity being destroyed + */ void entityDestroyed(Entity entity) override; + /** + * @brief Duplicates the component from sourceEntity to destEntity + * @param sourceEntity The entity to copy the component from + * @param destEntity The entity to copy the component to + */ void duplicateComponent(Entity sourceEntity, Entity destEntity) override; + /** + * @brief Gets the size of each component in bytes + * @return Size of individual component in bytes + */ [[nodiscard]] size_t getComponentSize() const override; + /** + * @brief Gets the total number of components in the array + * @return The number of active components + */ [[nodiscard]] size_t size() const override; + /** + * @brief Gets raw pointer to component data for an entity + * @param entity The entity to get the component from + * @return Raw pointer to component data, or nullptr if not found + */ [[nodiscard]] void* getRawComponent(Entity entity) override; + /** + * @brief Gets const raw pointer to component data for an entity + * @param entity The entity to get the component from + * @return Const raw pointer to component data, or nullptr if not found + */ [[nodiscard]] const void* getRawComponent(Entity entity) const override; + /** + * @brief Gets raw pointer to all component data + * @return Raw pointer to contiguous component data + */ [[nodiscard]] void* getRawData() override; + /** + * @brief Gets const raw pointer to all component data + * @return Const raw pointer to contiguous component data + */ [[nodiscard]] const void* getRawData() const override; + /** + * @brief Gets a const span view of all entities with this component + * @return Const span of entity IDs + */ [[nodiscard]] std::span entities() const override; /** @@ -727,7 +753,7 @@ namespace nexo::ecs { */ [[nodiscard]] size_t memoryUsage() const; - private: + private: // Component data storage std::vector m_componentData; // Sparse mapping: maps entity ID to index in the dense arrays @@ -743,10 +769,24 @@ namespace nexo::ecs { // Group size for component grouping size_t m_groupSize = 0; + /** + * @brief Ensures m_sparse is large enough to index 'entity' + * @param entity The entity to ensure capacity for + */ void ensureSparseCapacity(Entity entity); + /** + * @brief Swaps components at two indices in the dense array + * @param index1 The first index + * @param index2 The second index + */ void swapComponents(size_t index1, size_t index2); + /** @brief Shrinks vectors if they're significantly larger than needed + * + * Reduces memory usage by shrinking the dense vectors when size + * is less than half of their capacity. + */ void shrinkIfNeeded(); }; @@ -754,4 +794,4 @@ namespace nexo::ecs { #pragma warning(pop) // TypeErasedComponentArray #endif -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/Components.cpp b/engine/src/ecs/Components.cpp index 84e5072e4..59dc629e9 100644 --- a/engine/src/ecs/Components.cpp +++ b/engine/src/ecs/Components.cpp @@ -18,14 +18,12 @@ namespace nexo::ecs { void ComponentManager::entityDestroyed(const Entity entity, const Signature &entitySignature) { - for (const auto &group: m_groupRegistry | std::views::values) { - if ((entitySignature & group->allSignature()) == group->allSignature()) - group->removeFromGroup(entity); + for (const auto &group : m_groupRegistry | std::views::values) { + if ((entitySignature & group->allSignature()) == group->allSignature()) group->removeFromGroup(entity); } - for (const auto& componentArray : m_componentArrays) { - if (componentArray) - componentArray->entityDestroyed(entity); + for (const auto &componentArray : m_componentArrays) { + if (componentArray) componentArray->entityDestroyed(entity); } } -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 5bbff01ae..fa05b1cb4 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -14,721 +14,728 @@ #pragma once #include +#include #include #include +#include #include -#include #include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include -#include "ECSExceptions.hpp" +#include "ComponentArray.hpp" #include "Definitions.hpp" +#include "ECSExceptions.hpp" #include "Exception.hpp" -#include "Logger.hpp" -#include "ComponentArray.hpp" #include "Group.hpp" +#include "Logger.hpp" namespace nexo::ecs { - /** - * @brief Helper template to tag non-owned component types - * - * This template is used for tag dispatching to distinguish between - * owned and non-owned components in group registration. - * - * @tparam NonOwning The non-owned component types - */ - template - struct get_t { }; - - /** - * @brief Creates a type tag for specifying non-owned components - * - * This helper function is used to create a tag type that identifies - * which components should be accessible but not owned by a group. - * - * @tparam NonOwning The non-owned component types - * @return A tag object of get_t - */ - template - get_t get() - { - return {}; - } - - /** - * @brief Type alias for a tuple of owned component arrays - * - * Used internally by the Group class to store owned components. - * - * @tparam Owned The owned component types - */ - template - using OwnedComponents = std::tuple>...>; - - /** - * @brief Type alias for a tuple of non-owned component arrays - * - * Used internally by the Group class to store references to non-owned components. - * - * @tparam NonOwned The non-owned component types - */ - template - using NonOwnedComponents = std::tuple>...>; - - /** - * @brief Type alias for a shared pointer to a Group - * - * Simplifies the declaration of Group instances by hiding the complex template types. - * - * @tparam OwnedGroup Tuple type for owned component arrays - * @tparam NonOwnedGroup Tuple type for non-owned component arrays - */ - template - using GroupAlias = std::shared_ptr>; - - /** - * @brief Structure to represent a group key using numeric types - * - * Provides a more efficient way to identify groups compared to strings. - * Uses a combination of two separate signatures to represent owned and non-owned components. - */ - struct GroupKey { - Signature ownedSignature; ///< Bits set for components owned by the group - Signature nonOwnedSignature; ///< Bits set for components used but not owned by the group - - /** - * @brief Equality comparison operator - */ - bool operator==(const GroupKey& other) const { - return ownedSignature == other.ownedSignature && - nonOwnedSignature == other.nonOwnedSignature; - } - - /** - * @brief Returns a string representation of the component types in this key - * Used for error messages and debugging - * - * @return String describing the components - */ - [[nodiscard]] std::string toString() const { - std::stringstream ss; - ss << "Owned: {"; - bool first = true; - - // Add owned component IDs - for (ComponentType i = 0; i < MAX_COMPONENT_TYPE; ++i) { - if (ownedSignature.test(i)) { - if (!first) - ss << ", "; - ss << "Component#" << i; - first = false; - } - } - - ss << "}, Non-owned: {"; - first = true; - - // Add non-owned component IDs - for (ComponentType i = 0; i < MAX_COMPONENT_TYPE; ++i) { - if (nonOwnedSignature.test(i)) { - if (!first) - ss << ", "; - ss << "Component#" << i; - first = false; - } - } - - ss << "}"; - return ss.str(); - } - }; -} - -// Add hash function for GroupKey to use it in an unordered_map -namespace std { - /** - * @brief Hash function for GroupKey - * - * Allows GroupKey to be used as a key in unordered_map - */ - template<> - struct hash { - size_t operator()(const nexo::ecs::GroupKey& key) const noexcept - { - const size_t h1 = std::hash()(key.ownedSignature); - const size_t h2 = std::hash()(key.nonOwnedSignature); - return h1 ^ (h2 << 1); - } - }; -} + /** + * @brief Helper template to tag non-owned component types + * + * This template is used for tag dispatching to distinguish between + * owned and non-owned components in group registration. + * + * @tparam NonOwning The non-owned component types + */ + template + struct get_t {}; + + /** + * @brief Creates a type tag for specifying non-owned components + * + * This helper function is used to create a tag type that identifies + * which components should be accessible but not owned by a group. + * + * @tparam NonOwning The non-owned component types + * @return A tag object of get_t + */ + template + get_t get() + { + return {}; + } + + /** + * @brief Type alias for a tuple of owned component arrays + * + * Used internally by the Group class to store owned components. + * + * @tparam Owned The owned component types + */ + template + using OwnedComponents = std::tuple>...>; + + /** + * @brief Type alias for a tuple of non-owned component arrays + * + * Used internally by the Group class to store references to non-owned components. + * + * @tparam NonOwned The non-owned component types + */ + template + using NonOwnedComponents = std::tuple>...>; + + /** + * @brief Type alias for a shared pointer to a Group + * + * Simplifies the declaration of Group instances by hiding the complex template types. + * + * @tparam OwnedGroup Tuple type for owned component arrays + * @tparam NonOwnedGroup Tuple type for non-owned component arrays + */ + template + using GroupAlias = std::shared_ptr>; + + /** + * @brief Structure to represent a group key using numeric types + * + * Provides a more efficient way to identify groups compared to strings. + * Uses a combination of two separate signatures to represent owned and non-owned components. + */ + struct GroupKey { + Signature ownedSignature; ///< Bits set for components owned by the group + Signature nonOwnedSignature; ///< Bits set for components used but not owned by the group + + /** + * @brief Equality comparison operator + */ + bool operator==(const GroupKey& other) const + { + return ownedSignature == other.ownedSignature && nonOwnedSignature == other.nonOwnedSignature; + } + + /** + * @brief Returns a string representation of the component types in this key + * Used for error messages and debugging + * + * @return String describing the components + */ + [[nodiscard]] std::string toString() const + { + std::stringstream ss; + ss << "Owned: {"; + bool first = true; + + // Add owned component IDs + for (ComponentType i = 0; i < MAX_COMPONENT_TYPE; ++i) { + if (ownedSignature.test(i)) { + if (!first) ss << ", "; + ss << "Component#" << i; + first = false; + } + } + + ss << "}, Non-owned: {"; + first = true; + + // Add non-owned component IDs + for (ComponentType i = 0; i < MAX_COMPONENT_TYPE; ++i) { + if (nonOwnedSignature.test(i)) { + if (!first) ss << ", "; + ss << "Component#" << i; + first = false; + } + } + + ss << "}"; + return ss.str(); + } + }; +} // namespace nexo::ecs + +/** + * @brief Hash function for GroupKey + * + * Allows GroupKey to be used as a key in unordered_map + */ +template<> +struct std::hash { + size_t operator()(const nexo::ecs::GroupKey& key) const noexcept + { + const size_t h1 = std::hash()(key.ownedSignature); + const size_t h2 = std::hash()(key.nonOwnedSignature); + return h1 ^ (h2 << 1); + } +}; // namespace std namespace nexo::ecs { - /** - * @class ComponentManager - * - * @brief Central manager for all component types and their arrays - * - * The ComponentManager is responsible for: - * - Registering component types in the ECS - * - Creating and maintaining component arrays - * - Adding/removing components from entities - * - Managing component group registrations - * - Handling entity destruction with respect to components - */ - class ComponentManager { - public: - ComponentManager() = default; - - /** - * @brief Copy constructor (deleted) - * - * ComponentManager is not copyable to prevent duplication of component data. - */ - ComponentManager(const ComponentManager&) = delete; - - /** - * @brief Copy assignment operator (deleted) - * - * ComponentManager is not copyable to prevent duplication of component data. - */ - ComponentManager& operator=(const ComponentManager&) = delete; - - /** - * @brief Move constructor - * - * Allows transferring ownership of component arrays to a new manager. - */ - ComponentManager(ComponentManager&&) noexcept = default; - - /** - * @brief Move assignment operator - * - * Allows transferring ownership of component arrays to a new manager. - */ - ComponentManager& operator=(ComponentManager&&) noexcept = default; - - /** - * @brief Registers a component type in the ECS - * - * Creates a new component array for the specified component type. - * If the component type is already registered, a warning is logged. - * - * @tparam T The component type to register - */ - template - void registerComponent() - { - const ComponentType typeID = getComponentTypeID(); - - assert(typeID < m_componentArrays.size() && "Component type ID exceeds component array size"); - if (m_componentArrays[typeID] != nullptr) { - LOG(NEXO_WARN, "Component already registered"); - return; - } - - m_componentArrays[typeID] = std::make_shared>(); - } - - ComponentType registerComponent(const size_t componentSize, const size_t initialCapacity = 1024) - { - const ComponentType typeID = generateComponentTypeID(); - assert(typeID < m_componentArrays.size() && "Component type ID exceeds component array size"); - - assert(m_componentArrays[typeID] == nullptr && "TypeErasedComponent already registered, should really not happen"); - m_componentArrays[typeID] = std::make_shared(componentSize, initialCapacity); - return typeID; - } - - /** - * @brief Gets the unique identifier for a component type - * - * @tparam T The component type - * @return The component type ID - * @throws ComponentNotRegistered if the component type is not registered - */ - template - [[nodiscard]] ComponentType getComponentType() const - { - const ComponentType typeID = getComponentTypeID(); - - if (m_componentArrays[typeID] == nullptr) - THROW_EXCEPTION(ComponentNotRegistered); - - return typeID; - } - - /** - * @brief Adds a component to an entity - * - * Adds the component to the appropriate component array and - * updates any groups that match the entity's new signature. - * - * @tparam T The component type - * @param entity The entity to add the component to - * @param component The component instance to add - * @param oldSignature The entity's current component signature - * @param newSignature The entity's new component signature - */ - template - void addComponent(Entity entity, T component, const Signature oldSignature, const Signature newSignature) - { - getComponentArray()->insert(entity, std::move(component)); - - for (const auto& group : std::ranges::views::values(m_groupRegistry)) { - // Check if entity qualifies now but did not qualify before. - if (((oldSignature & group->allSignature()) != group->allSignature()) && - ((newSignature & group->allSignature()) == group->allSignature())) { - group->addToGroup(entity); - } - } - } - - /** - * @brief Adds a component to an entity using type ID and raw data - * - * Adds the component using the component type ID and raw data pointer, - * useful for runtime component type handling. Updates any groups that - * match the entity's new signature. - * - * @param entity The entity to add the component to - * @param componentType The type ID of the component to add - * @param componentData Pointer to the raw component data - * @param oldSignature The entity's signature before adding the component - * @param newSignature The entity's signature after adding the component - * - * @pre componentType must be a valid registered component type - * @pre componentData must point to valid memory of the component's size - */ - void addComponent(const Entity entity, const ComponentType componentType, const void *componentData, const Signature oldSignature, const Signature newSignature) - { - getComponentArray(componentType)->insertRaw(entity, componentData); - - for (const auto& group : std::ranges::views::values(m_groupRegistry)) { - // Check if entity qualifies now but did not qualify before. - if (((oldSignature & group->allSignature()) != group->allSignature()) && - ((newSignature & group->allSignature()) == group->allSignature())) { - group->addToGroup(entity); - } - } - } - - /** - * @brief Removes a component from an entity using type ID - * - * Removes the component using the component type ID and updates any groups that - * depend on the component. - * - * @param entity The entity to remove the component from - * @param componentType The type ID of the component to remove - * @param previousSignature The entity's signature before removal - * @param newSignature The entity's signature after removal - * - * @pre componentType must be a valid registered component type - */ - void removeComponent(const Entity entity, const ComponentType componentType, const Signature previousSignature, const Signature newSignature) - { - for (const auto& group : std::ranges::views::values(m_groupRegistry)) - { - if (((previousSignature & group->allSignature()) == group->allSignature()) && - ((newSignature & group->allSignature()) != group->allSignature())) - { - group->removeFromGroup(entity); - } - } - getComponentArray(componentType)->remove(entity); - } - - - /** - * @brief Removes a component from an entity - * - * Removes the entity from any groups that required the component - * and then removes the component from its array. - * - * @tparam T The component type - * @param entity The entity to remove the component from - * @param previousSignature The entity's signature before removal - * @param newSignature The entity's signature after removal - */ - template - void removeComponent(Entity entity, const Signature previousSignature, const Signature newSignature) - { - for (const auto& group : std::ranges::views::values(m_groupRegistry)) - { - // If the entity no longer qualifies but did before, remove it. - if (((previousSignature & group->allSignature()) == group->allSignature()) && + /** + * @class ComponentManager + * + * @brief Central manager for all component types and their arrays + * + * The ComponentManager is responsible for: + * - Registering component types in the ECS + * - Creating and maintaining component arrays + * - Adding/removing components from entities + * - Managing component group registrations + * - Handling entity destruction with respect to components + */ + class ComponentManager { + public: + ComponentManager() = default; + + /** + * @brief Copy constructor (deleted) + * + * ComponentManager is not copyable to prevent duplication of component data. + */ + ComponentManager(const ComponentManager&) = delete; + + /** + * @brief Copy assignment operator (deleted) + * + * ComponentManager is not copyable to prevent duplication of component data. + */ + ComponentManager& operator=(const ComponentManager&) = delete; + + /** + * @brief Move constructor + * + * Allows transferring ownership of component arrays to a new manager. + */ + ComponentManager(ComponentManager&&) noexcept = default; + + /** + * @brief Move assignment operator + * + * Allows transferring ownership of component arrays to a new manager. + */ + ComponentManager& operator=(ComponentManager&&) noexcept = default; + + /** + * @brief Registers a component type in the ECS + * + * Creates a new component array for the specified component type. + * If the component type is already registered, a warning is logged. + * + * @tparam T The component type to register + */ + template + void registerComponent() + { + const ComponentType typeID = getComponentTypeID(); + + assert(typeID < m_componentArrays.size() && "Component type ID exceeds component array size"); + if (m_componentArrays[typeID] != nullptr) { + LOG(NEXO_WARN, "Component already registered"); + return; + } + + m_componentArrays[typeID] = std::make_shared>(); + } + + /** + * @brief Retrieves the component array for a specific component type + * + * @tparam T The component type + * @return std::shared_ptr> Shared pointer to the component array + * @throws ComponentNotRegistered if the component type is not registered + */ + ComponentType registerComponent(const size_t componentSize, const size_t initialCapacity = 1024) + { + const ComponentType typeID = generateComponentTypeID(); + assert(typeID < m_componentArrays.size() && "Component type ID exceeds component array size"); + + assert(m_componentArrays[typeID] == nullptr && + "TypeErasedComponent already registered, should really not happen"); + m_componentArrays[typeID] = std::make_shared(componentSize, initialCapacity); + return typeID; + } + + /** + * @brief Gets the unique identifier for a component type + * + * @tparam T The component type + * @return The component type ID + * @throws ComponentNotRegistered if the component type is not registered + */ + template + [[nodiscard]] ComponentType getComponentType() const + { + const ComponentType typeID = getComponentTypeID(); + + if (m_componentArrays[typeID] == nullptr) THROW_EXCEPTION(ComponentNotRegistered); + + return typeID; + } + + /** + * @brief Adds a component to an entity + * + * Adds the component to the appropriate component array and + * updates any groups that match the entity's new signature. + * + * @tparam T The component type + * @param entity The entity to add the component to + * @param component The component instance to add + * @param oldSignature The entity's current component signature + * @param newSignature The entity's new component signature + */ + template + void addComponent(Entity entity, T component, const Signature oldSignature, const Signature newSignature) + { + getComponentArray()->insert(entity, std::move(component)); + + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { + // Check if entity qualifies now but did not qualify before. + if (((oldSignature & group->allSignature()) != group->allSignature()) && + ((newSignature & group->allSignature()) == group->allSignature())) { + group->addToGroup(entity); + } + } + } + + /** + * @brief Adds a component to an entity using type ID and raw data + * + * Adds the component using the component type ID and raw data pointer, + * useful for runtime component type handling. Updates any groups that + * match the entity's new signature. + * + * @param entity The entity to add the component to + * @param componentType The type ID of the component to add + * @param componentData Pointer to the raw component data + * @param oldSignature The entity's signature before adding the component + * @param newSignature The entity's signature after adding the component + * + * @pre componentType must be a valid registered component type + * @pre componentData must point to valid memory of the component's size + */ + void addComponent(const Entity entity, const ComponentType componentType, const void* componentData, + const Signature oldSignature, const Signature newSignature) + { + getComponentArray(componentType)->insertRaw(entity, componentData); + + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { + // Check if entity qualifies now but did not qualify before. + if (((oldSignature & group->allSignature()) != group->allSignature()) && + ((newSignature & group->allSignature()) == group->allSignature())) { + group->addToGroup(entity); + } + } + } + + /** + * @brief Removes a component from an entity using type ID + * + * Removes the component using the component type ID and updates any groups that + * depend on the component. + * + * @param entity The entity to remove the component from + * @param componentType The type ID of the component to remove + * @param previousSignature The entity's signature before removal + * @param newSignature The entity's signature after removal + * + * @pre componentType must be a valid registered component type + */ + void removeComponent(const Entity entity, const ComponentType componentType, const Signature previousSignature, + const Signature newSignature) + { + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { + if (((previousSignature & group->allSignature()) == group->allSignature()) && ((newSignature & group->allSignature()) != group->allSignature())) { - group->removeFromGroup(entity); - } + group->removeFromGroup(entity); } - getComponentArray()->remove(entity); } - - /** - * @brief Attempts to remove a component from an entity - * - * Checks if the entity has the component first, then removes it - * if it exists. Updates groups as needed. - * - * @tparam T The component type - * @param entity The entity to remove the component from - * @param previousSignature The entity's signature before the attempted removal - * @param newSignature The entity's signature after the attempted removal - * @return true if the component was removed, false if it didn't exist - */ - template - bool tryRemoveComponent(Entity entity, const Signature previousSignature, const Signature newSignature) - { - auto componentArray = getComponentArray(); - if (!componentArray->hasComponent(entity)) - return false; - - for (const auto& group : std::ranges::views::values(m_groupRegistry)) - { - // If the entity no longer qualifies but did before, remove it. - if (((previousSignature & group->allSignature()) == group->allSignature()) && + getComponentArray(componentType)->remove(entity); + } + + /** + * @brief Removes a component from an entity + * + * Removes the entity from any groups that required the component + * and then removes the component from its array. + * + * @tparam T The component type + * @param entity The entity to remove the component from + * @param previousSignature The entity's signature before removal + * @param newSignature The entity's signature after removal + */ + template + void removeComponent(Entity entity, const Signature previousSignature, const Signature newSignature) + { + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { + // If the entity no longer qualifies but did before, remove it. + if (((previousSignature & group->allSignature()) == group->allSignature()) && ((newSignature & group->allSignature()) != group->allSignature())) { - group->removeFromGroup(entity); + group->removeFromGroup(entity); + } + } + getComponentArray()->remove(entity); + } + + /** + * @brief Attempts to remove a component from an entity + * + * Checks if the entity has the component first, then removes it + * if it exists. Updates groups as needed. + * + * @tparam T The component type + * @param entity The entity to remove the component from + * @param previousSignature The entity's signature before the attempted removal + * @param newSignature The entity's signature after the attempted removal + * @return true if the component was removed, false if it didn't exist + */ + template + bool tryRemoveComponent(Entity entity, const Signature previousSignature, const Signature newSignature) + { + auto componentArray = getComponentArray(); + if (!componentArray->hasComponent(entity)) return false; + + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { + // If the entity no longer qualifies but did before, remove it. + if (((previousSignature & group->allSignature()) == group->allSignature()) && + ((newSignature & group->allSignature()) != group->allSignature())) { + group->removeFromGroup(entity); + } + } + componentArray->remove(entity); + return true; + } + + /** + * @brief Gets a component from an entity + * + * @tparam T The component type + * @param entity The entity to get the component from + * @return Reference to the component + * @throws ComponentNotFound if the entity doesn't have the component + */ + template + [[nodiscard]] T& getComponent(Entity entity) + { + return getComponentArray()->get(entity); + } + + /** + * @brief Duplicates a component from one entity to another. + * + * This template function retrieves the component of type `T` from the source entity + * and adds it to the destination entity. It also updates the entity's signature + * to reflect the addition of the component. + * + * @tparam T The type of the component to duplicate. + * @param sourceEntity The entity from which the component is duplicated. + * @param destEntity The entity to which the component is added. + * @param oldSignature The signature of the destination entity before the duplication. + * @param newSignature The signature of the destination entity after the duplication. + */ + template + void duplicateComponent(Entity sourceEntity, Entity destEntity, const Signature oldSignature, + const Signature newSignature) + { + const auto& componentArray = getComponentArray(); + const auto sourceComponent = componentArray->get(sourceEntity); + addComponent(destEntity, sourceComponent, oldSignature, newSignature); + } + + /** + * @brief Duplicates a component from one entity to another using a type ID. + * + * This function duplicates a component identified by its `ComponentType` from the + * source entity to the destination entity. It also updates the destination entity's + * signature and ensures that the entity is added to any relevant groups. + * + * @param componentType The type ID of the component to duplicate. + * @param sourceEntity The entity from which the component is duplicated. + * @param destEntity The entity to which the component is added. + * @param oldSignature The signature of the destination entity before the duplication. + * @param newSignature The signature of the destination entity after the duplication. + */ + void duplicateComponent(const ComponentType componentType, const Entity sourceEntity, const Entity destEntity, + const Signature oldSignature, const Signature newSignature) + { + const auto& componentArray = m_componentArrays[componentType]; + componentArray->duplicateComponent(sourceEntity, destEntity); + + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { + // Check if entity qualifies now but did not qualify before. + if (((oldSignature & group->allSignature()) != group->allSignature()) && + ((newSignature & group->allSignature()) == group->allSignature())) { + group->addToGroup(destEntity); + } + } + } + + /** + * @brief Gets the component array for a specific component type with ComponentType (const version) + * + * @param typeID The component type ID + * @return Const shared pointer to the component array + * @throws ComponentNotRegistered if the component type is not registered + */ + [[nodiscard]] std::shared_ptr getComponentArray(const ComponentType typeID) const + { + const auto& componentArray = m_componentArrays[typeID]; + if (componentArray == nullptr) THROW_EXCEPTION(ComponentNotRegistered); + + return componentArray; + } + + /** + * @brief Gets the component array for a specific component type + * + * @tparam T The component type + * @return Shared pointer to the component array + * @throws ComponentNotRegistered if the component type is not registered + */ + template + [[nodiscard]] std::shared_ptr> getComponentArray() + { + const ComponentType typeID = getComponentTypeID(); + return std::static_pointer_cast>(getComponentArray(typeID)); + } + + /** + * @brief Gets the component array for a specific component type (const version) + * + * @tparam T The component type + * @return Const shared pointer to the component array + * @throws ComponentNotRegistered if the component type is not registered + */ + template + [[nodiscard]] std::shared_ptr> getComponentArray() const + { + const ComponentType typeID = getComponentTypeID(); + return std::static_pointer_cast>(getComponentArray(typeID)); + } + + /** + * @brief Safely attempts to get a component from an entity + * + * @tparam T The component type + * @param entity The entity to get the component from + * @return Optional reference to the component, or nullopt if not found + */ + template + [[nodiscard]] std::optional> tryGetComponent(Entity entity) + { + auto componentArray = getComponentArray(); + if (!componentArray->hasComponent(entity)) return std::nullopt; + + return componentArray->get(entity); + } + + /** + * @brief Safely attempts to get a component from an entity + * + * @param entity The entity to get the component from + * @param typeID The component type ID + * @return Pointer to the component if it exists, or nullptr if not found + */ + [[nodiscard]] void* tryGetComponent(const Entity entity, const ComponentType typeID) const + { + const auto componentArray = getComponentArray(typeID); + if (!componentArray->hasComponent(entity)) return nullptr; + + return componentArray->getRawComponent(entity); + } + + /** + * @brief Notifies all component arrays that an entity has been destroyed + * + * Removes the entity from all component arrays it exists in. + * + * @param entity The destroyed entity + * @param entitySignature Signature of the entity to be destroyed + */ + void entityDestroyed(Entity entity, const Signature& entitySignature); + + /** + * @brief Creates or retrieves a group for specific component combinations + * + * Creates a group that provides optimized access to entities having + * a specific combination of components, or returns an existing one. + * Components specified in the template parameter pack are "owned" (internal to the group), + * while those in the nonOwned parameter are "non-owned" (externally referenced). + * + * @tparam Owned Component types that are owned by the group + * @param nonOwned A get_t<...> tag specifying non-owned component types + * @return A shared pointer to the group (either existing or newly created) + * @throws ComponentNotRegistered if any component type is not registered + * @throws OverlappingGroupsException if the new group would have overlapping owned + * components with an existing group + */ + template + auto registerGroup(const auto& nonOwned) + { + const GroupKey newGroupKey = generateGroupKey(nonOwned); + + // Check if this exact group already exists + auto it = m_groupRegistry.find(newGroupKey); + if (it != m_groupRegistry.end()) { + using OwnedTuple = std::tuple>...>; + using NonOwnedTuple = decltype(getNonOwnedTuple(nonOwned)); + return std::static_pointer_cast>(it->second); + } + + // Check for conflicts with existing groups + for (const auto& existingKey : std::ranges::views::keys(m_groupRegistry)) { + if (hasCommonOwnedComponents(existingKey, newGroupKey)) { + for (ComponentType i = 0; i < MAX_COMPONENT_TYPE; i++) { + if (existingKey.ownedSignature.test(i) && newGroupKey.ownedSignature.test(i)) { + THROW_EXCEPTION(OverlappingGroupsException, existingKey.toString(), newGroupKey.toString(), + i); + } } - } - componentArray->remove(entity); - return true; - } - - /** - * @brief Gets a component from an entity - * - * @tparam T The component type - * @param entity The entity to get the component from - * @return Reference to the component - * @throws ComponentNotFound if the entity doesn't have the component - */ - template - [[nodiscard]] T& getComponent(Entity entity) - { - return getComponentArray()->get(entity); - } - - template - void duplicateComponent( - Entity sourceEntity, - Entity destEntity, - const Signature oldSignature, - const Signature newSignature - ) { - const auto &componentArray = getComponentArray(); - const auto sourceComponent = componentArray->get(sourceEntity); - addComponent(destEntity, sourceComponent, oldSignature, newSignature); - } - - void duplicateComponent( - ComponentType componentType, - Entity sourceEntity, - Entity destEntity, - const Signature oldSignature, - const Signature newSignature - ) { - const auto& componentArray = m_componentArrays[componentType]; - componentArray->duplicateComponent(sourceEntity, destEntity); - - for (const auto& group : std::ranges::views::values(m_groupRegistry)) { - // Check if entity qualifies now but did not qualify before. - if (((oldSignature & group->allSignature()) != group->allSignature()) && - ((newSignature & group->allSignature()) == group->allSignature())) { - group->addToGroup(destEntity); - } - } - } - - /** - * @brief Gets the component array for a specific component type with ComponentType (const version) - * - * @param typeID The component type ID - * @return Const shared pointer to the component array - * @throws ComponentNotRegistered if the component type is not registered - */ - [[nodiscard]] std::shared_ptr getComponentArray(const ComponentType typeID) const - { - const auto& componentArray = m_componentArrays[typeID]; - if (componentArray == nullptr) - THROW_EXCEPTION(ComponentNotRegistered); - - return componentArray; - } - - /** - * @brief Gets the component array for a specific component type - * - * @tparam T The component type - * @return Shared pointer to the component array - * @throws ComponentNotRegistered if the component type is not registered - */ - template - [[nodiscard]] std::shared_ptr> getComponentArray() - { - const ComponentType typeID = getComponentTypeID(); - return std::static_pointer_cast>(getComponentArray(typeID)); - } - - /** - * @brief Gets the component array for a specific component type (const version) - * - * @tparam T The component type - * @return Const shared pointer to the component array - * @throws ComponentNotRegistered if the component type is not registered - */ - template - [[nodiscard]] std::shared_ptr> getComponentArray() const - { - const ComponentType typeID = getComponentTypeID(); - return std::static_pointer_cast>(getComponentArray(typeID)); - } - - /** - * @brief Safely attempts to get a component from an entity - * - * @tparam T The component type - * @param entity The entity to get the component from - * @return Optional reference to the component, or nullopt if not found - */ - template - [[nodiscard]] std::optional> tryGetComponent(Entity entity) - { - auto componentArray = getComponentArray(); - if (!componentArray->hasComponent(entity)) - return std::nullopt; - - return componentArray->get(entity); - } - - /** - * @brief Safely attempts to get a component from an entity - * - * @param entity The entity to get the component from - * @param typeID The component type ID - * @return Pointer to the component if it exists, or nullptr if not found - */ - [[nodiscard]] void *tryGetComponent(const Entity entity, const ComponentType typeID) const - { - const auto componentArray = getComponentArray(typeID); - if (!componentArray->hasComponent(entity)) - return nullptr; - - return componentArray->getRawComponent(entity); - } - - /** - * @brief Notifies all component arrays that an entity has been destroyed - * - * Removes the entity from all component arrays it exists in. - * - * @param entity The destroyed entity - * @param entitySignature Signature of the entity to be destroyed - */ - void entityDestroyed(Entity entity, const Signature &entitySignature); - - /** - * @brief Creates or retrieves a group for specific component combinations - * - * Creates a group that provides optimized access to entities having - * a specific combination of components, or returns an existing one. - * Components specified in the template parameter pack are "owned" (internal to the group), - * while those in the nonOwned parameter are "non-owned" (externally referenced). - * - * @tparam Owned Component types that are owned by the group - * @param nonOwned A get_t<...> tag specifying non-owned component types - * @return A shared pointer to the group (either existing or newly created) - * @throws ComponentNotRegistered if any component type is not registered - * @throws OverlappingGroupsException if the new group would have overlapping owned - * components with an existing group - */ - template - auto registerGroup(const auto& nonOwned) - { - const GroupKey newGroupKey = generateGroupKey(nonOwned); - - // Check if this exact group already exists - auto it = m_groupRegistry.find(newGroupKey); - if (it != m_groupRegistry.end()) { - using OwnedTuple = std::tuple>...>; - using NonOwnedTuple = decltype(getNonOwnedTuple(nonOwned)); - return std::static_pointer_cast>(it->second); - } - - // Check for conflicts with existing groups - for (const auto& existingKey : std::ranges::views::keys(m_groupRegistry)) { - if (hasCommonOwnedComponents(existingKey, newGroupKey)) { - for (ComponentType i = 0; i < MAX_COMPONENT_TYPE; i++) { - if (existingKey.ownedSignature.test(i) && newGroupKey.ownedSignature.test(i)) { - THROW_EXCEPTION(OverlappingGroupsException, - existingKey.toString(), - newGroupKey.toString(), - i); - } - } - } - } - - auto group = createNewGroup(nonOwned); - m_groupRegistry[newGroupKey] = group; - return group; - } - - /** - * @brief Retrieves an existing group for specific component combinations - * - * Gets a previously registered group that matches the specified - * owned and non-owned component types. - * - * @tparam Owned Component types that are owned by the group - * @param nonOwned A get_t<...> tag specifying non-owned component types - * @return A shared pointer to the existing group - * @throws std::runtime_error if the group doesn't exist - */ - template - auto getGroup(const auto& nonOwned) - { - const GroupKey groupKey = generateGroupKey(nonOwned); - - const auto it = m_groupRegistry.find(groupKey); - if (it == m_groupRegistry.end()) - THROW_EXCEPTION(GroupNotFound, "Group not found"); - - using OwnedTuple = std::tuple>...>; - using NonOwnedTuple = decltype(getNonOwnedTuple(nonOwned)); - return std::static_pointer_cast>(it->second); - } - - /** - * @brief Checks if two groups share any common owned components - * - * Determines if two groups have any overlap in their owned components. - * This is useful for determining if two groups might affect each other's sorting - * or partitioning when entities are updated. - * - * @param key1 First group key to compare - * @param key2 Second group key to compare - * @return true if the keys share at least one owned component, false otherwise - */ - [[nodiscard]] static bool hasCommonOwnedComponents(const GroupKey& key1, const GroupKey& key2) - { - return (key1.ownedSignature & key2.ownedSignature).any(); + } + } + + auto group = createNewGroup(nonOwned); + m_groupRegistry[newGroupKey] = group; + return group; + } + + /** + * @brief Retrieves an existing group for specific component combinations + * + * Gets a previously registered group that matches the specified + * owned and non-owned component types. + * + * @tparam Owned Component types that are owned by the group + * @param nonOwned A get_t<...> tag specifying non-owned component types + * @return A shared pointer to the existing group + * @throws std::runtime_error if the group doesn't exist + */ + template + auto getGroup(const auto& nonOwned) + { + const GroupKey groupKey = generateGroupKey(nonOwned); + + const auto it = m_groupRegistry.find(groupKey); + if (it == m_groupRegistry.end()) THROW_EXCEPTION(GroupNotFound, "Group not found"); + + using OwnedTuple = std::tuple>...>; + using NonOwnedTuple = decltype(getNonOwnedTuple(nonOwned)); + return std::static_pointer_cast>(it->second); + } + + /** + * @brief Checks if two groups share any common owned components + * + * Determines if two groups have any overlap in their owned components. + * This is useful for determining if two groups might affect each other's sorting + * or partitioning when entities are updated. + * + * @param key1 First group key to compare + * @param key2 Second group key to compare + * @return true if the keys share at least one owned component, false otherwise + */ + [[nodiscard]] static bool hasCommonOwnedComponents(const GroupKey& key1, const GroupKey& key2) + { + return (key1.ownedSignature & key2.ownedSignature).any(); + } + + private: + /** + * @brief Array of component arrays indexed by component type ID + * + * Provides O(1) lookup of component arrays by their type ID. + */ + std::array, MAX_COMPONENT_TYPE> m_componentArrays{}; + + /** + * @brief Registry of groups indexed by their component signatures + * + * Allows retrieval of previously created groups by their component types. + */ + std::unordered_map> m_groupRegistry; + + /** + * @brief Helper function to get the tuple of non-owned component arrays + * + * @param nonOwned A get_t<...> tag specifying non-owned component types + * @return Tuple of non-owned component arrays + */ + template + auto getNonOwnedTuple(const get_t&) + { + return std::make_tuple(getComponentArray()...); + } + + /** + * @brief Creates a new group for the specified component types + * + * @tparam Owned Component types owned by the group + * @param nonOwned Tag for non-owned component types + * @return Shared pointer to the new group + */ + template + auto createNewGroup(const auto& nonOwned) + { + auto nonOwnedArrays = getNonOwnedTuple(nonOwned); + + auto ownedArrays = std::make_tuple(getComponentArray()...); + using OwnedTuple = std::tuple>...>; + using NonOwnedTuple = decltype(nonOwnedArrays); + + // Find entities that should be in this group + auto driver = std::get<0>(ownedArrays); + const std::size_t minSize = std::apply( + [](auto&&... arrays) -> std::size_t { return std::min({static_cast(arrays->size())...}); }, + ownedArrays); + + for (std::size_t i = 0; i < minSize; ++i) { + [[maybe_unused]] Entity e = driver->getEntityAtIndex(i); + bool valid = true; + + // Check in owned arrays + std::apply([&](auto&&... arrays) { valid = (valid && ... && arrays->hasComponent(e)); }, ownedArrays); + + // Check in non-owned arrays + std::apply([&](auto&&... arrays) { valid = (valid && ... && arrays->hasComponent(e)); }, + nonOwnedArrays); + + if (valid) { + std::apply([&](auto&&... arrays) { ((arrays->addToGroup(e)), ...); }, ownedArrays); + } } - private: - /** - * @brief Array of component arrays indexed by component type ID - * - * Provides O(1) lookup of component arrays by their type ID. - */ - std::array, MAX_COMPONENT_TYPE> m_componentArrays{}; - - /** - * @brief Registry of groups indexed by their component signatures - * - * Allows retrieval of previously created groups by their component types. - */ - std::unordered_map> m_groupRegistry; - - /** - * @brief Helper function to get the tuple of non-owned component arrays - * - * @param nonOwned A get_t<...> tag specifying non-owned component types - * @return Tuple of non-owned component arrays - */ - template - auto getNonOwnedTuple(const get_t&) - { - return std::make_tuple(getComponentArray()...); - } - - /** - * @brief Creates a new group for the specified component types - * - * @tparam Owned Component types owned by the group - * @param nonOwned Tag for non-owned component types - * @return Shared pointer to the new group - */ - template - auto createNewGroup(const auto& nonOwned) - { - auto nonOwnedArrays = getNonOwnedTuple(nonOwned); - - auto ownedArrays = std::make_tuple(getComponentArray()...); - using OwnedTuple = std::tuple>...>; - using NonOwnedTuple = decltype(nonOwnedArrays); - - // Find entities that should be in this group - auto driver = std::get<0>(ownedArrays); - const std::size_t minSize = std::apply([](auto&&... arrays) -> std::size_t { - return std::min({ static_cast(arrays->size())... }); - }, ownedArrays); - - for (std::size_t i = 0; i < minSize; ++i) { - Entity e = driver->getEntityAtIndex(i); - bool valid = true; - - // Check in owned arrays - std::apply([&](auto&&... arrays) { - valid = (valid && ... && arrays->hasComponent(e)); - }, ownedArrays); - - // Check in non-owned arrays - std::apply([&](auto&&... arrays) { - valid = (valid && ... && arrays->hasComponent(e)); - }, nonOwnedArrays); - - if (valid) { - std::apply([&](auto&&... arrays) { - ((arrays->addToGroup(e)), ...); - }, ownedArrays); - } - } - - return std::make_shared>(ownedArrays, nonOwnedArrays); - } - - /** - * @brief Generates a unique key for a group based on its component types - * - * Creates a GroupKey with separate signatures for owned and non-owned components. - * - * @tparam Owned Component types owned by the group - * @param nonOwned Tag for non-owned component types - * @return GroupKey uniquely identifying this group type combination - */ - template - GroupKey generateGroupKey(const auto& nonOwned) - { - GroupKey key; - - // Set bits for owned components - ((key.ownedSignature.set(getComponentTypeID())), ...); - - // Set bits for non-owned components - setNonOwnedBits(key.nonOwnedSignature, nonOwned); - - return key; - } - - /** - * @brief Sets bits in the non-owned signature for each non-owned component - * - * @tparam NonOwning Non-owned component types - * @param signature The signature to modify - * @param nonOwned The non-owned components tag - */ - template - static void setNonOwnedBits(Signature& signature, const get_t&) - { - ((signature.set(getComponentTypeID())), ...); - } - }; -} + return std::make_shared>(ownedArrays, nonOwnedArrays); + } + + /** + * @brief Generates a unique key for a group based on its component types + * + * Creates a GroupKey with separate signatures for owned and non-owned components. + * + * @tparam Owned Component types owned by the group + * @param nonOwned Tag for non-owned component types + * @return GroupKey uniquely identifying this group type combination + */ + template + GroupKey generateGroupKey(const auto& nonOwned) + { + GroupKey key; + + // Set bits for owned components + ((key.ownedSignature.set(getComponentTypeID())), ...); + + // Set bits for non-owned components + setNonOwnedBits(key.nonOwnedSignature, nonOwned); + + return key; + } + + /** + * @brief Sets bits in the non-owned signature for each non-owned component + * + * @tparam NonOwning Non-owned component types + * @param signature The signature to modify + * @param nonOwned The non-owned components tag + */ + template + static void setNonOwnedBits(Signature& signature, const get_t&) + { + ((signature.set(getComponentTypeID())), ...); + } + }; +} // namespace nexo::ecs diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index c2fb081d0..b2985e03c 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -20,9 +20,9 @@ namespace nexo::ecs { void Coordinator::init() { - m_componentManager = std::make_shared(); - m_entityManager = std::make_shared(); - m_systemManager = std::make_shared(); + m_componentManager = std::make_shared(); + m_entityManager = std::make_shared(); + m_systemManager = std::make_shared(); m_singletonComponentManager = std::make_shared(); System::coord = std::shared_ptr(this, [](const Coordinator*) {}); @@ -65,8 +65,7 @@ namespace nexo::ecs { std::vector typeIndices; typeIndices.reserve(types.size()); - for (const auto& type : types) - { + for (const auto& type : types) { typeIndices.push_back(m_typeIDtoTypeIndex.at(type)); } @@ -93,9 +92,9 @@ namespace nexo::ecs { Entity Coordinator::duplicateEntity(const Entity sourceEntity) const { - const Entity newEntity = createEntity(); + const Entity newEntity = createEntity(); const Signature signature = m_entityManager->getSignature(sourceEntity); - Signature destSignature = m_entityManager->getSignature(newEntity); + Signature destSignature = m_entityManager->getSignature(newEntity); for (ComponentType type = 0; type < MAX_COMPONENT_TYPE; ++type) { if (signature.test(type) && m_typeIDtoTypeIndex.contains(type)) { const Signature previousSignature = destSignature; @@ -111,31 +110,28 @@ namespace nexo::ecs { bool Coordinator::supportsMementoPattern(const std::any& component) const { const auto typeId = std::type_index(component.type()); - const auto it = m_supportsMementoPattern.find(typeId); + const auto it = m_supportsMementoPattern.find(typeId); return (it != m_supportsMementoPattern.end()) && it->second; } std::any Coordinator::saveComponent(const std::any& component) const { const auto typeId = std::type_index(component.type()); - const auto it = m_saveComponentFunctions.find(typeId); - if (it != m_saveComponentFunctions.end()) - return it->second(component); + const auto it = m_saveComponentFunctions.find(typeId); + if (it != m_saveComponentFunctions.end()) return it->second(component); return {}; } std::any Coordinator::restoreComponent(const std::any& memento, const std::type_index& componentType) const { const auto it = m_restoreComponentFunctions.find(componentType); - if (it != m_restoreComponentFunctions.end()) - return it->second(memento); + if (it != m_restoreComponentFunctions.end()) return it->second(memento); return {}; } void Coordinator::addComponentAny(const Entity entity, const std::type_index& typeIndex, const std::any& component) { const auto it = m_addComponentFunctions.find(typeIndex); - if (it != m_addComponentFunctions.end()) - it->second(entity, component); + if (it != m_addComponentFunctions.end()) it->second(entity, component); } -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 770fdf977..4f9f5a534 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -14,45 +14,45 @@ #pragma once -#include #include +#include #include "Components.hpp" -#include "System.hpp" -#include "SingletonComponent.hpp" #include "Entity.hpp" #include "Logger.hpp" +#include "SingletonComponent.hpp" +#include "System.hpp" #include "TypeErasedComponent/ComponentDescription.hpp" namespace nexo::ecs { - template + template struct Exclude { using type = T; }; // Check if type is an Exclude specialization - template + template struct is_exclude : std::false_type {}; - template + template struct is_exclude> : std::true_type {}; - template + template inline constexpr bool is_exclude_v = is_exclude::value; // Extract the actual type from Exclude - template + template struct extract_type { using type = T; }; - template + template struct extract_type> { using type = T; }; - template + template using extract_type_t = typename extract_type::type; // Check if T has a nested Memento type @@ -67,8 +67,7 @@ namespace nexo::ecs { struct has_save_method : std::false_type {}; template - struct has_save_method().save())>> + struct has_save_method().save())>> : std::is_same().save()), typename T::Memento> {}; // Check if T::Memento has a restore() method that returns T @@ -76,18 +75,14 @@ namespace nexo::ecs { struct has_restore_method : std::false_type {}; template - struct has_restore_method().restore(std::declval()))>> + struct has_restore_method< + T, std::void_t().restore(std::declval()))>> : std::is_same().restore(std::declval())), void> {}; // Combined check for full memento pattern support template - struct supports_memento_pattern : - std::conjunction< - has_memento_type, - has_save_method, - has_restore_method - > {}; + struct supports_memento_pattern : std::conjunction, has_save_method, has_restore_method> { + }; template inline constexpr bool has_memento_type_v = has_memento_type::value; @@ -101,7 +96,6 @@ namespace nexo::ecs { template inline constexpr bool supports_memento_pattern_v = supports_memento_pattern::value; - /** * @class Coordinator * @@ -112,539 +106,711 @@ namespace nexo::ecs { * and interaction of entities, components, and systems within the ECS framework. */ class Coordinator { - public: - /** - * @brief Initializes the Coordinator, creating instances of EntityManager, - * ComponentManager, SystemManager, EventManager, SingletonComponentManager - * and SceneManager. - */ - void init(); - - /** - * @brief Creates a new entity. - * - * @return Entity - The newly created entity's ID. - */ - Entity createEntity() const; - - /** - * @brief Destroys an entity and cleans up its components and system references. - * - * @param entity - The ID of the entity to destroy. - */ - void destroyEntity(Entity entity) const; - - /** - * @brief Registers a new component type within the ComponentManager. - */ - template - void registerComponent() - { - m_componentManager->registerComponent(); - - m_getComponentFunctions[typeid(T)] = [this](const Entity entity) -> std::any { - return this->getComponent(entity); + public: + /** + * @brief Initializes the Coordinator, creating instances of EntityManager, + * ComponentManager, SystemManager, EventManager, SingletonComponentManager + * and SceneManager. + */ + void init(); + + /** + * @brief Creates a new entity. + * + * @return Entity - The newly created entity's ID. + */ + [[nodiscard]] Entity createEntity() const; + + /** + * @brief Destroys an entity and cleans up its components and system references. + * + * @param entity - The ID of the entity to destroy. + */ + void destroyEntity(Entity entity) const; + + /** + * @brief Registers a new component type within the ComponentManager. + */ + template + void registerComponent() + { + m_componentManager->registerComponent(); + + m_getComponentFunctions[typeid(T)] = [this](const Entity entity) -> std::any { + return this->getComponent(entity); + }; + + m_getComponentPointers[typeid(T)] = [this](const Entity entity) -> std::any { + auto opt = this->tryGetComponent(entity); + if (!opt.has_value()) return {}; + T* ptr = &opt.value().get(); + return std::any(static_cast(ptr)); + }; + m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); + + m_addComponentFunctions[typeid(T)] = [this](const Entity entity, const std::any& componentAny) { + T component = std::any_cast(componentAny); + this->addComponent(entity, component); + }; + + if constexpr (supports_memento_pattern_v) { + m_supportsMementoPattern.emplace(typeid(T), true); + + m_saveComponentFunctions[typeid(T)] = [](const std::any& componentAny) -> std::any { + const T& component = std::any_cast(componentAny); + return std::any(component.save()); }; - m_getComponentPointers[typeid(T)] = [this](const Entity entity) -> std::any { - auto opt = this->tryGetComponent(entity); - if (!opt.has_value()) - return {}; - T* ptr = &opt.value().get(); - return std::any(static_cast(ptr)); + m_restoreComponentFunctions[typeid(T)] = [](const std::any& mementoAny) -> std::any { + const typename T::Memento& memento = std::any_cast(mementoAny); + T component{}; + component.restore(memento); + return std::any(std::move(component)); }; - m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); - - m_addComponentFunctions[typeid(T)] = [this](const Entity entity, const std::any& componentAny) { - T component = std::any_cast(componentAny); - this->addComponent(entity, component); - }; - - if constexpr (supports_memento_pattern_v) { - m_supportsMementoPattern.emplace(typeid(T), true); - - m_saveComponentFunctions[typeid(T)] = [](const std::any& componentAny) -> std::any { - const T& component = std::any_cast(componentAny); - return std::any(component.save()); - }; - - m_restoreComponentFunctions[typeid(T)] = [](const std::any& mementoAny) -> std::any { - const typename T::Memento& memento = std::any_cast(mementoAny); - T component{}; - component.restore(memento); - return std::any(std::move(component)); - }; - } else { - m_supportsMementoPattern.emplace(typeid(T), false); - } - } - - void addComponentDescription(const ComponentType componentType, const ComponentDescription& description) - { - m_componentDescriptions[componentType] = std::make_shared(description); - } - - ComponentType registerComponent(const size_t componentSize, const size_t initialCapacity = 1024) - { - const auto typeID = m_componentManager->registerComponent(componentSize, initialCapacity); - return typeID; - } - - /** - * @brief Registers a new singleton component - * - * @tparam T Class that should inherit from SingletonComponent class - * @param args Optional argument to forward to the singleton component constructor - */ - template - void registerSingletonComponent(Args&&... args) - { - m_singletonComponentManager->registerSingletonComponent(std::forward(args)...); - } - - /** - * @brief Adds a component to an entity, updates its signature, and notifies systems. - * - * @param entity - The ID of the entity. - * @param component - The component to add to the entity. - */ - template - void addComponent(const Entity entity, T component) - { - Signature signature = m_entityManager->getSignature(entity); - const Signature oldSignature = signature; - signature.set(m_componentManager->getComponentType(), true); - m_componentManager->addComponent(entity, component, oldSignature, signature); - - m_entityManager->setSignature(entity, signature); - - m_systemManager->entitySignatureChanged(entity, oldSignature, signature); - } - - /** - * @brief Adds a component to an entity, updates its signature, and notifies systems. - * - * This function allows adding a component by its type ID and raw data pointer. - * - * @param entity The ID of the entity to which the component will be added. - * @param componentType The type ID of the component to be added. - * @param componentData Pointer to the raw component data. - * - * @pre componentType must be a valid registered component type. - * @pre componentData must point to valid memory of the component's size. - */ - void addComponent(const Entity entity, const ComponentType componentType, const void *componentData) const - { - Signature signature = m_entityManager->getSignature(entity); - const Signature oldSignature = signature; - signature.set(componentType, true); - m_componentManager->addComponent(entity, componentType, componentData, oldSignature, signature); - - m_entityManager->setSignature(entity, signature); - - m_systemManager->entitySignatureChanged(entity, oldSignature, signature); - } - - /** - * @brief Removes a component from an entity using ComponentType, updates its signature, and notifies systems. - * - * @param entity - The ID of the entity. - * @param componentType - The ID of the component type to remove. - */ - void removeComponent(const Entity entity, const ComponentType componentType) - { - Signature signature = m_entityManager->getSignature(entity); - const Signature oldSignature = signature; - - signature.set(componentType, false); - - m_componentManager->removeComponent(entity, componentType, oldSignature, signature); - - m_entityManager->setSignature(entity, signature); - m_systemManager->entitySignatureChanged(entity, oldSignature, signature); + } else { + m_supportsMementoPattern.emplace(typeid(T), false); } - - - /** - * @brief Removes a component from an entity, updates its signature, and notifies systems. - * - * This is the runtime equivalent of removeComponent(), useful when the component type - * is only known at runtime (e.g., from scripting APIs). - * - * @param entity - The ID of the entity. - */ - template - void removeComponent(const Entity entity) const - { - Signature signature = m_entityManager->getSignature(entity); - const Signature oldSignature = signature; - signature.set(m_componentManager->getComponentType(), false); - m_componentManager->removeComponent(entity, oldSignature, signature); - - + } + + /** + * @brief Adds a description for a component type. + * + * This description can include metadata such as the component's name, + * size, default values, serialization info, etc. + * + * @param componentType The type ID of the component. + * @param description The ComponentDescription object containing metadata. + */ + void addComponentDescription(const ComponentType componentType, const ComponentDescription& description) + { + m_componentDescriptions[componentType] = std::make_shared(description); + } + + /** + * @brief Registers a new component type with the specified size and initial capacity. + * + * This function allows the registration of a new component type in the ECS framework. + * It delegates the registration process to the ComponentManager and returns the + * unique ComponentType identifier for the registered component. + * + * @param componentSize The size of the component in bytes. + * @param initialCapacity The initial capacity for the component storage (default is 1024). + * @return ComponentType The unique identifier for the registered component type. + */ + [[nodiscard]] ComponentType registerComponent(const size_t componentSize, + const size_t initialCapacity = 1024) const + { + const auto typeID = m_componentManager->registerComponent(componentSize, initialCapacity); + return typeID; + } + + /** + * @brief Registers a new singleton component + * + * @tparam T Class that should inherit from SingletonComponent class + * @param args Optional argument to forward to the singleton component constructor + */ + template + void registerSingletonComponent(Args&&... args) + { + m_singletonComponentManager->registerSingletonComponent(std::forward(args)...); + } + + /** + * @brief Adds a component to an entity, updates its signature, and notifies systems. + * + * @param entity - The ID of the entity. + * @param component - The component to add to the entity. + */ + template + void addComponent(const Entity entity, T component) + { + Signature signature = m_entityManager->getSignature(entity); + const Signature oldSignature = signature; + signature.set(m_componentManager->getComponentType(), true); + m_componentManager->addComponent(entity, component, oldSignature, signature); + + m_entityManager->setSignature(entity, signature); + + m_systemManager->entitySignatureChanged(entity, oldSignature, signature); + } + + /** + * @brief Adds a component to an entity, updates its signature, and notifies systems. + * + * This function allows adding a component by its type ID and raw data pointer. + * + * @param entity The ID of the entity to which the component will be added. + * @param componentType The type ID of the component to be added. + * @param componentData Pointer to the raw component data. + * + * @pre componentType must be a valid registered component type. + * @pre componentData must point to valid memory of the component's size. + */ + void addComponent(const Entity entity, const ComponentType componentType, const void* componentData) const + { + Signature signature = m_entityManager->getSignature(entity); + const Signature oldSignature = signature; + signature.set(componentType, true); + m_componentManager->addComponent(entity, componentType, componentData, oldSignature, signature); + + m_entityManager->setSignature(entity, signature); + + m_systemManager->entitySignatureChanged(entity, oldSignature, signature); + } + + /** + * @brief Removes a component from an entity using ComponentType, updates its signature, and notifies systems. + * + * @param entity - The ID of the entity. + * @param componentType - The ID of the component type to remove. + */ + void removeComponent(const Entity entity, const ComponentType componentType) const + { + Signature signature = m_entityManager->getSignature(entity); + const Signature oldSignature = signature; + + signature.set(componentType, false); + + m_componentManager->removeComponent(entity, componentType, oldSignature, signature); + + m_entityManager->setSignature(entity, signature); + m_systemManager->entitySignatureChanged(entity, oldSignature, signature); + } + + /** + * @brief Removes a component from an entity, updates its signature, and notifies systems. + * + * This is the runtime equivalent of removeComponent(), useful when the component type + * is only known at runtime (e.g., from scripting APIs). + * + * @param entity - The ID of the entity. + */ + template + void removeComponent(const Entity entity) const + { + Signature signature = m_entityManager->getSignature(entity); + const Signature oldSignature = signature; + signature.set(m_componentManager->getComponentType(), false); + m_componentManager->removeComponent(entity, oldSignature, signature); + + m_entityManager->setSignature(entity, signature); + + m_systemManager->entitySignatureChanged(entity, oldSignature, signature); + } + + /** + * @brief Attempts to remove a component from an entity. + * + * If the component exists, it is removed and the entity's signature is updated. + * + * @tparam T The component type. + * @param entity The target entity identifier. + */ + template + void tryRemoveComponent(const Entity entity) const + { + Signature signature = m_entityManager->getSignature(entity); + Signature oldSignature = signature; + signature.set(m_componentManager->getComponentType(), false); + if (m_componentManager->tryRemoveComponent(entity, oldSignature, signature)) { m_entityManager->setSignature(entity, signature); m_systemManager->entitySignatureChanged(entity, oldSignature, signature); } - - /** - * @brief Attempts to remove a component from an entity. - * - * If the component exists, it is removed and the entity's signature is updated. - * - * @tparam T The component type. - * @param entity The target entity identifier. - */ - template - void tryRemoveComponent(const Entity entity) const - { - Signature signature = m_entityManager->getSignature(entity); - Signature oldSignature = signature; - signature.set(m_componentManager->getComponentType(), false); - if (m_componentManager->tryRemoveComponent(entity, oldSignature, signature)) - { - m_entityManager->setSignature(entity, signature); - - m_systemManager->entitySignatureChanged(entity, oldSignature, signature); - } - } - - /** - * @brief Remove a singleton component - * - * @tparam T Class that should inherit from the SingletonComponent class - */ - template - void removeSingletonComponent() const - { - m_singletonComponentManager->unregisterSingletonComponent(); - } - - /** - * @brief Retrieves a reference to a component of an entity. - * - * @param entity - The ID of the entity. - * @return T& - Reference to the requested component. - */ - template - T &getComponent(const Entity entity) - { - return m_componentManager->getComponent(entity); - } - - /** - * @brief Retrieves the component array for a specific component type - * - * @tparam T The component type - * @return std::shared_ptr> Shared pointer to the component array - */ - template - std::shared_ptr> getComponentArray() - { - return m_componentManager->getComponentArray(); - } - - /** - * @brief Attempts to retrieve a component from an entity. - * - * @tparam T The component type. - * @param entity The target entity identifier. - * @return std::optional> A reference to the component if it exists. - */ - template - std::optional> tryGetComponent(const Entity entity) - { - return m_componentManager->tryGetComponent(entity); - } - - void *tryGetComponentById(const ComponentType componentType, const Entity entity)const - { - return m_componentManager->tryGetComponent(entity, componentType); - } - - const std::unordered_map& getTypeIdToTypeIndex() const { - return m_typeIDtoTypeIndex; - } - - const std::unordered_map>& getAddComponentFunctions() const { - return m_addComponentFunctions; - } - - Signature getSignature(const Entity entity) const { - return m_entityManager->getSignature(entity); - } - - /** - * @brief Get the Singleton Component object - * - * @tparam T Class that should inherit from the SingletonComponent class - * @return T& The instance of the desired singleton component - */ - template - T &getSingletonComponent() - { - return m_singletonComponentManager->getSingletonComponent(); - } - - /** - * @brief Get the Raw Singleton Component object - * - * @tparam T Class that should inherit from the SingletonComponent class - * @return std::shared_ptr The pointer to the desired singleton component - */ - template - std::shared_ptr getRawSingletonComponent() const - { - return m_singletonComponentManager->getRawSingletonComponent(); - } - - /** - * @brief Retrieves all component types associated with an entity. - * - * @param entity The target entity identifier. - * @return std::vector A list of type indices for each component the entity has. - */ - std::vector getAllComponentTypes(Entity entity) const; - - std::vector getAllComponentTypeIndices(Entity entity) const; - - /** - * @brief Retrieves all components associated with an entity. - * - * @param entity The target entity identifier. - * @return std::vector> A vector of pairs, each containing a component type and its instance. - */ - std::vector getAllComponents(Entity entity); - - /** - * @brief Retrieves all entities that have the specified components. - * - * @tparam Components The component types to filter by. - * @return std::set A set of entities that contain all the specified components. - */ - template - std::vector getAllEntitiesWith() const - { - // Prepare signatures - Signature requiredSignature; - Signature excludeSignature; - - // Process each component type - (processComponentSignature(requiredSignature, excludeSignature), ...); - - // Query entities - std::span livingEntities = m_entityManager->getLivingEntities(); - std::vector result; - result.reserve(livingEntities.size()); - - for (Entity entity : livingEntities) - { - const Signature entitySignature = m_entityManager->getSignature(entity); - - // Entity must have all required components - const bool hasAllRequired = (entitySignature & requiredSignature) == requiredSignature; - - // Entity must not have any excluded components - const bool hasAnyExcluded = (entitySignature & excludeSignature).any(); - - if (hasAllRequired && !hasAnyExcluded) - result.push_back(entity); - } - - return result; - } - - /** - * @brief Gets the component type ID for a specific component type. - * - * @return components::ComponentType - The ID of the component type. - */ - template - ComponentType getComponentType() const - { - return m_componentManager->getComponentType(); - } - - /** - * @brief Retrieves all registered component descriptions. - * - * @return const std::unordered_map>& - * A const reference to the map of component types to their descriptions. - */ - const std::unordered_map>& getComponentDescriptions() const - { - return m_componentDescriptions; + } + + /** + * @brief Remove a singleton component + * + * @tparam T Class that should inherit from the SingletonComponent class + */ + template + void removeSingletonComponent() const + { + m_singletonComponentManager->unregisterSingletonComponent(); + } + + /** + * @brief Retrieves a reference to a component of an entity. + * + * @param entity - The ID of the entity. + * @return T& - Reference to the requested component. + */ + template + T& getComponent(const Entity entity) + { + return m_componentManager->getComponent(entity); + } + + /** + * @brief Retrieves the component array for a specific component type + * + * @tparam T The component type + * @return std::shared_ptr> Shared pointer to the component array + */ + template + std::shared_ptr> getComponentArray() + { + return m_componentManager->getComponentArray(); + } + + /** + * @brief Attempts to retrieve a component from an entity. + * + * @tparam T The component type. + * @param entity The target entity identifier. + * @return std::optional> A reference to the component if it exists. + */ + template + std::optional> tryGetComponent(const Entity entity) + { + return m_componentManager->tryGetComponent(entity); + } + + /** + * @brief Attempts to retrieve a component from an entity by its ComponentType. + * + * @param componentType The type ID of the component. + * @param entity The target entity identifier. + * @return void* Pointer to the component if it exists, or nullptr if not found. + */ + [[nodiscard]] void* tryGetComponentById(const ComponentType componentType, const Entity entity) const + { + return m_componentManager->tryGetComponent(entity, componentType); + } + + /** + * @brief Retrieves the mapping between component types and their corresponding type indices. + * + * This function provides access to the internal mapping that associates each + * registered component type with its corresponding `std::type_index`. + * + * @return const std::unordered_map& + * A constant reference to the mapping of component types to type indices. + */ + [[nodiscard]] const std::unordered_map& getTypeIdToTypeIndex() const + { + return m_typeIDtoTypeIndex; + } + + /** + * @brief Retrieves the mapping of functions used to add components to entities. + * + * This function provides access to the internal mapping that associates each + * component type with its corresponding function for adding components to entities. + * + * @return const std::unordered_map>& + * A constant reference to the mapping of component addition functions. + */ + [[nodiscard]] const std::unordered_map>& + getAddComponentFunctions() const + { + return m_addComponentFunctions; + } + + /** + * @brief Retrieves the signature of a specific entity. + * + * This function returns the signature associated with the given entity, which + * represents the set of components currently assigned to the entity. + * + * @param entity The ID of the entity whose signature is to be retrieved. + * @return Signature The signature of the specified entity. + */ + [[nodiscard]] Signature getSignature(const Entity entity) const + { + return m_entityManager->getSignature(entity); + } + + /** + * @brief Get the Singleton Component object + * + * @tparam T Class that should inherit from the SingletonComponent class + * @return T& The instance of the desired singleton component + */ + template + T& getSingletonComponent() + { + return m_singletonComponentManager->getSingletonComponent(); + } + + /** + * @brief Get the Raw Singleton Component object + * + * @tparam T Class that should inherit from the SingletonComponent class + * @return std::shared_ptr The pointer to the desired singleton component + */ + template + [[nodiscard]] std::shared_ptr getRawSingletonComponent() const + { + return m_singletonComponentManager->getRawSingletonComponent(); + } + + /** + * @brief Retrieves all component types associated with an entity. + * + * @param entity The target entity identifier. + * @return std::vector A list of type indices for each component the entity has. + */ + [[nodiscard]] std::vector getAllComponentTypes(Entity entity) const; + + [[nodiscard]] std::vector getAllComponentTypeIndices(Entity entity) const; + + /** + * @brief Retrieves all components associated with an entity. + * + * @param entity The target entity identifier. + * @return std::vector> A vector of pairs, each containing a component type + * and its instance. + */ + std::vector getAllComponents(Entity entity); + + /** + * @brief Retrieves all entities that have the specified components. + * + * @tparam Components The component types to filter by. + * @return std::set A set of entities that contain all the specified components. + */ + template + [[nodiscard]] std::vector getAllEntitiesWith() const + { + // Prepare signatures + Signature requiredSignature; + Signature excludeSignature; + + // Process each component type + (processComponentSignature(requiredSignature, excludeSignature), ...); + + // Query entities + std::span livingEntities = m_entityManager->getLivingEntities(); + std::vector result; + result.reserve(livingEntities.size()); + + for (Entity entity : livingEntities) { + const Signature entitySignature = m_entityManager->getSignature(entity); + + // Entity must have all required components + const bool hasAllRequired = (entitySignature & requiredSignature) == requiredSignature; + + // Entity must not have any excluded components + const bool hasAnyExcluded = (entitySignature & excludeSignature).any(); + + if (hasAllRequired && !hasAnyExcluded) result.push_back(entity); } - /** - * @brief Registers a new query system - * - * @tparam T The system type to register - * @tparam Args Additional constructor arguments - * @return std::shared_ptr Shared pointer to the registered system - */ - template - std::shared_ptr registerQuerySystem(Args&&... args) { - auto newQuerySystem = m_systemManager->registerQuerySystem(std::forward(args)...); - std::span livingEntities = m_entityManager->getLivingEntities(); - const Signature querySystemSignature = newQuerySystem->getSignature(); - for (Entity entity : livingEntities) { - const Signature entitySignature = m_entityManager->getSignature(entity); - if ((entitySignature & querySystemSignature) == querySystemSignature) { - newQuerySystem->entities.insert(entity); - } + return result; + } + + /** + * @brief Gets the component type ID for a specific component type. + * + * @return components::ComponentType - The ID of the component type. + */ + template + [[nodiscard]] ComponentType getComponentType() const + { + return m_componentManager->getComponentType(); + } + + /** + * @brief Retrieves all registered component descriptions. + * + * @return const std::unordered_map>& + * A const reference to the map of component types to their descriptions. + */ + [[nodiscard]] const std::unordered_map>& + getComponentDescriptions() const + { + return m_componentDescriptions; + } + + /** + * @brief Registers a new query system + * + * @tparam T The system type to register + * @tparam Args Additional constructor arguments + * @return std::shared_ptr Shared pointer to the registered system + */ + template + std::shared_ptr registerQuerySystem(Args&&... args) + { + auto newQuerySystem = m_systemManager->registerQuerySystem(std::forward(args)...); + std::span livingEntities = m_entityManager->getLivingEntities(); + const Signature querySystemSignature = newQuerySystem->getSignature(); + for (Entity entity : livingEntities) { + const Signature entitySignature = m_entityManager->getSignature(entity); + if ((entitySignature & querySystemSignature) == querySystemSignature) { + newQuerySystem->entities.insert(entity); } - return newQuerySystem; - } - - /** - * @brief Registers a new group system - * - * @tparam T The system type to register - * @tparam Args Additional constructor arguments - * @return std::shared_ptr Shared pointer to the registered system - */ - template - std::shared_ptr registerGroupSystem(Args&&... args) { - return m_systemManager->registerGroupSystem(std::forward(args)...); - } - - /** - * @brief Creates or retrieves a group for specific component combinations - * - * @tparam Owned Component types that are owned by the group - * @param nonOwned A get_t<...> tag specifying non-owned component types - * @return A shared pointer to the group (either existing or newly created) - */ - template - auto registerGroup(const auto & nonOwned) - { - return m_componentManager->registerGroup(nonOwned); - } - - /** - * @brief Retrieves an existing group for specific component combinations - * - * @tparam Owned Component types that are owned by the group - * @param nonOwned A get_t<...> tag specifying non-owned component types - * @return A shared pointer to the existing group - */ - template - auto getGroup(const auto& nonOwned) - { - return m_componentManager->getGroup(nonOwned); - } - - /** - * @brief Sets the signature for a system, defining which entities it will process. - * - * @param signature - The signature to associate with the system. - */ - template - void setSystemSignature(const Signature signature) const - { - m_systemManager->setSignature(signature); - } - - /** - * @brief Checks whether an entity has a specific component. - * - * @tparam T The component type. - * @param entity The target entity. - * @return true If the entity has the component. - * @return false Otherwise. - */ - template - [[nodiscard]] bool entityHasComponent(const Entity entity) const - { - const ComponentType componentType = m_componentManager->getComponentType(); - return entityHasComponent(entity, componentType); } - - /** - * @brief Checks whether an entity has a specific component by its type ID. - * - * @param entity The target entity. - * @param componentType The type ID of the component. - * @return true If the entity has the component. - * @return false Otherwise. - */ - [[nodiscard]] bool entityHasComponent(const Entity entity, const ComponentType componentType) const - { - const Signature signature = m_entityManager->getSignature(entity); - return signature.test(componentType); - } - - template - void setRestoreComponent() { - m_restoreComponentFunctions[typeid(T)] = [](const std::any&) -> std::any { - return std::any(T{}); - }; - } - - - bool supportsMementoPattern(const std::any& component) const; - std::any saveComponent(const std::any& component) const; - std::any restoreComponent(const std::any& memento, const std::type_index& componentType) const; - void addComponentAny(Entity entity, const std::type_index& typeIndex, const std::any& component); - - Entity duplicateEntity(Entity sourceEntity) const; - - /** - * @brief Retrieves all entities that have all the specified components. - * - * This method iterates over all entities and returns a list of those that possess - * each of the given component types. It uses the current signature of each entity - * to determine component ownership. - * - * @tparam ComponentTypes - A variadic list of component types to filter by. - * @return std::vector - A list of entities matching all specified component types. - */ - template - std::vector getEntitiesWithComponents() const - { - std::vector result; - std::span livingEntities = m_entityManager->getLivingEntities(); - result.reserve(livingEntities.size()); - for (Entity entity : livingEntities) - { - const bool hasAll = (entityHasComponent(entity) && ...); - if (hasAll) - { - result.push_back(entity); - } + return newQuerySystem; + } + + /** + * @brief Registers a new group system + * + * @tparam T The system type to register + * @tparam Args Additional constructor arguments + * @return std::shared_ptr Shared pointer to the registered system + */ + template + std::shared_ptr registerGroupSystem(Args&&... args) + { + return m_systemManager->registerGroupSystem(std::forward(args)...); + } + + /** + * @brief Creates or retrieves a group for specific component combinations + * + * @tparam Owned Component types that are owned by the group + * @param nonOwned A get_t<...> tag specifying non-owned component types + * @return A shared pointer to the group (either existing or newly created) + */ + template + auto registerGroup(const auto& nonOwned) + { + return m_componentManager->registerGroup(nonOwned); + } + + /** + * @brief Retrieves an existing group for specific component combinations + * + * @tparam Owned Component types that are owned by the group + * @param nonOwned A get_t<...> tag specifying non-owned component types + * @return A shared pointer to the existing group + */ + template + auto getGroup(const auto& nonOwned) + { + return m_componentManager->getGroup(nonOwned); + } + + /** + * @brief Sets the signature for a system, defining which entities it will process. + * + * @param signature - The signature to associate with the system. + */ + template + void setSystemSignature(const Signature signature) const + { + m_systemManager->setSignature(signature); + } + + /** + * @brief Checks whether an entity has a specific component. + * + * @tparam T The component type. + * @param entity The target entity. + * @return true If the entity has the component. + * @return false Otherwise. + */ + template + [[nodiscard]] bool entityHasComponent(const Entity entity) const + { + const ComponentType componentType = m_componentManager->getComponentType(); + return entityHasComponent(entity, componentType); + } + + /** + * @brief Checks whether an entity has a specific component by its type ID. + * + * @param entity The target entity. + * @param componentType The type ID of the component. + * @return true If the entity has the component. + * @return false Otherwise. + */ + [[nodiscard]] bool entityHasComponent(const Entity entity, const ComponentType componentType) const + { + const Signature signature = m_entityManager->getSignature(entity); + return signature.test(componentType); + } + + /** + * @brief Sets a default save function for a component type that does not support the Memento pattern. + * + * This function registers a default save function for the specified component type T. + * The default function simply returns a default-constructed instance of T wrapped in a std::any. + * This is useful for component types that do not implement the Memento pattern but still need + * to be handled in a uniform way. + * + * @tparam T The component type for which to set the default save function. + */ + template + void setRestoreComponent() + { + m_restoreComponentFunctions[typeid(T)] = [](const std::any&) -> std::any { return std::any(T{}); }; + } + + /** + * @brief Checks if a component type supports the Memento pattern. + * + * This method checks if the given component type has the necessary methods + * to support saving and restoring its state using the Memento design pattern. + * + * @tparam T The component type to check. + * @return true if the component type supports the Memento pattern, false otherwise. + */ + [[nodiscard]] bool supportsMementoPattern(const std::any& component) const; + + /** + * @brief Saves the state of a component using the Memento pattern. + * + * This method invokes the save function of the component to capture its current state + * and returns it as a std::any object. The component must support the Memento pattern. + * + * @param component The component instance to save. + * @return std::any containing the saved state (memento) of the component. + * @throws std::bad_any_cast if the component does not support the Memento pattern. + */ + [[nodiscard]] std::any saveComponent(const std::any& component) const; + + /** + * @brief Restores a component's state from a memento. + * + * This method takes a memento object and the type of the component to restore. + * It reconstructs the component's state from the memento and returns the restored + * component as a std::any object. The component type must support the Memento pattern. + * + * @param memento The memento object containing the saved state. + * @param componentType The type index of the component to restore. + * @return std::any containing the restored component instance. + * @throws std::bad_any_cast if the component type does not support the Memento pattern + * or if the memento is of an incompatible type. + */ + [[nodiscard]] std::any restoreComponent(const std::any& memento, const std::type_index& componentType) const; + + /** + * @brief Adds a component to an entity using type erasure. + * + * This method allows adding a component to an entity when the component type + * is only known at runtime. It uses type erasure to handle the component generically. + * + * @param entity The ID of the entity to which the component will be added. + * @param typeIndex The type index of the component to add. + * @param component The component instance wrapped in a std::any. + * + * @throws std::bad_any_cast if the component cannot be cast to the expected type. + * @throws ComponentNotRegistered if the component type is not registered. + */ + void addComponentAny(Entity entity, const std::type_index& typeIndex, const std::any& component); + + /** + * @brief Retrieves a component from an entity using type erasure. + * + * This method allows retrieving a component from an entity when the component type + * is only known at runtime. It returns the component wrapped in a std::any. + * + * @param entity The ID of the entity from which to retrieve the component. + * @param typeIndex The type index of the component to retrieve. + * @return std::any containing the component instance, or an empty std::any if not found. + * + * @throws ComponentNotRegistered if the component type is not registered. + */ + [[nodiscard]] std::any getComponentAny(Entity entity, const std::type_index& typeIndex) const; + + /** + * @brief Retrieves a pointer to a component from an entity using type erasure. + * + * This method allows retrieving a pointer to a component from an entity when the + * component type is only known at runtime. It returns the component pointer wrapped + * in a std::any. + * + * @param entity The ID of the entity from which to retrieve the component pointer. + * @param typeIndex The type index of the component to retrieve. + * @return std::any containing a void* pointer to the component, or an empty std::any if not found. + * + * @throws ComponentNotRegistered if the component type is not registered. + */ + [[nodiscard]] std::any getComponentPointer(Entity entity, const std::type_index& typeIndex) const; + + /** + * @brief Duplicates an entity along with all its components. + * + * This method creates a new entity and copies all components from the source entity + * to the new entity. It uses the registered component addition functions to ensure + * that each component is properly added to the new entity. + * + * @param sourceEntity The ID of the entity to duplicate. + * @return Entity The ID of the newly created duplicate entity. + * + * @throws ComponentNotRegistered if any component type of the source entity is not registered. + */ + [[nodiscard]] Entity duplicateEntity(Entity sourceEntity) const; + + /** + * @brief Retrieves all entities that have all the specified components. + * + * This method iterates over all entities and returns a list of those that possess + * each of the given component types. It uses the current signature of each entity + * to determine component ownership. + * + * @tparam ComponentTypes - A variadic list of component types to filter by. + * @return std::vector - A list of entities matching all specified component types. + */ + template + [[nodiscard]] std::vector getEntitiesWithComponents() const + { + std::vector result; + std::span livingEntities = m_entityManager->getLivingEntities(); + result.reserve(livingEntities.size()); + for (Entity entity : livingEntities) { + const bool hasAll = (entityHasComponent(entity) && ...); + if (hasAll) { + result.push_back(entity); } - return result; } - + return result; + } + + /** + * @brief Updates all system entities based on current entity signatures. + * + * This method iterates through all registered systems and updates their + * entity lists according to the current signatures of all entities. It ensures + * that each system processes only the entities that match its signature criteria. + */ void updateSystemEntities() const; - private: - template - void processComponentSignature(Signature& required, Signature& excluded) const { - if constexpr (is_exclude_v) { - // This is an excluded component - using ActualType = typename Component::type; - excluded.set(m_componentManager->getComponentType(), true); - } else { - // This is a required component - required.set(m_componentManager->getComponentType(), true); - } + private: + /** + * @brief Processes a component type to update required and excluded signatures. + * + * This helper function checks if the given component type is wrapped in an Exclude + * template. If it is, the actual component type is added to the excluded signature. + * Otherwise, it is added to the required signature. This allows for flexible querying + * of entities based on both required and excluded components. + * + * @tparam Component The component type to process, which may be wrapped in Exclude. + * @param required Reference to the signature representing required components. + * @param excluded Reference to the signature representing excluded components. + */ + template + void processComponentSignature(Signature& required, Signature& excluded) const + { + if constexpr (is_exclude_v) { + // This is an excluded component + using ActualType = typename Component::type; + excluded.set(m_componentManager->getComponentType(), true); + } else { + // This is a required component + required.set(m_componentManager->getComponentType(), true); } - - std::shared_ptr m_componentManager; - std::shared_ptr m_entityManager; - std::shared_ptr m_systemManager; - std::shared_ptr m_singletonComponentManager; - - std::unordered_map m_typeIDtoTypeIndex; - std::unordered_map m_supportsMementoPattern; - std::unordered_map> m_saveComponentFunctions; - std::unordered_map> m_restoreComponentFunctions; - std::unordered_map> m_addComponentFunctions; - std::unordered_map> m_getComponentFunctions; - std::unordered_map> m_getComponentPointers; - - std::unordered_map> m_componentDescriptions; + } + + std::shared_ptr m_componentManager; + std::shared_ptr m_entityManager; + std::shared_ptr m_systemManager; + std::shared_ptr m_singletonComponentManager; + + std::unordered_map m_typeIDtoTypeIndex; + std::unordered_map m_supportsMementoPattern; + std::unordered_map> m_saveComponentFunctions; + std::unordered_map> m_restoreComponentFunctions; + std::unordered_map> m_addComponentFunctions; + std::unordered_map> m_getComponentFunctions; + std::unordered_map> m_getComponentPointers; + + std::unordered_map> m_componentDescriptions; }; -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/Definitions.hpp b/engine/src/ecs/Definitions.hpp index 0d1fe0a7b..6b279c6bd 100644 --- a/engine/src/ecs/Definitions.hpp +++ b/engine/src/ecs/Definitions.hpp @@ -14,66 +14,74 @@ #pragma once -#include -#include #include #include +#include +#include namespace nexo::ecs { - // Entity type definition - - /** - * @brief Entity identifier type - * - * Used to uniquely identify entities in the ECS. - */ - using Entity = std::uint32_t; - - /** - * @brief Maximum number of entities that can exist simultaneously - */ - constexpr Entity MAX_ENTITIES = 500000; - - /** - * @brief Special value representing an invalid or non-existent entity - */ - constexpr Entity INVALID_ENTITY = std::numeric_limits::max(); - - // Component type definitions - - /** - * @brief Component type identifier - * - * Used to uniquely identify different component types. - */ - using ComponentType = std::uint8_t; - - /** - * @brief Maximum number of different component types in the system - */ - constexpr ComponentType MAX_COMPONENT_TYPE = 32; - - /** - * @brief Global counter for generating unique component type IDs - */ - inline ComponentType globalComponentCounter = 0; + // Entity type definition + + /** + * @brief Entity identifier type + * + * Used to uniquely identify entities in the ECS. + */ + using Entity = std::uint32_t; + + /** + * @brief Maximum number of entities that can exist simultaneously + */ + constexpr Entity MAX_ENTITIES = 500000; + + /** + * @brief Special value representing an invalid or non-existent entity + */ + constexpr Entity INVALID_ENTITY = std::numeric_limits::max(); + + // Component type definitions + + /** + * @brief Component type identifier + * + * Used to uniquely identify different component types. + */ + using ComponentType = std::uint8_t; + + /** + * @brief Maximum number of different component types in the system + */ + constexpr ComponentType MAX_COMPONENT_TYPE = 32; + + /** + * @brief Global counter for generating unique component type IDs + */ + inline ComponentType globalComponentCounter = 0; + /** + * @brief Generates a new unique component type ID + * + * Increments the global component counter and returns the new ID. + * Asserts if the maximum number of component types is exceeded. + * + * @return ComponentType New unique component type ID + */ inline ComponentType generateComponentTypeID() { assert(globalComponentCounter < MAX_COMPONENT_TYPE && "Maximum number of component types exceeded"); return globalComponentCounter++; } - /** - * @brief Gets a unique ID for a component type - * - * Returns a statically allocated ID for each unique component type T. - * The first call for a type T will assign a new ID; subsequent calls - * for the same type will return the previously assigned ID. - * - * @tparam T Component type - * @return ComponentType Unique ID for the type - */ + /** + * @brief Gets a unique ID for a component type + * + * Returns a statically allocated ID for each unique component type T. + * The first call for a type T will assign a new ID; subsequent calls + * for the same type will return the previously assigned ID. + * + * @tparam T Component type + * @return ComponentType Unique ID for the type + */ template ComponentType getUniqueComponentTypeID() { @@ -90,31 +98,31 @@ namespace nexo::ecs { * @tparam T Component type * @return ComponentType ID for the component type */ - template - ComponentType getComponentTypeID() - { - return getUniqueComponentTypeID>(); - } - - // Group type definition - - /** - * @brief Group identifier type - * - * Used to uniquely identify different entity groups. - */ - using GroupType = std::uint8_t; - - /** - * @brief Maximum number of groups that can exist simultaneously - */ - constexpr GroupType MAX_GROUP_NUMBER = 32; - - /** - * @brief Signature type for component composition - * - * A bitset where each bit represents whether an entity has a specific component type. - */ - using Signature = std::bitset; - -} + template + ComponentType getComponentTypeID() + { + return getUniqueComponentTypeID>(); + } + + // Group type definition + + /** + * @brief Group identifier type + * + * Used to uniquely identify different entity groups. + */ + using GroupType = std::uint8_t; + + /** + * @brief Maximum number of groups that can exist simultaneously + */ + constexpr GroupType MAX_GROUP_NUMBER = 32; + + /** + * @brief Signature type for component composition + * + * A bitset where each bit represents whether an entity has a specific component type. + */ + using Signature = std::bitset; + +} // namespace nexo::ecs diff --git a/engine/src/ecs/ECSExceptions.hpp b/engine/src/ecs/ECSExceptions.hpp index e1c824197..3aee6e804 100644 --- a/engine/src/ecs/ECSExceptions.hpp +++ b/engine/src/ecs/ECSExceptions.hpp @@ -13,76 +13,147 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "Exception.hpp" #include "Definitions.hpp" +#include "Exception.hpp" -#include #include +#include namespace nexo::ecs { class InternalError final : public Exception { - public: + public: + /** + * @brief Exception thrown for internal errors in the ECS + * + * @param message The error message describing the internal error. + * @param loc The source location where the exception was thrown (default is current location). + */ explicit InternalError(const std::string& message, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Internal error: {}", message), loc) {} + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Internal error: {}", message), loc) + {} }; class ComponentNotFound final : public Exception { - public: - explicit ComponentNotFound(const Entity entity, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Component not found for: {}", entity), loc) {} + public: + /** + * @brief Exception thrown when a component is not found for a given entity. + * + * @param entity The entity for which the component was not found. + * @param loc The source location where the exception was thrown (default is current location). + */ + explicit ComponentNotFound(const Entity entity, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Component not found for: {}", entity), loc) + {} }; class OverlappingGroupsException final : public Exception { - public: - explicit OverlappingGroupsException(const std::string& existingGroup, - const std::string& newGroup, ComponentType conflictingComponent, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Cannot create group {} because it has overlapping owned component #{} with existing group {}", newGroup, conflictingComponent, existingGroup), loc) {} + public: + /** + * @brief Exception thrown when attempting to create a group with overlapping owned components. + * + * @param existingGroup The name of the existing group that has the conflicting component. + * @param newGroup The name of the new group being created. + * @param conflictingComponent The component type that is causing the overlap. + * @param loc The source location where the exception was thrown (default is current location). + */ + explicit OverlappingGroupsException(const std::string& existingGroup, const std::string& newGroup, + ComponentType conflictingComponent, + const std::source_location loc = std::source_location::current()) + : Exception( + std::format( + "Cannot create group {} because it has overlapping owned component #{} with existing group {}", + newGroup, conflictingComponent, existingGroup), + loc) + {} }; class GroupNotFound final : public Exception { - public: - explicit GroupNotFound(const std::string &groupKey, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Group not found for key: {}", groupKey), loc) {} + public: + /** + * @brief Exception thrown when a requested group is not found. + * + * @param groupKey The key identifying the group that was not found. + * @param loc The source location where the exception was thrown (default is current location). + */ + explicit GroupNotFound(const std::string& groupKey, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Group not found for key: {}", groupKey), loc) + {} }; class InvalidGroupComponent final : public Exception { - public: - explicit InvalidGroupComponent(const std::source_location loc = std::source_location::current()) - : Exception("Component has not been found in the group", loc) {} + public: + /** + * @brief Exception thrown when a component is not found in the group. + * + * @param loc The source location where the exception was thrown (default is current location). + */ + explicit InvalidGroupComponent(const std::source_location loc = std::source_location::current()) + : Exception("Component has not been found in the group", loc) + {} }; class ComponentNotRegistered final : public Exception { - public: - explicit ComponentNotRegistered(const std::source_location loc = std::source_location::current()) - : Exception("Component has not been registered before use", loc) {} + public: + /** + * @brief Exception thrown when a component type is not registered in the ECS. + * + * @param loc The source location where the exception was thrown (default is current location). + */ + explicit ComponentNotRegistered(const std::source_location loc = std::source_location::current()) + : Exception("Component has not been registered before use", loc) + {} }; class SingletonComponentNotRegistered final : public Exception { - public: - explicit SingletonComponentNotRegistered(const std::source_location loc = std::source_location::current()) - : Exception("Singleton component has not been registered before use", loc) {} + public: + /** + * @brief Exception thrown when a singleton component type is not registered in the ECS. + * + * @param loc The source location where the exception was thrown (default is current location). + */ + explicit SingletonComponentNotRegistered(const std::source_location loc = std::source_location::current()) + : Exception("Singleton component has not been registered before use", loc) + {} }; class SystemNotRegistered final : public Exception { - public: - explicit SystemNotRegistered(const std::source_location loc = std::source_location::current()) - : Exception("System has not been registered before use", loc) {} + public: + /** + * @brief Exception thrown when a system type is not registered in the ECS. + * + * @param loc The source location where the exception was thrown (default is current location). + */ + explicit SystemNotRegistered(const std::source_location loc = std::source_location::current()) + : Exception("System has not been registered before use", loc) + {} }; class TooManyEntities final : public Exception { - public: - explicit TooManyEntities(const std::source_location loc = std::source_location::current()) - : Exception(std::format("Too many living entities, max is {}", MAX_ENTITIES), loc) {} + public: + /** + * @brief Exception thrown when the maximum number of living entities is exceeded. + * + * @param loc The source location where the exception was thrown (default is current location). + */ + explicit TooManyEntities(const std::source_location loc = std::source_location::current()) + : Exception(std::format("Too many living entities, max is {}", MAX_ENTITIES), loc) + {} }; class OutOfRange final : public Exception { - public: - explicit OutOfRange(size_t index, const std::source_location loc = std::source_location::current()) - : Exception(std::format("Index {} is out of range", index), loc) {} + public: + /** + * @brief Exception thrown when an index is out of the valid range. + * + * @param index The out-of-range index. + * @param loc The source location where the exception was thrown (default is current location). + */ + explicit OutOfRange(size_t index, const std::source_location loc = std::source_location::current()) + : Exception(std::format("Index {} is out of range", index), loc) + {} }; -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/Entity.cpp b/engine/src/ecs/Entity.cpp index b54f745f8..f569adca0 100644 --- a/engine/src/ecs/Entity.cpp +++ b/engine/src/ecs/Entity.cpp @@ -16,21 +16,37 @@ #include "ECSExceptions.hpp" #include -#include #include namespace nexo::ecs { + /** + * @class EntityManager + * @brief Manages the creation, destruction, and signatures of entities in the ECS. + * + * The EntityManager is responsible for: + * - Creating and destroying entities + * - Managing entity signatures + * - Keeping track of living entities + */ EntityManager::EntityManager() { - for (Entity entity = 0; entity < MAX_ENTITIES; ++entity) - m_availableEntities.push_back(entity); + for (Entity entity = 0; entity < MAX_ENTITIES; ++entity) m_availableEntities.push_back(entity); } + /** + * @brief Creates a new entity and assigns it a unique ID. + * + * This function retrieves an available entity ID from the pool of unused entities, + * removes it from the list of available entities, and adds it to the list of living entities. + * If the maximum number of entities is exceeded, an exception is thrown. + * + * @throws TooManyEntities If the number of living entities exceeds the maximum allowed. + * @return Entity The unique ID of the newly created entity. + */ Entity EntityManager::createEntity() { - if (m_livingEntities.size() >= MAX_ENTITIES) - THROW_EXCEPTION(TooManyEntities); + if (m_livingEntities.size() >= MAX_ENTITIES) THROW_EXCEPTION(TooManyEntities); const Entity id = m_availableEntities.front(); m_availableEntities.pop_front(); @@ -39,10 +55,19 @@ namespace nexo::ecs { return id; } + /** + * @brief Destroys an entity, freeing its ID for future use. + * + * This function removes the entity from the list of living entities, + * resets its signature, and adds its ID back to the pool of available entities. + * If the entity ID is out of range or does not exist, an exception is thrown. + * + * @param entity The ID of the entity to be destroyed. + * @throws OutOfRange If the entity ID exceeds the maximum allowed. + */ void EntityManager::destroyEntity(const Entity entity) { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); + if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); const auto it = std::ranges::find(m_livingEntities, entity); if (it != m_livingEntities.end()) @@ -52,33 +77,67 @@ namespace nexo::ecs { m_signatures[entity].reset(); m_availableEntities.push_front(entity); - } + /** + * @brief Sets the signature for a specific entity. + * + * This function updates the component signature associated with the given entity ID. + * If the entity ID is out of range, an exception is thrown. + * + * @param entity The ID of the entity whose signature is to be set. + * @param signature The new signature to associate with the entity. + * @throws OutOfRange If the entity ID exceeds the maximum allowed. + */ void EntityManager::setSignature(const Entity entity, const Signature signature) { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); + if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); m_signatures[entity] = signature; } + /** + * @brief Retrieves the signature of a specific entity. + * + * This function returns the component signature associated with the given entity ID. + * If the entity ID is out of range, an exception is thrown. + * + * @param entity The ID of the entity whose signature is to be retrieved. + * @throws OutOfRange If the entity ID exceeds the maximum allowed. + * @return Signature The signature associated with the entity. + */ Signature EntityManager::getSignature(const Entity entity) const { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); + if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); return m_signatures[entity]; } + /** + * @brief Gets the current count of living entities. + * + * This function returns the number of entities that are currently active (living) + * in the ECS. It provides a way to monitor the total number of entities being managed. + * + * @return size_t The count of living entities. + */ size_t EntityManager::getLivingEntityCount() const { return m_livingEntities.size(); } + /** + * @brief Retrieves a span of all currently living entities. + * + * This function returns a read-only span containing the IDs of all entities + * that are currently active (living) in the ECS. It allows for efficient + * iteration over the list of living entities without copying the data. + * + * @return std::span A span of living entity IDs. + */ std::span EntityManager::getLivingEntities() const { return {m_livingEntities}; } -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/Entity.hpp b/engine/src/ecs/Entity.hpp index 9da777be7..b53342c05 100644 --- a/engine/src/ecs/Entity.hpp +++ b/engine/src/ecs/Entity.hpp @@ -14,83 +14,83 @@ #pragma once -#include #include -#include +#include #include +#include #include "Definitions.hpp" namespace nexo::ecs { /** - * @class EntityManager - * - * @brief Manages entities in an ECS (Entity-Component-System) architecture. - * - * This class is responsible for creating, managing, and destroying entities. It maintains - * a record of active entities and their signatures, which define the components associated with each entity. - */ + * @class EntityManager + * + * @brief Manages entities in an ECS (Entity-Component-System) architecture. + * + * This class is responsible for creating, managing, and destroying entities. It maintains + * a record of active entities and their signatures, which define the components associated with each entity. + */ class EntityManager { - public: - /** - * @brief Constructor for EntityManager. - * - * Initializes the pool of available entities. Each entity is represented by a unique ID. - */ - EntityManager(); + public: + /** + * @brief Constructor for EntityManager. + * + * Initializes the pool of available entities. Each entity is represented by a unique ID. + */ + EntityManager(); - /** - * @brief Creates a new entity. - * - * Assigns a unique ID to the new entity and tracks it as an active entity. - * @return Entity - The ID of the newly created entity. - */ - Entity createEntity(); + /** + * @brief Creates a new entity. + * + * Assigns a unique ID to the new entity and tracks it as an active entity. + * @return Entity - The ID of the newly created entity. + */ + Entity createEntity(); - /** - * @brief Destroys an entity. - * - * Marks the entity as inactive and returns its ID to the pool of available IDs. - * @param entity - The ID of the entity to be destroyed. - */ - void destroyEntity(Entity entity); + /** + * @brief Destroys an entity. + * + * Marks the entity as inactive and returns its ID to the pool of available IDs. + * @param entity - The ID of the entity to be destroyed. + */ + void destroyEntity(Entity entity); - /** - * @brief Sets the signature of an entity. - * - * The signature defines which components are associated with the entity. - * @param entity - The ID of the entity. - * @param signature - The signature to be set for the entity. - */ - void setSignature(Entity entity, Signature signature); + /** + * @brief Sets the signature of an entity. + * + * The signature defines which components are associated with the entity. + * @param entity - The ID of the entity. + * @param signature - The signature to be set for the entity. + */ + void setSignature(Entity entity, Signature signature); - /** - * @brief Retrieves the signature of an entity. - * - * @param entity - The ID of the entity. - * @return Signature - The signature of the entity. - */ - [[nodiscard]] Signature getSignature(Entity entity) const; + /** + * @brief Retrieves the signature of an entity. + * + * @param entity - The ID of the entity. + * @return Signature - The signature of the entity. + */ + [[nodiscard]] Signature getSignature(Entity entity) const; - /** - * @brief Returns the number of currently active entities - * - * @return size_t The count of living entities - */ - [[nodiscard]] size_t getLivingEntityCount() const; + /** + * @brief Returns the number of currently active entities + * + * @return size_t The count of living entities + */ + [[nodiscard]] size_t getLivingEntityCount() const; - /** - * @brief Retrieves a view of all currently active entities - * - * @return std::span A span containing all living entity IDs - */ - [[nodiscard]] std::span getLivingEntities() const; + /** + * @brief Retrieves a view of all currently active entities + * + * @return std::span A span containing all living entity IDs + */ + [[nodiscard]] std::span getLivingEntities() const; - private: - std::deque m_availableEntities{}; - std::vector m_livingEntities{}; + private: + std::deque m_availableEntities{}; + std::vector m_livingEntities{}; - std::array m_signatures{}; + std::array m_signatures{}; }; -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/Group.hpp b/engine/src/ecs/Group.hpp index f085fce5f..488788064 100644 --- a/engine/src/ecs/Group.hpp +++ b/engine/src/ecs/Group.hpp @@ -13,941 +13,953 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "Definitions.hpp" #include "ComponentArray.hpp" +#include "Definitions.hpp" #include "ECSExceptions.hpp" #include "Exception.hpp" +#include #include -#include #include -#include -#include +#include +#include #include #include -#include -#include +#include +#include namespace nexo::ecs { - /** - * @brief Interface for ECS groups. - * - * This interface defines the minimum requirements for groups that store a set - * of entities along with their associated component signatures. - */ - class IGroup { - public: - virtual ~IGroup() = default; - - /** - * @brief Returns the combined signature of all components in the group. - * - * @return const Signature& Combined signature. - */ - [[nodiscard]] virtual const Signature& allSignature() const = 0; - /** - * @brief Adds an entity to the group. - * - * @param e Entity to add. - */ - virtual void addToGroup(Entity e) = 0; - /** - * @brief Removes an entity from the group. - * - * @param e Entity to remove. - */ - virtual void removeFromGroup(Entity e) = 0; - }; - - /** - * @brief Helper template that always evaluates to false. - * - * This is used to trigger static_assert in template functions. - * - * @tparam T Type to test. - */ - template - struct dependent_false : std::false_type {}; - - /** - * @brief Metafunction to check if a tuple contains a specific component type. - * - * Example: if Tuple is std::tuple>, std::shared_ptr>> - * then tuple_contains_component::value is true if T is Position or Velocity. - * - * @tparam T Component type to check. - * @tparam Tuple Tuple type containing pointers to ComponentArray objects. - */ - template - struct tuple_contains_component; - - /** - * @brief Specialization of tuple_contains_component for std::tuple. - * - * @tparam T Component type to check. - * @tparam Ptrs Pointer types stored in the tuple. - */ - template - struct tuple_contains_component> - : std::disjunction())>::component_type>...> {}; - - /** - * @brief Convenience variable for tuple_contains_component. - * - * @tparam T Component type to check. - * @tparam Tuple Tuple type. - */ - template - constexpr bool tuple_contains_component_v = tuple_contains_component::value; - - /** - * @brief Represents a partition of entities based on a key. - * - * @tparam KeyType The type of the key used for partitioning. - */ - template - struct Partition { - KeyType key; ///< The partition key. - size_t startIndex; ///< The starting index of the partition. - size_t count; ///< The number of entities in the partition. - }; - - /** - * @brief Alias for a function that extracts a field from a component. - * - * @tparam T Component type. - * @tparam FieldType Type of the extracted field. - */ - template - using FieldExtractor = std::function; - - /** - * @brief Alias for a function that extracts a key from an entity. - * - * @tparam KeyType Type of the key. - */ - template - using EntityKeyExtractor = std::function; - - /** - * @brief Group class for a view over entities with both owned and non‑owned components. - * - * Two tuple types are taken: - * - OwnedTuple: std::tuple - * - NonOwnedTuple: std::tuple - * - * @tparam OwnedTuple Tuple of pointers (or smart pointers) to owned component arrays. - * @tparam NonOwnedTuple Tuple of pointers to non‑owned component arrays. - */ - template - class Group final : public IGroup { - public: - /** - * @brief Constructs a new Group. - * - * @tparam NonOwning Variadic template parameters for non‑owned components. - * @param ownedArrays Tuple of pointers to owned component arrays. - * @param nonOwnedArrays Tuple of pointers to non‑owned component arrays. - * - * The constructor computes the owned and non‑owned signatures and their combination. - */ - template - requires (std::tuple_size_v > 0) // Ensure at least one owned component for Group - Group(OwnedTuple ownedArrays, NonOwnedTuple nonOwnedArrays) - : m_ownedArrays(std::move(ownedArrays)) - , m_nonOwnedArrays(std::move(nonOwnedArrays)) - { - m_ownedSignature = std::apply([]([[maybe_unused]] auto&&... arrays) { - Signature signature; - ((signature.set(getComponentTypeID::component_type>())), ...); - return signature; - }, m_ownedArrays); - - const Signature nonOwnedSignature = std::apply([]([[maybe_unused]] auto&&... arrays) { - Signature signature; - ((signature.set(getComponentTypeID::component_type>())), ...); - return signature; - }, m_nonOwnedArrays); - - m_allSignature = m_ownedSignature | nonOwnedSignature; - } - - // ======================================= - // Core Group API - // ======================================= - - /** - * @brief Returns the number of entities in the group. - * - * @return std::size_t Number of entities. - */ - [[nodiscard]] std::size_t size() const - { - auto firstArray = std::get<0>(m_ownedArrays); - if (!firstArray) { - THROW_EXCEPTION(InternalError, "Component array is null"); - } - return firstArray->groupSize(); - } - - /** - * @brief Checks if sorting has been invalidated. - * - * @return true If sorting is invalidated. - * @return false Otherwise. - */ - [[nodiscard]] bool sortingInvalidated() const { return m_sortingInvalidated; } - - /** - * @brief Returns the signature for owned components. - * - * @return const Signature& Owned signature. - */ - [[nodiscard]] const Signature& ownedSignature() const { return m_ownedSignature; } - - /** - * @brief Returns the overall signature for both owned and non‑owned components. - * - * @return const Signature& Combined signature. - */ - [[nodiscard]] const Signature& allSignature() const override { return m_allSignature; } - - /** - * @brief Iterator for Group. - * - * Allows iterating over entities along with their owned and non‑owned components. - */ - class GroupIterator { - public: - using iterator_category = std::forward_iterator_tag; - /// The type returned by dereferencing the iterator. - using value_type = decltype(std::declval().dereference(0)); - using reference = value_type; - using difference_type = std::ptrdiff_t; - using pointer = void; - - /** - * @brief Constructs a GroupIterator. - * - * @param view Pointer to the Group. - * @param index Starting index. - */ - GroupIterator(const Group* view, std::size_t index) - : m_view(view), m_index(index) {} - - /** - * @brief Dereferences the iterator to get the entity and its components. - * - * @return reference Tuple containing the entity and its component data. - */ - reference operator*() const - { - if (m_index >= m_view->size()) - THROW_EXCEPTION(OutOfRange, m_index); - - return m_view->dereference(m_index); - } - - /** - * @brief Pre-increment operator. - * - * @return GroupIterator& Reference to the iterator after increment. - */ - GroupIterator& operator++() - { - ++m_index; - return *this; - } - - /** - * @brief Post-increment operator. - * - * @return GroupIterator Iterator before increment. - */ - GroupIterator operator++(int) - { - GroupIterator tmp = *this; - ++(*this); - return tmp; - } - - /** - * @brief Equality operator. - * - * @param other Another iterator. - * @return true If both iterators are equal. - * @return false Otherwise. - */ - bool operator==(const GroupIterator& other) const - { - return m_index == other.m_index && m_view == other.m_view; - } - - private: - const Group* m_view; ///< Pointer to the group. - std::size_t m_index; ///< Current index in the group. - }; - - /** - * @brief Returns an iterator to the beginning of the group - * - * @return GroupIterator Iterator pointing to the first entity - */ - GroupIterator begin() const { return GroupIterator(this, 0); } - - /** - * @brief Returns an iterator to the end of the group - * - * @return GroupIterator Iterator pointing beyond the last entity - */ - GroupIterator end() const { return GroupIterator(this, size()); } - - /** - * @brief Returns an iterator to the beginning of the group (non-const version) - * - * @return GroupIterator Iterator pointing to the first entity - */ - GroupIterator begin() { return GroupIterator(this, 0); } - - /** - * @brief Returns an iterator to the end of the group (non-const version) - * - * @return GroupIterator Iterator pointing beyond the last entity - */ - GroupIterator end() { return GroupIterator(this, size()); } - - /** - * @brief Iterates over each entity in the group. - * - * The callable 'func' must accept parameters of the form: - * (Entity, Owned&..., NonOwned&...). - * - * @tparam Func Callable type. - * @param func Function to call for each entity. - */ - template - void each(Func func) const - { - auto firstArray = std::get<0>(m_ownedArrays); - if (!firstArray) - THROW_EXCEPTION(InternalError, "Component array is null"); - - for (std::size_t i = 0; i < firstArray->groupSize(); ++i) { - Entity e = firstArray->getEntityAtIndex(i); - callFunc(func, e, - std::make_index_sequence>{}, - std::make_index_sequence>{}); - } - } - - /** - * @brief Iterates over a sub-range of entities in the group. - * - * @tparam Func Callable type. - * @param startIndex Starting index. - * @param count Number of entities to process. - * @param func Function to call for each entity in range. - */ - template - void eachInRange(size_t startIndex, const size_t count, Func func) const - { - auto firstArray = std::get<0>(m_ownedArrays); - if (!firstArray) - THROW_EXCEPTION(InternalError, "Component array is null"); - - if (startIndex >= firstArray->groupSize()) - return; // Nothing to iterate - - const size_t endIndex = std::min(startIndex + count, firstArray->groupSize()); - - for (size_t i = startIndex; i < endIndex; i++) { - Entity e = firstArray->getEntityAtIndex(i); - callFunc(func, e, - std::make_index_sequence>{}, - std::make_index_sequence>{}); - } - } - - /** - * @brief Adds an entity to the group. - * - * This method calls addToGroup(e) on every owned component array. - * - * @param e Entity to add. - */ - void addToGroup(Entity e) override - { - std::apply([e](auto&&... arrays) { - ((arrays->addToGroup(e)), ...); - }, m_ownedArrays); - - m_sortingInvalidated = true; - invalidatePartitions(); - } - - /** - * @brief Removes an entity from the group. - * - * This method calls removeFromGroup(e) on every owned component array. - * - * @param e Entity to remove. - */ - void removeFromGroup(Entity e) override - { - std::apply([e](auto&&... arrays) { - ((arrays->removeFromGroup(e)), ...); - }, m_ownedArrays); - - m_sortingInvalidated = true; - invalidatePartitions(); - } - - /** - * @brief Retrieves a span of entity IDs corresponding to the group. - * - * This is taken from the first owned component array's entities() span, - * restricted to its group region. - * - * @return std::span Span of entity IDs. - */ - [[nodiscard]] std::span entities() const - { - const std::span entities = std::get<0>(m_ownedArrays)->entities(); - return entities.subspan(0, std::get<0>(m_ownedArrays)->groupSize()); - } - - /** - * @brief Retrieves the component array data for a given component type. - * - * This overload returns a const view of the data. - * - * @tparam T Component type. - * @return auto Span over component data. - */ - template - auto get() const - { - if constexpr (tuple_contains_component_v) { - auto compArray = getOwnedImpl(); // internal lookup in owned tuple - if (!compArray) - THROW_EXCEPTION(InternalError, "Component array is null"); - - return compArray->getAllComponents().subspan(0, compArray->groupSize()); - } else if constexpr (tuple_contains_component_v) - return getNonOwnedImpl(); // internal lookup in non‑owned tuple - else - static_assert(dependent_false::value, "Component type not found in group"); - } - - /** - * @brief Retrieves the component array data for a given component type. - * - * This overload returns a mutable view of the data. - * - * @tparam T Component type. - * @return auto Span over component data. - */ - template - auto get() - { - if constexpr (tuple_contains_component_v) { - auto compArray = getOwnedImpl(); // internal lookup in owned tuple - if (!compArray) - THROW_EXCEPTION(InternalError, "Component array is null"); - - return compArray->getAllComponents().subspan(0, compArray->groupSize()); - } else if constexpr (tuple_contains_component_v) - return getNonOwnedImpl(); // internal lookup in non‑owned tuple - else - static_assert(dependent_false::value, "Component type not found in group"); - } - - // ======================================= - // Sorting API - // ======================================= - - /** - * @brief Marks the group's sorting as invalidated - * Should be called when modifying a component that can affect the sorting - * - * When sorting is invalidated, the next call to sortBy() will perform a full resort. - */ - void invalidateSorting() - { - m_sortingInvalidated = true; - } - - /** - * @brief Sorts the group by a specified component field. - * - * The sorting is only performed if the sorting is invalidated. - * - * @tparam CompType Component type to sort by. - * @tparam FieldType Field type to compare. - * @param extractor Function to extract the field value. - * @param ascending Set to true for ascending order (default true). - */ - template - void sortBy(FieldExtractor extractor, bool ascending = true) - { - SortingOrder sortingOrder = ascending ? SortingOrder::ASCENDING : SortingOrder::DESCENDING; - - if (sortingOrder != m_sortingOrder) { - m_sortingOrder = sortingOrder; - m_sortingInvalidated = true; - } - - if (!m_sortingInvalidated) - return; - - std::shared_ptr> compArray; - - if constexpr (tuple_contains_component_v) { - compArray = getOwnedImpl(); - } else if constexpr (tuple_contains_component_v) { - compArray = getNonOwnedImpl(); - } else { - static_assert(dependent_false::value, "Component type not found in group"); - } - - if (!compArray) - THROW_EXCEPTION(InternalError, "Component array is null"); - - auto drivingArray = std::get<0>(m_ownedArrays); - const size_t groupSize = drivingArray->groupSize(); - - std::vector entities; - entities.reserve(groupSize); - - // Add all entities currently in the group - for (size_t i = 0; i < groupSize; i++) - entities.push_back(drivingArray->getEntityAtIndex(i)); - - // Sort entities based on the extracted field from the component - std::ranges::sort(entities, - [&](Entity a, Entity b) { - const auto& compA = compArray->get(a); - const auto& compB = compArray->get(b); - if (ascending) - return extractor(compA) < extractor(compB); - return extractor(compA) > extractor(compB); - }); - - reorderGroup(entities); - m_sortingInvalidated = false; - } - - // ======================================= - // Partitioning API - // ======================================= - - /** - * @brief A view over a partition of entities based on a key. - * - * @tparam KeyType The type of the partition key. - */ - template - class PartitionView { - public: - /** - * @brief Constructs a PartitionView. - * - * @param group Pointer to the group. - * @param partitions Reference to a vector of Partition objects. - */ - PartitionView(Group* group, const std::vector>& partitions) - : m_group(group), m_partitions(partitions) {} - - /** - * @brief Retrieves a partition by key. - * - * @param key Key to search for. - * @return const Partition* Pointer to the partition if found; nullptr otherwise. - */ - const Partition* getPartition(const KeyType& key) const - { - for (const auto& partition : m_partitions) { - if (partition.key == key) - return &partition; - } - return nullptr; - } - - /** - * @brief Iterates over entities in a specific partition. - * - * @tparam Func Callable type. - * @param key Key of the partition. - * @param func Function to apply to each entity. - */ - template - void each(const KeyType& key, Func func) const - { - const auto* partition = getPartition(key); - if (!partition) - return; - - m_group->eachInRange(partition->startIndex, partition->count, func); - } - - /** - * @brief Gets all partition keys. - * - * @return std::vector Vector of partition keys. - */ - std::vector getPartitionKeys() const - { - std::vector keys; - keys.reserve(m_partitions.size()); - for (const auto& partition : m_partitions) - keys.push_back(partition.key); - return keys; - } - - /** - * @brief Returns the number of partitions. - * - * @return size_t Partition count. - */ - [[nodiscard]] size_t partitionCount() const - { - return m_partitions.size(); - } - - private: - Group* m_group; ///< Pointer to the group. - const std::vector>& m_partitions; ///< Reference to partitions. - }; - - /** - * @brief Returns a partition view based on a component field. - * - * @tparam CompType Component type used to partition. - * @tparam KeyType Key type extracted from the component. - * @param keyExtractor Function to extract the key from the component. - * @return PartitionView View over the partitioned entities. - */ - template - PartitionView getPartitionView(FieldExtractor keyExtractor) - { - std::string typeId = typeid(KeyType).name(); - typeId += "_" + std::string(typeid(CompType).name()); - - EntityKeyExtractor entityKeyExtractor = [this, keyExtractor](Entity e) { - if constexpr (tuple_contains_component_v) { - auto compArray = getOwnedImpl(); - if (!compArray) - THROW_EXCEPTION(InternalError, "Component array is null"); - - return keyExtractor(compArray->get(e)); - } else if constexpr (tuple_contains_component_v) { - auto compArray = getNonOwnedImpl(); - if (!compArray) - THROW_EXCEPTION(InternalError, "Component array is null"); - - return keyExtractor(compArray->get(e)); - } else - static_assert(dependent_false::value, "Component type not found in group"); - }; - - return getEntityPartitionView(typeId, entityKeyExtractor); - } - - /** - * @brief Returns a partition view based directly on entity IDs. - * - * @tparam KeyType Key type. - * @param partitionId Identifier for the partition view. - * @param keyExtractor Function to extract the key from an entity. - * @return PartitionView View over the partitioned entities. - */ - template - PartitionView getEntityPartitionView(const std::string& partitionId, - EntityKeyExtractor keyExtractor) - { - // Check if we already have this partition view - auto it = m_partitionStorageMap.find(partitionId); - if (it == m_partitionStorageMap.end()) { - auto storage = std::make_unique>(this, keyExtractor); - auto* storagePtr = storage.get(); - m_partitionStorageMap[partitionId] = std::move(storage); - storagePtr->rebuild(); - - return PartitionView(this, storagePtr->getPartitions()); - } - - // Get the existing storage and cast to the right type - auto* storage = static_cast*>(it->second.get()); - - if (storage->isDirty()) - storage->rebuild(); - - return PartitionView(this, storage->getPartitions()); - } - - /** - * @brief Invalidates all partition caches. - */ - void invalidatePartitions() - { - for (auto& [_, storage] : m_partitionStorageMap) - storage->markDirty(); - } - - private: - - // ======================================= - // Internal structures and methods - // ======================================= - - /** - * @brief Interface for type-erased partition storage. - * - * This allows handling partition storage for different key types uniformly. - */ - struct IPartitionStorage { - virtual ~IPartitionStorage() = default; - /** - * @brief Checks if the partition storage is dirty (needs rebuilding). - * - * @return true If dirty. - * @return false Otherwise. - */ - [[nodiscard]] virtual bool isDirty() const = 0; - /** - * @brief Marks the partition storage as dirty. - */ - virtual void markDirty() = 0; - /** - * @brief Rebuilds the partition storage. - */ - virtual void rebuild() = 0; - }; - - /** - * @brief Concrete partition storage for a specific key type. - * - * @tparam KeyType Type of the partition key. - */ - template - class PartitionStorage final : public IPartitionStorage { - public: - /** - * @brief Constructs PartitionStorage. - * - * @param group Pointer to the group. - * @param keyExtractor Function to extract key from an entity. - */ - PartitionStorage(Group* group, EntityKeyExtractor keyExtractor) - : m_group(group), m_keyExtractor(std::move(keyExtractor)) {} - - [[nodiscard]] bool isDirty() const override { return m_isDirty; } - void markDirty() override { m_isDirty = true; } - - /** - * @brief Rebuilds the partitions. - * - * This collects all entity keys and creates partitions. It then reorders - * the group entities according to the new partition order. - */ - void rebuild() override - { - if (!m_isDirty) - return; - auto drivingArray = std::get<0>(m_group->m_ownedArrays); - const size_t groupSize = drivingArray->groupSize(); - - // Skip if no entities - if (groupSize == 0) { - m_partitions.clear(); - m_isDirty = false; - return; - } - - std::unordered_map> keyToEntities; - - for (size_t i = 0; i < groupSize; i++) { - Entity e = drivingArray->getEntityAtIndex(i); - KeyType key = m_keyExtractor(e); - keyToEntities[key].push_back(e); - } - - m_partitions.clear(); - m_partitions.reserve(keyToEntities.size()); - - std::vector newOrder; - newOrder.reserve(groupSize); - - size_t currentIndex = 0; - for (auto& [key, entities] : keyToEntities) { - Partition partition; - partition.key = key; - partition.startIndex = currentIndex; - partition.count = entities.size(); - m_partitions.push_back(partition); - - // Add these entities to the new order - newOrder.insert(newOrder.end(), entities.begin(), entities.end()); - - currentIndex += entities.size(); - } - - m_group->reorderGroup(newOrder); - m_isDirty = false; - } - - /** - * @brief Gets the current partitions. - * - * @return const std::vector>& Reference to the partitions. - */ - const std::vector>& getPartitions() const - { - return m_partitions; - } - - private: - Group* m_group; ///< Pointer to the group. - EntityKeyExtractor m_keyExtractor; ///< Function to extract a key from an entity. - std::vector> m_partitions; ///< Vector of partitions. - bool m_isDirty = true; ///< Flag indicating if partitions need rebuilding. - }; - - /** - * @brief Reorders the group entities based on a new order. - * - * @param newOrder New order of entities. - */ - void reorderGroup(const std::vector& newOrder) - { - std::apply([&](auto&&... arrays) { - ((reorderArray(arrays, newOrder)), ...); - }, m_ownedArrays); - } - - /** - * @brief Reorders a single component array based on the new entity order. - * - * @tparam ArrayPtr Type of the component array pointer. - * @param array Component array pointer. - * @param newOrder New order of entities. - */ - template - void reorderArray(ArrayPtr array, const std::vector& newOrder) const - { - size_t groupSize = array->groupSize(); - if (newOrder.size() != groupSize) - THROW_EXCEPTION(InternalError, "New order size doesn't match group size"); - - if (groupSize == 0) - return; - - // Create a temporary storage for components - using CompType = typename std::decay_t::component_type; - std::vector tempComponents; - tempComponents.reserve(groupSize); - - for (Entity e : newOrder) - tempComponents.push_back(array->get(e)); //Maybe we should not push back, does it make a copy ? - - for (size_t i = 0; i < groupSize; i++) { - Entity e = newOrder[i]; - array->forceSetComponentAt(i, e, std::move(tempComponents[i])); - } - } - - /** - * @brief Helper to dereference an entity and its components by index. - * - * @param index Index in the group. - * @return auto Tuple containing the entity and its owned component data. - */ - auto dereference(std::size_t index) const - { - Entity entity = std::get<0>(m_ownedArrays)->getEntityAtIndex(index); - - // Use std::forward_as_tuple to preserve references. - auto ownedData = std::apply([entity](auto&&... arrays) { - return std::forward_as_tuple(arrays->get(entity)...); - }, m_ownedArrays); - // We still need the entity by value, so use std::make_tuple for that. - return std::tuple_cat(std::make_tuple(entity), ownedData); - } - - /** - * @brief Helper: Recursively search the non‑owned tuple for the ComponentArray with component_type == T. - * - * @tparam T Component type. - * @tparam I Current index in the tuple. - * @return std::shared_ptr> Pointer to the component array. - */ - template - requires (I < std::tuple_size_v) // Ensure we don't go out of bounds - auto getNonOwnedImpl() const -> std::shared_ptr> - { - using CurrentArrayPtr = std::tuple_element_t; - using CurrentComponent = typename std::decay_t())>::component_type; - if constexpr (std::is_same_v) - return std::get(m_nonOwnedArrays); - else - return getNonOwnedImpl(); - } - - /** - * @brief Helper: Recursively search the owned tuple for the ComponentArray with component_type == T. - * - * @tparam T Component type. - * @tparam I Current index in the tuple. - * @return std::shared_ptr> Pointer to the component array. - */ - template - requires (I < std::tuple_size_v) // Ensure we don't go out of bounds - auto getOwnedImpl() const -> std::shared_ptr> - { - using CurrentArrayPtr = std::tuple_element_t; - using CurrentComponent = typename std::decay_t())>::component_type; - if constexpr (std::is_same_v) - return std::get(m_ownedArrays); - else - return getOwnedImpl(); - } - - /** - * @brief Helper function to call a function with component data. - * - * This function unpacks the owned and non‑owned component arrays. - * - * @tparam Func Callable type. - * @tparam I Indices for the owned tuple. - * @tparam J Indices for the non‑owned tuple. - * @param func Callable to invoke. - * @param e Entity. - * @param index_sequence for owned components. - * @param index_sequence for non‑owned components. - */ - template - void callFunc(Func func, Entity e, std::index_sequence, std::index_sequence) const - { - func(e, (std::get(m_ownedArrays)->get(e))..., (std::get(m_nonOwnedArrays)->get(e))...); - } - - /** - * @brief Defines the direction for sorting operations - */ - enum class SortingOrder { - ASCENDING, - DESCENDING - }; - - // Member variables - OwnedTuple m_ownedArrays; ///< Tuple of pointers to owned component arrays. - NonOwnedTuple m_nonOwnedArrays; ///< Tuple of pointers to non‑owned component arrays. - Signature m_ownedSignature{}; ///< Signature for owned components. - Signature m_allSignature{}; ///< Combined signature for all components. - bool m_sortingInvalidated = true; ///< Flag indicating if sorting is invalidated. - SortingOrder m_sortingOrder = SortingOrder::ASCENDING; - std::unordered_map> m_partitionStorageMap; ///< Map storing partition data by ID. - - }; -} + /** + * @brief Interface for ECS groups. + * + * This interface defines the minimum requirements for groups that store a set + * of entities along with their associated component signatures. + */ + class IGroup { + public: + virtual ~IGroup() = default; + + /** + * @brief Returns the combined signature of all components in the group. + * + * @return const Signature& Combined signature. + */ + [[nodiscard]] virtual const Signature& allSignature() const = 0; + /** + * @brief Adds an entity to the group. + * + * @param e Entity to add. + */ + virtual void addToGroup(Entity e) = 0; + /** + * @brief Removes an entity from the group. + * + * @param e Entity to remove. + */ + virtual void removeFromGroup(Entity e) = 0; + }; + + /** + * @brief Helper template that always evaluates to false. + * + * This is used to trigger static_assert in template functions. + * + * @tparam T Type to test. + */ + template + struct dependent_false : std::false_type {}; + + /** + * @brief Metafunction to check if a tuple contains a specific component type. + * + * Example: if Tuple is std::tuple>, + * std::shared_ptr>> then tuple_contains_component::value is true if T is + * Position or Velocity. + * + * @tparam T Component type to check. + * @tparam Tuple Tuple type containing pointers to ComponentArray objects. + */ + template + struct tuple_contains_component; + + /** + * @brief Specialization of tuple_contains_component for std::tuple. + * + * @tparam T Component type to check. + * @tparam Ptrs Pointer types stored in the tuple. + */ + template + struct tuple_contains_component> + : std::disjunction())>::component_type>...> { + }; + + /** + * @brief Convenience variable for tuple_contains_component. + * + * @tparam T Component type to check. + * @tparam Tuple Tuple type. + */ + template + constexpr bool tuple_contains_component_v = tuple_contains_component::value; + + /** + * @brief Represents a partition of entities based on a key. + * + * @tparam KeyType The type of the key used for partitioning. + */ + template + struct Partition { + KeyType key; ///< The partition key. + size_t startIndex; ///< The starting index of the partition. + size_t count; ///< The number of entities in the partition. + }; + + /** + * @brief Alias for a function that extracts a field from a component. + * + * @tparam T Component type. + * @tparam FieldType Type of the extracted field. + */ + template + using FieldExtractor = std::function; + + /** + * @brief Alias for a function that extracts a key from an entity. + * + * @tparam KeyType Type of the key. + */ + template + using EntityKeyExtractor = std::function; + + /** + * @brief Group class for a view over entities with both owned and non‑owned components. + * + * Two tuple types are taken: + * - OwnedTuple: std::tuple + * - NonOwnedTuple: std::tuple + * + * @tparam OwnedTuple Tuple of pointers (or smart pointers) to owned component arrays. + * @tparam NonOwnedTuple Tuple of pointers to non‑owned component arrays. + */ + template + class Group final : public IGroup { + public: + /** + * @brief Constructs a new Group. + * + * @tparam NonOwning Variadic template parameters for non‑owned components. + * @param ownedArrays Tuple of pointers to owned component arrays. + * @param nonOwnedArrays Tuple of pointers to non‑owned component arrays. + * + * The constructor computes the owned and non‑owned signatures and their combination. + */ + template + requires(std::tuple_size_v > 0) // Ensure at least one owned component for Group + Group(OwnedTuple ownedArrays, NonOwnedTuple nonOwnedArrays) + : m_ownedArrays(std::move(ownedArrays)), m_nonOwnedArrays(std::move(nonOwnedArrays)) + { + m_ownedSignature = std::apply( + []([[maybe_unused]] auto&&... arrays) { + Signature signature; + ((signature.set(getComponentTypeID::component_type>())), + ...); + return signature; + }, + m_ownedArrays); + + const Signature nonOwnedSignature = std::apply( + []([[maybe_unused]] auto&&... arrays) { + Signature signature; + ((signature.set(getComponentTypeID::component_type>())), + ...); + return signature; + }, + m_nonOwnedArrays); + + m_allSignature = m_ownedSignature | nonOwnedSignature; + } + + // ======================================= + // Core Group API + // ======================================= + + /** + * @brief Returns the number of entities in the group. + * + * @return std::size_t Number of entities. + */ + [[nodiscard]] std::size_t size() const + { + auto firstArray = std::get<0>(m_ownedArrays); + if (!firstArray) { + THROW_EXCEPTION(InternalError, "Component array is null"); + } + return firstArray->groupSize(); + } + + /** + * @brief Checks if sorting has been invalidated. + * + * @return true If sorting is invalidated. + * @return false Otherwise. + */ + [[nodiscard]] bool sortingInvalidated() const + { + return m_sortingInvalidated; + } + + /** + * @brief Returns the signature for owned components. + * + * @return const Signature& Owned signature. + */ + [[nodiscard]] const Signature& ownedSignature() const + { + return m_ownedSignature; + } + + /** + * @brief Returns the overall signature for both owned and non‑owned components. + * + * @return const Signature& Combined signature. + */ + [[nodiscard]] const Signature& allSignature() const override + { + return m_allSignature; + } + + /** + * @brief Iterator for Group. + * + * Allows iterating over entities along with their owned and non‑owned components. + */ + class GroupIterator { + public: + using iterator_category = std::forward_iterator_tag; + /// The type returned by dereferencing the iterator. + using value_type = decltype(std::declval().dereference(0)); + using reference = value_type; + using difference_type = std::ptrdiff_t; + using pointer = void; + + /** + * @brief Constructs a GroupIterator. + * + * @param view Pointer to the Group. + * @param index Starting index. + */ + GroupIterator(const Group* view, std::size_t index) : m_view(view), m_index(index) + {} + + /** + * @brief Dereferences the iterator to get the entity and its components. + * + * @return reference Tuple containing the entity and its component data. + */ + reference operator*() const + { + if (m_index >= m_view->size()) THROW_EXCEPTION(OutOfRange, m_index); + + return m_view->dereference(m_index); + } + + /** + * @brief Pre-increment operator. + * + * @return GroupIterator& Reference to the iterator after increment. + */ + GroupIterator& operator++() + { + ++m_index; + return *this; + } + + /** + * @brief Post-increment operator. + * + * @return GroupIterator Iterator before increment. + */ + GroupIterator operator++(int) + { + GroupIterator tmp = *this; + ++(*this); + return tmp; + } + + /** + * @brief Equality operator. + * + * @param other Another iterator. + * @return true If both iterators are equal. + * @return false Otherwise. + */ + bool operator==(const GroupIterator& other) const + { + return m_index == other.m_index && m_view == other.m_view; + } + + private: + const Group* m_view; ///< Pointer to the group. + std::size_t m_index; ///< Current index in the group. + }; + + /** + * @brief Returns an iterator to the beginning of the group + * + * @return GroupIterator Iterator pointing to the first entity + */ + GroupIterator begin() const + { + return GroupIterator(this, 0); + } + + /** + * @brief Returns an iterator to the end of the group + * + * @return GroupIterator Iterator pointing beyond the last entity + */ + GroupIterator end() const + { + return GroupIterator(this, size()); + } + + /** + * @brief Returns an iterator to the beginning of the group (non-const version) + * + * @return GroupIterator Iterator pointing to the first entity + */ + GroupIterator begin() + { + return GroupIterator(this, 0); + } + + /** + * @brief Returns an iterator to the end of the group (non-const version) + * + * @return GroupIterator Iterator pointing beyond the last entity + */ + GroupIterator end() + { + return GroupIterator(this, size()); + } + + /** + * @brief Iterates over each entity in the group. + * + * The callable 'func' must accept parameters of the form: + * (Entity, Owned&..., NonOwned&...). + * + * @tparam Func Callable type. + * @param func Function to call for each entity. + */ + template + void each(Func func) const + { + auto firstArray = std::get<0>(m_ownedArrays); + if (!firstArray) THROW_EXCEPTION(InternalError, "Component array is null"); + + for (std::size_t i = 0; i < firstArray->groupSize(); ++i) { + Entity e = firstArray->getEntityAtIndex(i); + callFunc(func, e, std::make_index_sequence>{}, + std::make_index_sequence>{}); + } + } + + /** + * @brief Iterates over a sub-range of entities in the group. + * + * @tparam Func Callable type. + * @param startIndex Starting index. + * @param count Number of entities to process. + * @param func Function to call for each entity in range. + */ + template + void eachInRange(size_t startIndex, const size_t count, Func func) const + { + auto firstArray = std::get<0>(m_ownedArrays); + if (!firstArray) THROW_EXCEPTION(InternalError, "Component array is null"); + + if (startIndex >= firstArray->groupSize()) return; // Nothing to iterate + + const size_t endIndex = std::min(startIndex + count, firstArray->groupSize()); + + for (size_t i = startIndex; i < endIndex; i++) { + Entity e = firstArray->getEntityAtIndex(i); + callFunc(func, e, std::make_index_sequence>{}, + std::make_index_sequence>{}); + } + } + + /** + * @brief Adds an entity to the group. + * + * This method calls addToGroup(e) on every owned component array. + * + * @param e Entity to add. + */ + void addToGroup(Entity e) override + { + std::apply([e](auto&&... arrays) { ((arrays->addToGroup(e)), ...); }, m_ownedArrays); + + m_sortingInvalidated = true; + invalidatePartitions(); + } + + /** + * @brief Removes an entity from the group. + * + * This method calls removeFromGroup(e) on every owned component array. + * + * @param e Entity to remove. + */ + void removeFromGroup(Entity e) override + { + std::apply([e](auto&&... arrays) { ((arrays->removeFromGroup(e)), ...); }, m_ownedArrays); + + m_sortingInvalidated = true; + invalidatePartitions(); + } + + /** + * @brief Retrieves a span of entity IDs corresponding to the group. + * + * This is taken from the first owned component array's entities() span, + * restricted to its group region. + * + * @return std::span Span of entity IDs. + */ + [[nodiscard]] std::span entities() const + { + const std::span entities = std::get<0>(m_ownedArrays)->entities(); + return entities.subspan(0, std::get<0>(m_ownedArrays)->groupSize()); + } + + /** + * @brief Retrieves the component array data for a given component type. + * + * This overload returns a const view of the data. + * + * @tparam T Component type. + * @return auto Span over component data. + */ + template + auto get() const + { + if constexpr (tuple_contains_component_v) { + auto compArray = getOwnedImpl(); // internal lookup in owned tuple + if (!compArray) THROW_EXCEPTION(InternalError, "Component array is null"); + + return compArray->getAllComponents().subspan(0, compArray->groupSize()); + } else if constexpr (tuple_contains_component_v) + return getNonOwnedImpl(); // internal lookup in non‑owned tuple + else + static_assert(dependent_false::value, "Component type not found in group"); + } + + /** + * @brief Retrieves the component array data for a given component type. + * + * This overload returns a mutable view of the data. + * + * @tparam T Component type. + * @return auto Span over component data. + */ + template + auto get() + { + if constexpr (tuple_contains_component_v) { + auto compArray = getOwnedImpl(); // internal lookup in owned tuple + if (!compArray) THROW_EXCEPTION(InternalError, "Component array is null"); + + return compArray->getAllComponents().subspan(0, compArray->groupSize()); + } else if constexpr (tuple_contains_component_v) + return getNonOwnedImpl(); // internal lookup in non‑owned tuple + else + static_assert(dependent_false::value, "Component type not found in group"); + } + + // ======================================= + // Sorting API + // ======================================= + + /** + * @brief Marks the group's sorting as invalidated + * Should be called when modifying a component that can affect the sorting + * + * When sorting is invalidated, the next call to sortBy() will perform a full resort. + */ + void invalidateSorting() + { + m_sortingInvalidated = true; + } + + /** + * @brief Sorts the group by a specified component field. + * + * The sorting is only performed if the sorting is invalidated. + * + * @tparam CompType Component type to sort by. + * @tparam FieldType Field type to compare. + * @param extractor Function to extract the field value. + * @param ascending Set to true for ascending order (default true). + */ + template + void sortBy(FieldExtractor extractor, bool ascending = true) + { + SortingOrder sortingOrder = ascending ? SortingOrder::ASCENDING : SortingOrder::DESCENDING; + + if (sortingOrder != m_sortingOrder) { + m_sortingOrder = sortingOrder; + m_sortingInvalidated = true; + } + + if (!m_sortingInvalidated) return; + + std::shared_ptr> compArray; + + if constexpr (tuple_contains_component_v) { + compArray = getOwnedImpl(); + } else if constexpr (tuple_contains_component_v) { + compArray = getNonOwnedImpl(); + } else { + static_assert(dependent_false::value, "Component type not found in group"); + } + + if (!compArray) THROW_EXCEPTION(InternalError, "Component array is null"); + + auto drivingArray = std::get<0>(m_ownedArrays); + const size_t groupSize = drivingArray->groupSize(); + + std::vector entities; + entities.reserve(groupSize); + + // Add all entities currently in the group + for (size_t i = 0; i < groupSize; i++) entities.push_back(drivingArray->getEntityAtIndex(i)); + + // Sort entities based on the extracted field from the component + std::ranges::sort(entities, [&](Entity a, Entity b) { + const auto& compA = compArray->get(a); + const auto& compB = compArray->get(b); + if (ascending) return extractor(compA) < extractor(compB); + return extractor(compA) > extractor(compB); + }); + + reorderGroup(entities); + m_sortingInvalidated = false; + } + + // ======================================= + // Partitioning API + // ======================================= + + /** + * @brief A view over a partition of entities based on a key. + * + * @tparam KeyType The type of the partition key. + */ + template + class PartitionView { + public: + /** + * @brief Constructs a PartitionView. + * + * @param group Pointer to the group. + * @param partitions Reference to a vector of Partition objects. + */ + PartitionView(Group* group, const std::vector>& partitions) + : m_group(group), m_partitions(partitions) + {} + + /** + * @brief Retrieves a partition by key. + * + * @param key Key to search for. + * @return const Partition* Pointer to the partition if found; nullptr otherwise. + */ + const Partition* getPartition(const KeyType& key) const + { + for (const auto& partition : m_partitions) { + if (partition.key == key) return &partition; + } + return nullptr; + } + + /** + * @brief Iterates over entities in a specific partition. + * + * @tparam Func Callable type. + * @param key Key of the partition. + * @param func Function to apply to each entity. + */ + template + void each(const KeyType& key, Func func) const + { + const auto* partition = getPartition(key); + if (!partition) return; + + m_group->eachInRange(partition->startIndex, partition->count, func); + } + + /** + * @brief Gets all partition keys. + * + * @return std::vector Vector of partition keys. + */ + std::vector getPartitionKeys() const + { + std::vector keys; + keys.reserve(m_partitions.size()); + for (const auto& partition : m_partitions) keys.push_back(partition.key); + return keys; + } + + /** + * @brief Returns the number of partitions. + * + * @return size_t Partition count. + */ + [[nodiscard]] size_t partitionCount() const + { + return m_partitions.size(); + } + + private: + Group* m_group; ///< Pointer to the group. + const std::vector>& m_partitions; ///< Reference to partitions. + }; + + /** + * @brief Returns a partition view based on a component field. + * + * @tparam CompType Component type used to partition. + * @tparam KeyType Key type extracted from the component. + * @param keyExtractor Function to extract the key from the component. + * @return PartitionView View over the partitioned entities. + */ + template + PartitionView getPartitionView(FieldExtractor keyExtractor) + { + std::string typeId = typeid(KeyType).name(); + typeId += "_" + std::string(typeid(CompType).name()); + + EntityKeyExtractor entityKeyExtractor = [this, keyExtractor](Entity e) { + if constexpr (tuple_contains_component_v) { + auto compArray = getOwnedImpl(); + if (!compArray) THROW_EXCEPTION(InternalError, "Component array is null"); + + return keyExtractor(compArray->get(e)); + } else if constexpr (tuple_contains_component_v) { + auto compArray = getNonOwnedImpl(); + if (!compArray) THROW_EXCEPTION(InternalError, "Component array is null"); + + return keyExtractor(compArray->get(e)); + } else + static_assert(dependent_false::value, "Component type not found in group"); + }; + + return getEntityPartitionView(typeId, entityKeyExtractor); + } + + /** + * @brief Returns a partition view based directly on entity IDs. + * + * @tparam KeyType Key type. + * @param partitionId Identifier for the partition view. + * @param keyExtractor Function to extract the key from an entity. + * @return PartitionView View over the partitioned entities. + */ + template + PartitionView getEntityPartitionView(const std::string& partitionId, + EntityKeyExtractor keyExtractor) + { + // Check if we already have this partition view + auto it = m_partitionStorageMap.find(partitionId); + if (it == m_partitionStorageMap.end()) { + auto storage = std::make_unique>(this, keyExtractor); + auto* storagePtr = storage.get(); + m_partitionStorageMap[partitionId] = std::move(storage); + storagePtr->rebuild(); + + return PartitionView(this, storagePtr->getPartitions()); + } + + // Get the existing storage and cast to the right type + auto* storage = static_cast*>(it->second.get()); + + if (storage->isDirty()) storage->rebuild(); + + return PartitionView(this, storage->getPartitions()); + } + + /** + * @brief Invalidates all partition caches. + */ + void invalidatePartitions() + { + for (auto& [_, storage] : m_partitionStorageMap) storage->markDirty(); + } + + private: + // ======================================= + // Internal structures and methods + // ======================================= + + /** + * @brief Interface for type-erased partition storage. + * + * This allows handling partition storage for different key types uniformly. + */ + struct IPartitionStorage { + virtual ~IPartitionStorage() = default; + /** + * @brief Checks if the partition storage is dirty (needs rebuilding). + * + * @return true If dirty. + * @return false Otherwise. + */ + [[nodiscard]] virtual bool isDirty() const = 0; + /** + * @brief Marks the partition storage as dirty. + */ + virtual void markDirty() = 0; + /** + * @brief Rebuilds the partition storage. + */ + virtual void rebuild() = 0; + }; + + /** + * @brief Concrete partition storage for a specific key type. + * + * @tparam KeyType Type of the partition key. + */ + template + class PartitionStorage final : public IPartitionStorage { + public: + /** + * @brief Constructs PartitionStorage. + * + * @param group Pointer to the group. + * @param keyExtractor Function to extract key from an entity. + */ + PartitionStorage(Group* group, EntityKeyExtractor keyExtractor) + : m_group(group), m_keyExtractor(std::move(keyExtractor)) + {} + + /** + * @brief Checks if the partition storage is dirty (needs rebuilding). + * + * @return true If dirty. + * @return false Otherwise. + */ + [[nodiscard]] bool isDirty() const override + { + return m_isDirty; + } + + /** + * @brief Marks the partition storage as dirty. + */ + void markDirty() override + { + m_isDirty = true; + } + + /** + * @brief Rebuilds the partitions. + * + * This collects all entity keys and creates partitions. It then reorders + * the group entities according to the new partition order. + */ + void rebuild() override + { + if (!m_isDirty) return; + auto drivingArray = std::get<0>(m_group->m_ownedArrays); + const size_t groupSize = drivingArray->groupSize(); + + // Skip if no entities + if (groupSize == 0) { + m_partitions.clear(); + m_isDirty = false; + return; + } + + std::unordered_map> keyToEntities; + + for (size_t i = 0; i < groupSize; i++) { + Entity e = drivingArray->getEntityAtIndex(i); + KeyType key = m_keyExtractor(e); + keyToEntities[key].push_back(e); + } + + m_partitions.clear(); + m_partitions.reserve(keyToEntities.size()); + + std::vector newOrder; + newOrder.reserve(groupSize); + + size_t currentIndex = 0; + for (auto& [key, entities] : keyToEntities) { + Partition partition; + partition.key = key; + partition.startIndex = currentIndex; + partition.count = entities.size(); + m_partitions.push_back(partition); + + // Add these entities to the new order + newOrder.insert(newOrder.end(), entities.begin(), entities.end()); + + currentIndex += entities.size(); + } + + m_group->reorderGroup(newOrder); + m_isDirty = false; + } + + /** + * @brief Gets the current partitions. + * + * @return const std::vector>& Reference to the partitions. + */ + const std::vector>& getPartitions() const + { + return m_partitions; + } + + private: + Group* m_group; ///< Pointer to the group. + EntityKeyExtractor m_keyExtractor; ///< Function to extract a key from an entity. + std::vector> m_partitions; ///< Vector of partitions. + bool m_isDirty = true; ///< Flag indicating if partitions need rebuilding. + }; + + /** + * @brief Reorders the group entities based on a new order. + * + * @param newOrder New order of entities. + */ + void reorderGroup(const std::vector& newOrder) + { + std::apply([&](auto&&... arrays) { ((reorderArray(arrays, newOrder)), ...); }, m_ownedArrays); + } + + /** + * @brief Reorders a single component array based on the new entity order. + * + * @tparam ArrayPtr Type of the component array pointer. + * @param array Component array pointer. + * @param newOrder New order of entities. + */ + template + void reorderArray(ArrayPtr array, const std::vector& newOrder) const + { + size_t groupSize = array->groupSize(); + if (newOrder.size() != groupSize) THROW_EXCEPTION(InternalError, "New order size doesn't match group size"); + + if (groupSize == 0) return; + + // Create a temporary storage for components + using CompType = typename std::decay_t::component_type; + std::vector tempComponents; + tempComponents.reserve(groupSize); + + for (Entity e : newOrder) + tempComponents.push_back(array->get(e)); // Maybe we should not push back, does it make a copy ? + + for (size_t i = 0; i < groupSize; i++) { + Entity e = newOrder[i]; + array->forceSetComponentAt(i, e, std::move(tempComponents[i])); + } + } + + /** + * @brief Helper to dereference an entity and its components by index. + * + * @param index Index in the group. + * @return auto Tuple containing the entity and its owned component data. + */ + auto dereference(std::size_t index) const + { + Entity entity = std::get<0>(m_ownedArrays)->getEntityAtIndex(index); + + // Use std::forward_as_tuple to preserve references. + auto ownedData = std::apply( + [entity](auto&&... arrays) { return std::forward_as_tuple(arrays->get(entity)...); }, m_ownedArrays); + // We still need the entity by value, so use std::make_tuple for that. + return std::tuple_cat(std::make_tuple(entity), ownedData); + } + + /** + * @brief Helper: Recursively search the non‑owned tuple for the ComponentArray with component_type == T. + * + * @tparam T Component type. + * @tparam I Current index in the tuple. + * @return std::shared_ptr> Pointer to the component array. + */ + template + requires(I < std::tuple_size_v) // Ensure we don't go out of bounds + auto getNonOwnedImpl() const -> std::shared_ptr> + { + using CurrentArrayPtr = std::tuple_element_t; + using CurrentComponent = typename std::decay_t())>::component_type; + if constexpr (std::is_same_v) + return std::get(m_nonOwnedArrays); + else + return getNonOwnedImpl(); + } + + /** + * @brief Helper: Recursively search the owned tuple for the ComponentArray with component_type == T. + * + * @tparam T Component type. + * @tparam I Current index in the tuple. + * @return std::shared_ptr> Pointer to the component array. + */ + template + requires(I < std::tuple_size_v) // Ensure we don't go out of bounds + auto getOwnedImpl() const -> std::shared_ptr> + { + using CurrentArrayPtr = std::tuple_element_t; + using CurrentComponent = typename std::decay_t())>::component_type; + if constexpr (std::is_same_v) + return std::get(m_ownedArrays); + else + return getOwnedImpl(); + } + + /** + * @brief Helper function to call a function with component data. + * + * This function unpacks the owned and non‑owned component arrays. + * + * @tparam Func Callable type. + * @tparam I Indices for the owned tuple. + * @tparam J Indices for the non‑owned tuple. + * @param func Callable to invoke. + * @param e Entity. + * @param ownedIndices index_sequence for owned components. + * @param nonOwnedIndices index_sequence for non‑owned components. + */ + template + void callFunc(Func func, Entity e, std::index_sequence, std::index_sequence) const + { + func(e, (std::get(m_ownedArrays)->get(e))..., (std::get(m_nonOwnedArrays)->get(e))...); + } + + /** + * @brief Defines the direction for sorting operations + */ + enum class SortingOrder { ASCENDING, DESCENDING }; + + // Member variables + OwnedTuple m_ownedArrays; ///< Tuple of pointers to owned component arrays. + NonOwnedTuple m_nonOwnedArrays; ///< Tuple of pointers to non‑owned component arrays. + Signature m_ownedSignature{}; ///< Signature for owned components. + Signature m_allSignature{}; ///< Combined signature for all components. + bool m_sortingInvalidated = true; ///< Flag indicating if sorting is invalidated. + SortingOrder m_sortingOrder = SortingOrder::ASCENDING; + std::unordered_map> + m_partitionStorageMap; ///< Map storing partition data by ID. + }; +} // namespace nexo::ecs diff --git a/engine/src/ecs/GroupSystem.hpp b/engine/src/ecs/GroupSystem.hpp index 1732be5ca..abebd0299 100644 --- a/engine/src/ecs/GroupSystem.hpp +++ b/engine/src/ecs/GroupSystem.hpp @@ -13,16 +13,16 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "System.hpp" +#include +#include +#include +#include #include "Access.hpp" -#include "Group.hpp" #include "ComponentArray.hpp" #include "Coordinator.hpp" +#include "Group.hpp" #include "SingletonComponentMixin.hpp" -#include -#include -#include -#include +#include "System.hpp" namespace nexo::ecs { @@ -35,309 +35,328 @@ namespace nexo::ecs { * @tparam SingletonAccessTypes Singleton component access types (ReadSingleton or WriteSingleton) */ template, typename... SingletonAccessTypes> - class GroupSystem : public AGroupSystem, - public SingletonComponentMixin< - GroupSystem, SingletonAccessTypes...> { - private: - // Extract component access types - using OwnedAccessTypes = typename OwnedAccess::ComponentTypes; - using NonOwnedAccessTypes = typename NonOwnedAccess::ComponentTypes; - - // Extract raw component types for group creation - template - struct GetComponentTypes; - - template - struct GetComponentTypes> { - using Types = std::tuple; - }; - - using OwnedTypes = typename GetComponentTypes::Types; - using NonOwnedTypes = typename GetComponentTypes::Types; - - // Helper to unpack tuple types to parameter pack - template - auto tupleToTypeList(std::index_sequence) - { - return std::tuple...>{}; - } - - // Function to create a type list from a tuple type - template - auto tupleToTypeList() - { - return tupleToTypeList(std::make_index_sequence>{}); - } - - // Type aliases for the actual group - template - using ComponentArraysTuple = std::tuple>...>; - - // Group type is determined by the owned and non-owned component arrays - template - struct GroupTypeFromTuples; - - template - struct GroupTypeFromTuples, std::tuple> { - using Type = Group, ComponentArraysTuple>; - }; - - // The actual group type for this system - using ActualGroupType = typename GroupTypeFromTuples::Type; - - // Component access trait to find the access type for a component - template - struct ComponentAccessTrait { - static constexpr bool found = false; - static constexpr auto accessType = AccessType::Read; - }; - - template - struct ComponentAccessTrait, - std::void_t || ...)>>> { - static constexpr bool found = true; - - template - static constexpr AccessType GetAccessTypeFromPack() { - auto result = AccessType::Read; - ((std::is_same_v ? result = As::accessType : result), ...); - return result; - } - - static constexpr AccessType accessType = GetAccessTypeFromPack(); - }; - - template - struct GetComponentAccess { - using OwnedTrait = ComponentAccessTrait; - using NonOwnedTrait = ComponentAccessTrait; - - static constexpr bool found = OwnedTrait::found || NonOwnedTrait::found; - static constexpr AccessType accessType = - OwnedTrait::found ? OwnedTrait::accessType : - NonOwnedTrait::found ? NonOwnedTrait::accessType : - AccessType::Read; - }; - - /** - * @brief Access-controlled span wrapper for component arrays - * - * Provides enforced read-only or read-write access to components - * based on the access permissions specified in the system. - * - * @tparam T The component type - */ - template - class ComponentSpan { - private: - std::span m_span; - - public: - /** - * @brief Constructs a ComponentSpan from a raw span - * - * @param span The underlying component data span - */ - explicit ComponentSpan(std::span span) : m_span(span) {} - - /** - * @brief Returns the number of components in the span - * - * @return size_t Number of components - */ - [[nodiscard]] size_t size() const { return m_span.size(); } - - /** - * @brief Access operator with enforced permissions - * - * Returns a mutable reference if Write access is specified, - * otherwise returns a const reference. - * - * @param index Element index to access - * @return Reference to component with appropriate const qualification - */ - template - auto operator[](size_t index) -> std::conditional_t< - GetComponentAccess< - std::remove_const_t>::accessType == AccessType::Write, std::remove_const_t&, - const std::remove_const_t& - > - { - if constexpr (GetComponentAccess>::accessType == AccessType::Write) - return const_cast&>(m_span[index]); - else - return m_span[index]; - } - - /** - * @brief Const access operator - * - * Always returns a const reference regardless of permissions. - * - * @param index Element index to access - * @return Const reference to component - */ - template - auto operator[](size_t index) const -> const std::remove_const_t& - { - return m_span[index]; - } - - /** - * @brief Returns an iterator to the beginning of the span - * @return Iterator to the first element - */ - auto begin() { return m_span.begin(); } - - /** - * @brief Returns an iterator to the end of the span - * @return Iterator one past the last element - */ - auto end() { return m_span.end(); } - - /** - * @brief Returns a const iterator to the beginning of the span - * @return Const iterator to the first element - */ - auto begin() const { return m_span.begin(); } - - /** - * @brief Returns a const iterator to the end of the span - * @return Const iterator one past the last element - */ - auto end() const { return m_span.end(); } - }; - - public: - // Make the base class a friend to access protected members - friend class SingletonComponentMixin< - GroupSystem, - SingletonAccessTypes...>; - - /** - * @brief Constructs a new GroupSystem - * - * Creates the component group and initializes singleton components. - * @throws InternalError if the coordinator is null - */ - GroupSystem() - { - static_assert(std::tuple_size_v > 0, - "GroupSystem must have at least one owned component type"); - - if (!coord) - THROW_EXCEPTION(InternalError, "Coordinator is null in GroupSystem constructor"); - - m_group = createGroupImpl( - tupleToTypeList(), - tupleToTypeList() - ); - - this->initializeSingletonComponents(); - } - - /** - * @brief Get component array with correct access permissions - * - * @tparam T The component type - * @return ComponentSpan with enforced access control - */ - template - auto get() - { - if (!m_group) - THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); - - constexpr bool isOwned = isOwnedComponent(); - if constexpr (isOwned) { - // Get the span from the group - auto baseSpan = m_group->template get(); - - // Wrap it in our access-controlled span - return ComponentSpan::accessType == AccessType::Read, - const T, - T - >>(baseSpan); - } else { - // For non-owned components, return the component array itself - auto componentArray = m_group->template get(); - - // Apply access control by wrapping in a special pointer wrapper if needed - if constexpr (GetComponentAccess::accessType == AccessType::Read) - return std::shared_ptr>(m_group->template get()); - else - return componentArray; - } - } - - /** - * @brief Check if a component type is owned by this system - * - * @tparam T The component type to check - * @return true if owned, false if non-owned - */ - template - static constexpr bool isOwnedComponent() - { - using OwnedTraitResult = ComponentAccessTrait; - return OwnedTraitResult::found; - } - - /** - * @brief Get all entities in this group - * - * @return Span of entities in the group - */ - [[nodiscard]] std::span getEntities() const - { - if (!m_group) - THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); - - return m_group->entities(); - } - - protected: - std::shared_ptr m_group = nullptr; - - private: - + class GroupSystem + : public AGroupSystem, + public SingletonComponentMixin, + SingletonAccessTypes...> { + private: + // Extract component access types + using OwnedAccessTypes = typename OwnedAccess::ComponentTypes; + using NonOwnedAccessTypes = typename NonOwnedAccess::ComponentTypes; + + // Extract raw component types for group creation + template + struct GetComponentTypes; + + template + struct GetComponentTypes> { + using Types = std::tuple; + }; + + using OwnedTypes = typename GetComponentTypes::Types; + using NonOwnedTypes = typename GetComponentTypes::Types; + + /** + * @brief Helper to convert a tuple type to a type list (parameter pack) + * + * This utility unpacks the types from a std::tuple and returns them as a new std::tuple. + * It is used to facilitate the creation of type lists from tuple types. + * + * @tparam Tuple The input tuple type containing component types + * @tparam I Index sequence to unpack the tuple + * @return A new std::tuple containing the unpacked types + */ + template + auto tupleToTypeList(std::index_sequence) + { + return std::tuple...>{}; + } + + /** + * @brief Overload to create a type list from a tuple type + * + * This function generates an index sequence based on the size of the input tuple + * and calls the implementation function to unpack the types. + * + * @tparam Tuple The input tuple type containing component types + * @return A new std::tuple containing the unpacked types + */ + template + auto tupleToTypeList() + { + return tupleToTypeList(std::make_index_sequence>{}); + } + + // Type aliases for the actual group + template + using ComponentArraysTuple = std::tuple>...>; + + // Group type is determined by the owned and non-owned component arrays + template + struct GroupTypeFromTuples; + + template + struct GroupTypeFromTuples, std::tuple> { + using Type = Group, ComponentArraysTuple>; + }; + + // The actual group type for this system + using ActualGroupType = typename GroupTypeFromTuples::Type; + + // Component access trait to find the access type for a component + template + struct ComponentAccessTrait { + static constexpr bool found = false; + static constexpr auto accessType = AccessType::Read; + }; + + template + struct ComponentAccessTrait< + T, std::tuple, + std::void_t || ...)>>> { + static constexpr bool found = true; + + /** + * @brief Helper to get the access type from the pack + */ + template + static constexpr AccessType GetAccessTypeFromPack() + { + auto result = AccessType::Read; + ((std::is_same_v ? result = As::accessType : result), ...); + return result; + } + + static constexpr AccessType accessType = GetAccessTypeFromPack(); + }; + + template + struct GetComponentAccess { + using OwnedTrait = ComponentAccessTrait; + using NonOwnedTrait = ComponentAccessTrait; + + static constexpr bool found = OwnedTrait::found || NonOwnedTrait::found; + static constexpr AccessType accessType = OwnedTrait::found ? OwnedTrait::accessType : + NonOwnedTrait::found ? NonOwnedTrait::accessType : + AccessType::Read; + }; + + /** + * @brief Access-controlled span wrapper for component arrays + * + * Provides enforced read-only or read-write access to components + * based on the access permissions specified in the system. + * + * @tparam T The component type + */ + template + class ComponentSpan { + private: + std::span m_span; + + public: + /** + * @brief Constructs a ComponentSpan from a raw span + * + * @param span The underlying component data span + */ + explicit ComponentSpan(std::span span) : m_span(span) + {} + + /** + * @brief Returns the number of components in the span + * + * @return size_t Number of components + */ + [[nodiscard]] size_t size() const + { + return m_span.size(); + } + + /** + * @brief Access operator with enforced permissions + * + * Returns a mutable reference if Write access is specified, + * otherwise returns a const reference. + * + * @param index Element index to access + * @return Reference to component with appropriate const qualification + */ + template + auto operator[](size_t index) + -> std::conditional_t>::accessType == AccessType::Write, + std::remove_const_t&, const std::remove_const_t&> + { + if constexpr (GetComponentAccess>::accessType == AccessType::Write) + return const_cast&>(m_span[index]); + else + return m_span[index]; + } + + /** + * @brief Const access operator + * + * Always returns a const reference regardless of permissions. + * + * @param index Element index to access + * @return Const reference to component + */ + template + auto operator[](size_t index) const -> const std::remove_const_t& + { + return m_span[index]; + } + + /** + * @brief Returns an iterator to the beginning of the span + * @return Iterator to the first element + */ + auto begin() + { + return m_span.begin(); + } + + /** + * @brief Returns an iterator to the end of the span + * @return Iterator one past the last element + */ + auto end() + { + return m_span.end(); + } + + /** + * @brief Returns a const iterator to the beginning of the span + * @return Const iterator to the first element + */ + auto begin() const + { + return m_span.begin(); + } + + /** + * @brief Returns a const iterator to the end of the span + * @return Const iterator one past the last element + */ + auto end() const + { + return m_span.end(); + } + }; + + public: + // Make the base class a friend to access protected members + friend class SingletonComponentMixin, + SingletonAccessTypes...>; + + /** + * @brief Constructs a new GroupSystem + * + * Creates the component group and initializes singleton components. + * @throws InternalError if the coordinator is null + */ + GroupSystem() + { + static_assert(std::tuple_size_v > 0, "GroupSystem must have at least one owned component type"); + + if (!coord) THROW_EXCEPTION(InternalError, "Coordinator is null in GroupSystem constructor"); + + m_group = createGroupImpl(tupleToTypeList(), tupleToTypeList()); + + this->initializeSingletonComponents(); + } + + /** + * @brief Get component array with correct access permissions + * + * @tparam T The component type + * @return ComponentSpan with enforced access control + */ + template + auto get() + { + if (!m_group) THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); + + constexpr bool isOwned = isOwnedComponent(); + if constexpr (isOwned) { + // Get the span from the group + auto baseSpan = m_group->template get(); + + // Wrap it in our access-controlled span + return ComponentSpan< + std::conditional_t::accessType == AccessType::Read, const T, T>>(baseSpan); + } else { + // For non-owned components, return the component array itself + auto componentArray = m_group->template get(); + + // Apply access control by wrapping in a special pointer wrapper if needed + if constexpr (GetComponentAccess::accessType == AccessType::Read) + return std::shared_ptr>(m_group->template get()); + else + return componentArray; + } + } + + /** + * @brief Check if a component type is owned by this system + * + * @tparam T The component type to check + * @return true if owned, false if non-owned + */ + template + static constexpr bool isOwnedComponent() + { + using OwnedTraitResult = ComponentAccessTrait; + return OwnedTraitResult::found; + } + + /** + * @brief Get all entities in this group + * + * @return Span of entities in the group + */ + [[nodiscard]] std::span getEntities() const + { + if (!m_group) THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); + + return m_group->entities(); + } + + protected: + std::shared_ptr m_group = nullptr; + + private: #if defined(_MSC_VER) - #pragma warning(push) // createGroupImpl - #pragma warning(disable: 4702) // Unreachable code + #pragma warning(push) // createGroupImpl + #pragma warning(disable : 4702) // Unreachable code #endif - /** - * @brief Implementation to create a group with the extracted component types - * - * @tparam OT Owned component types - * @tparam NOT Non-owned component types - * @return Shared pointer to the created group - */ - template - std::shared_ptr createGroupImpl(std::tuple, std::tuple) - { - if constexpr (sizeof...(OT) > 0) { - if constexpr (sizeof...(NOT) > 0) { - auto group = coord->registerGroup(nexo::ecs::get()); - if (!group) - THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); - return std::static_pointer_cast(group); - } else { - auto group = coord->registerGroup(nexo::ecs::get<>()); - if (!group) - THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); - return std::static_pointer_cast(group); - } - } - return nullptr; - } + /** + * @brief Implementation to create a group with the extracted component types + * + * @tparam OT Owned component types + * @tparam NOT Non-owned component types + * @return Shared pointer to the created group + */ + template + std::shared_ptr createGroupImpl(std::tuple, std::tuple) + { + if constexpr (sizeof...(OT) > 0) { + if constexpr (sizeof...(NOT) > 0) { + auto group = coord->registerGroup(nexo::ecs::get()); + if (!group) THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); + return std::static_pointer_cast(group); + } else { + auto group = coord->registerGroup(nexo::ecs::get<>()); + if (!group) THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); + return std::static_pointer_cast(group); + } + } + return nullptr; + } #if defined(_MSC_VER) #pragma warning(pop) // createGroupImpl #endif - }; -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/QuerySystem.hpp b/engine/src/ecs/QuerySystem.hpp index 7328c6243..cc8b53ab7 100644 --- a/engine/src/ecs/QuerySystem.hpp +++ b/engine/src/ecs/QuerySystem.hpp @@ -13,15 +13,15 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "Definitions.hpp" -#include "ECSExceptions.hpp" -#include "System.hpp" +#include +#include #include "Access.hpp" #include "ComponentArray.hpp" #include "Coordinator.hpp" +#include "Definitions.hpp" +#include "ECSExceptions.hpp" #include "SingletonComponentMixin.hpp" -#include -#include +#include "System.hpp" namespace nexo::ecs { /** @@ -64,8 +64,7 @@ namespace nexo::ecs { // Add this component to the mixin along with other singleton components typename ExtractSingletonComponents::type::template RebindWithComponent, // Skip this component and continue with the rest - typename ExtractSingletonComponents::type - >; + typename ExtractSingletonComponents::type>; }; /** * @class QuerySystem @@ -73,148 +72,150 @@ namespace nexo::ecs { * * @tparam Components Component access specifiers (Read, Write, ReadSingleton, WriteSingleton) */ - template - class QuerySystem : public AQuerySystem, - public ExtractSingletonComponents::type::template RebindWithDerived> { - private: - /** - * @brief Helper template to check if a component type has Read access in a parameter pack - * - * @tparam T The component type to check for Read access - * @tparam First The first component access type in the parameter pack - * @tparam Rest The remaining component access types - */ - template - struct HasReadAccess { - static constexpr bool value = - (std::is_same_v> || - HasReadAccess::value); - }; - - /** - * @brief Base case for HasReadAccess template recursion - * - * @tparam T The component type to check for Read access - * @tparam First The last component access type in the parameter pack - */ - template - struct HasReadAccess { - static constexpr bool value = std::is_same_v>; - }; - - /** - * @brief Convenience function to check if a component has Read-only access - * - * @tparam T The component type to check - * @return true if the component has Read-only access, false otherwise - */ - template - static constexpr bool hasReadAccess() - { - return HasReadAccess::value; - } - - public: - /** - * @brief Constructs a new QuerySystem - * - * Sets up the system signature based on required components, - * caches component arrays for faster access, and initializes - * singleton components. - * - * @throws InternalError if the coordinator is null - */ - QuerySystem() - { - if (!coord) - THROW_EXCEPTION(InternalError, "Coordinator is null in QuerySystem constructor"); - - // Set system signature based on required components (ignore singleton components) - (setComponentSignatureIfRegular(m_signature), ...); - - // Cache component arrays for faster access (ignore singleton components) - (cacheComponentArrayIfRegular(), ...); - - // Initialize singleton components - this->initializeSingletonComponents(); - } - - /** - * @brief Get a component for an entity with access type determined at compile time - * - * @tparam T The component type - * @param entity The entity to get the component from - * @return Reference to the component with appropriate const-ness - */ - template - std::conditional_t(), const T&, T&> getComponent(Entity entity) - { - const ComponentType typeIndex = getUniqueComponentTypeID(); - const auto it = m_componentArrays.find(typeIndex); - - if (it == m_componentArrays.end()) - THROW_EXCEPTION(InternalError, "Component array not found"); - - auto componentArray = std::static_pointer_cast>(it->second); - - if (!componentArray) - THROW_EXCEPTION(InternalError, "Failed to cast component array"); - - if (!componentArray->hasComponent(entity)) - THROW_EXCEPTION(InternalError, "Entity doesn't have requested component"); - return componentArray->get(entity); - } - - /** - * @brief Gets the component signature for this system - * - * @return const Signature& Reference to the system's component signature - */ - const Signature& getSignature() const override { return m_signature; } - - /** - * @brief Gets a mutable reference to the component signature for this system - * - * @return Signature& Reference to the system's component signature - */ - Signature& getSignature() { return m_signature; } - - protected: - /** - * @brief Caches component arrays for faster access (only for regular components) - * - * @tparam ComponentAccessType The component access type to cache - */ - template - void cacheComponentArrayIfRegular() - { - if constexpr (!IsSingleton::value) { - using T = typename ComponentAccessType::ComponentType; - auto componentArray = coord->getComponentArray(); - m_componentArrays[getUniqueComponentTypeID()] = componentArray; - } - } - - /** - * @brief Sets the component bit in the system signature (only for regular components) - * - * @tparam ComponentAccessType The component access type - * @param signature The signature to modify - */ - template - void setComponentSignatureIfRegular(Signature& signature) - { - if constexpr (!IsSingleton::value) { - using T = typename ComponentAccessType::ComponentType; - signature.set(coord->getComponentType(), true); - } - } - - private: - // Cache of component arrays for faster access - std::unordered_map> m_componentArrays; - - /// Component signature defining required components for this system - Signature m_signature; + template + class QuerySystem : public AQuerySystem, + public ExtractSingletonComponents::type::template RebindWithDerived< + QuerySystem> { + private: + /** + * @brief Helper template to check if a component type has Read access in a parameter pack + * + * @tparam T The component type to check for Read access + * @tparam First The first component access type in the parameter pack + * @tparam Rest The remaining component access types + */ + template + struct HasReadAccess { + static constexpr bool value = (std::is_same_v> || HasReadAccess::value); + }; + + /** + * @brief Base case for HasReadAccess template recursion + * + * @tparam T The component type to check for Read access + * @tparam First The last component access type in the parameter pack + */ + template + struct HasReadAccess { + static constexpr bool value = std::is_same_v>; + }; + + /** + * @brief Convenience function to check if a component has Read-only access + * + * @tparam T The component type to check + * @return true if the component has Read-only access, false otherwise + */ + template + static constexpr bool hasReadAccess() + { + return HasReadAccess::value; + } + + public: + /** + * @brief Constructs a new QuerySystem + * + * Sets up the system signature based on required components, + * caches component arrays for faster access, and initializes + * singleton components. + * + * @throws InternalError if the coordinator is null + */ + QuerySystem() + { + if (!coord) THROW_EXCEPTION(InternalError, "Coordinator is null in QuerySystem constructor"); + + // Set system signature based on required components (ignore singleton components) + (setComponentSignatureIfRegular(m_signature), ...); + + // Cache component arrays for faster access (ignore singleton components) + (cacheComponentArrayIfRegular(), ...); + + // Initialize singleton components + this->initializeSingletonComponents(); + } + + /** + * @brief Get a component for an entity with access type determined at compile time + * + * @tparam T The component type + * @param entity The entity to get the component from + * @return Reference to the component with appropriate const-ness + */ + template + std::conditional_t(), const T&, T&> getComponent(Entity entity) + { + const ComponentType typeIndex = getUniqueComponentTypeID(); + const auto it = m_componentArrays.find(typeIndex); + + if (it == m_componentArrays.end()) THROW_EXCEPTION(InternalError, "Component array not found"); + + auto componentArray = std::static_pointer_cast>(it->second); + + if (!componentArray) THROW_EXCEPTION(InternalError, "Failed to cast component array"); + + if (!componentArray->hasComponent(entity)) + THROW_EXCEPTION(InternalError, "Entity doesn't have requested component"); + return componentArray->get(entity); + } + + /** + * @brief Gets the component signature for this system + * + * @return const Signature& Reference to the system's component signature + */ + [[nodiscard]] const Signature& getSignature() const override + { + return m_signature; + } + + /** + * @brief Gets a mutable reference to the component signature for this system + * + * @return Signature& Reference to the system's component signature + */ + Signature& getSignature() + { + return m_signature; + } + + protected: + /** + * @brief Caches component arrays for faster access (only for regular components) + * + * @tparam ComponentAccessType The component access type to cache + */ + template + void cacheComponentArrayIfRegular() + { + if constexpr (!IsSingleton::value) { + using T = typename ComponentAccessType::ComponentType; + auto componentArray = coord->getComponentArray(); + m_componentArrays[getUniqueComponentTypeID()] = componentArray; + } + } + + /** + * @brief Sets the component bit in the system signature (only for regular components) + * + * @tparam ComponentAccessType The component access type + * @param signature The signature to modify + */ + template + void setComponentSignatureIfRegular(Signature& signature) + { + if constexpr (!IsSingleton::value) { + using T = typename ComponentAccessType::ComponentType; + signature.set(coord->getComponentType(), true); + } + } + + private: + // Cache of component arrays for faster access + std::unordered_map> m_componentArrays; + + /// Component signature defining required components for this system + Signature m_signature; }; -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/SingletonComponent.hpp b/engine/src/ecs/SingletonComponent.hpp index 14c4818ec..b51c0ab5b 100644 --- a/engine/src/ecs/SingletonComponent.hpp +++ b/engine/src/ecs/SingletonComponent.hpp @@ -14,24 +14,24 @@ #pragma once -#include #include +#include #include "Definitions.hpp" -#include "Logger.hpp" #include "ECSExceptions.hpp" +#include "Logger.hpp" namespace nexo::ecs { - /** - * @brief Base interface for singleton components. - * - * All singleton components must derive from this interface. It ensures proper polymorphic destruction. - */ - class ISingletonComponent { - public: - virtual ~ISingletonComponent() = default; - }; + /** + * @brief Base interface for singleton components. + * + * All singleton components must derive from this interface. It ensures proper polymorphic destruction. + */ + class ISingletonComponent { + public: + virtual ~ISingletonComponent() = default; + }; /** * @brief Template class representing a singleton component. @@ -40,49 +40,50 @@ namespace nexo::ecs { * * @tparam T The type of the singleton component. */ - template - class SingletonComponent final : public ISingletonComponent { - public: - static_assert(!std::is_copy_constructible_v, - "Singleton component types must have a deleted copy constructor"); - /** - * @brief Templated constructor that perfectly forwards arguments to construct the instance. - * - * This constructor is only enabled when T is constructible from the provided arguments - * and none of the arguments (after decaying) is of type T (to avoid accidental copying or moving - * of an object of the same type). - * - * @tparam Args The types of constructor arguments. - * @param args Arguments forwarded to T's constructor. - */ - template - requires (std::is_constructible_v && (!std::is_same_v, T> && ...)) - explicit SingletonComponent(Args&&... args) - : _instance(std::forward(args)...) - { - } - - SingletonComponent() = default; - ~SingletonComponent() override = default; - - // Prevent copying - SingletonComponent(const SingletonComponent&) = delete; - SingletonComponent& operator=(const SingletonComponent&) = delete; - - // Allow moving - SingletonComponent(SingletonComponent&&) noexcept = default; - SingletonComponent& operator=(SingletonComponent&&) noexcept = default; - - /** - * @brief Gets the singleton component instance - * - * @return T& Reference to the instance of the singleton component - */ - T &getInstance() { return _instance; } - - private: - T _instance; - }; + template + class SingletonComponent final : public ISingletonComponent { + public: + static_assert(!std::is_copy_constructible_v, + "Singleton component types must have a deleted copy constructor"); + /** + * @brief Templated constructor that perfectly forwards arguments to construct the instance. + * + * This constructor is only enabled when T is constructible from the provided arguments + * and none of the arguments (after decaying) is of type T (to avoid accidental copying or moving + * of an object of the same type). + * + * @tparam Args The types of constructor arguments. + * @param args Arguments forwarded to T's constructor. + */ + template + requires(std::is_constructible_v && (!std::is_same_v, T> && ...)) + explicit SingletonComponent(Args&&... args) : _instance(std::forward(args)...) + {} + + SingletonComponent() = default; + ~SingletonComponent() override = default; + + // Prevent copying + SingletonComponent(const SingletonComponent&) = delete; + SingletonComponent& operator=(const SingletonComponent&) = delete; + + // Allow moving + SingletonComponent(SingletonComponent&&) noexcept = default; + SingletonComponent& operator=(SingletonComponent&&) noexcept = default; + + /** + * @brief Gets the singleton component instance + * + * @return T& Reference to the instance of the singleton component + */ + T& getInstance() + { + return _instance; + } + + private: + T _instance; + }; /** * @brief Manager for singleton components in the ECS. @@ -90,85 +91,83 @@ namespace nexo::ecs { * The SingletonComponentManager is responsible for registering, retrieving, and unregistering * singleton components. Singleton components are globally unique and accessed via their type. */ - class SingletonComponentManager { - public: - /** - * @brief Registers a singleton component in place by forwarding constructor arguments. - * - * This method constructs the singleton directly in the manager using the provided arguments. - * - * @tparam T The type of the singleton component. - * @tparam Args The types of the constructor arguments. - * @param args Arguments to construct an instance of T. - */ - template - void registerSingletonComponent(Args&&... args) - { - ComponentType typeName = getUniqueComponentTypeID(); - if (m_singletonComponents.contains(typeName)) { - LOG(NEXO_WARN, "ECS::SingletonComponentManager::registerSingletonComponent: trying to register a singleton component more than once"); - return; - } - m_singletonComponents.insert( - {typeName, - std::make_shared>(std::forward(args)...)} - ); - } - - /** - * @brief Retrieves a singleton component instance. - * - * @tparam T The type of the singleton component. - * @return T& A reference to the registered singleton component. - * @throws SingletonComponentNotRegistered if the component is not registered. - */ - template - T &getSingletonComponent() - { - const ComponentType typeName = getUniqueComponentTypeID(); - if (!m_singletonComponents.contains(typeName)) - THROW_EXCEPTION(SingletonComponentNotRegistered); - - auto componentPtr = dynamic_cast*>(m_singletonComponents[typeName].get()); - - return componentPtr->getInstance(); - } - - /** - * @brief Retrieves a singleton component pointer (internal use only). - * - * @tparam T The type of the singleton component. - * @return std::shared_ptr A shared pointer to the registered singleton component. - * @throws SingletonComponentNotRegistered if the component is not registered. - */ - template - std::shared_ptr getRawSingletonComponent() - { - const ComponentType typeName = getUniqueComponentTypeID(); - if (!m_singletonComponents.contains(typeName)) - THROW_EXCEPTION(SingletonComponentNotRegistered); - - return m_singletonComponents[typeName]; - } - - /** - * @brief Unregisters a singleton component. - * - * Removes the singleton component of type T from the manager. - * - * @tparam T The type of the singleton component. - * @throws SingletonComponentNotRegistered if the component is not registered. - */ - template - void unregisterSingletonComponent() - { - const ComponentType typeName = getUniqueComponentTypeID(); - if (!m_singletonComponents.contains(typeName)) - THROW_EXCEPTION(SingletonComponentNotRegistered); - - m_singletonComponents.erase(typeName); - } - private: - std::unordered_map> m_singletonComponents{}; - }; -} + class SingletonComponentManager { + public: + /** + * @brief Registers a singleton component in place by forwarding constructor arguments. + * + * This method constructs the singleton directly in the manager using the provided arguments. + * + * @tparam T The type of the singleton component. + * @tparam Args The types of the constructor arguments. + * @param args Arguments to construct an instance of T. + */ + template + void registerSingletonComponent(Args&&... args) + { + ComponentType typeName = getUniqueComponentTypeID(); + if (m_singletonComponents.contains(typeName)) { + LOG(NEXO_WARN, + "ECS::SingletonComponentManager::registerSingletonComponent: trying to register a singleton " + "component more than once"); + return; + } + m_singletonComponents.insert( + {typeName, std::make_shared>(std::forward(args)...)}); + } + + /** + * @brief Retrieves a singleton component instance. + * + * @tparam T The type of the singleton component. + * @return T& A reference to the registered singleton component. + * @throws SingletonComponentNotRegistered if the component is not registered. + */ + template + T& getSingletonComponent() + { + const ComponentType typeName = getUniqueComponentTypeID(); + if (!m_singletonComponents.contains(typeName)) THROW_EXCEPTION(SingletonComponentNotRegistered); + + auto componentPtr = dynamic_cast*>(m_singletonComponents[typeName].get()); + + return componentPtr->getInstance(); + } + + /** + * @brief Retrieves a singleton component pointer (internal use only). + * + * @tparam T The type of the singleton component. + * @return std::shared_ptr A shared pointer to the registered singleton component. + * @throws SingletonComponentNotRegistered if the component is not registered. + */ + template + std::shared_ptr getRawSingletonComponent() + { + const ComponentType typeName = getUniqueComponentTypeID(); + if (!m_singletonComponents.contains(typeName)) THROW_EXCEPTION(SingletonComponentNotRegistered); + + return m_singletonComponents[typeName]; + } + + /** + * @brief Unregisters a singleton component. + * + * Removes the singleton component of type T from the manager. + * + * @tparam T The type of the singleton component. + * @throws SingletonComponentNotRegistered if the component is not registered. + */ + template + void unregisterSingletonComponent() + { + const ComponentType typeName = getUniqueComponentTypeID(); + if (!m_singletonComponents.contains(typeName)) THROW_EXCEPTION(SingletonComponentNotRegistered); + + m_singletonComponents.erase(typeName); + } + + private: + std::unordered_map> m_singletonComponents{}; + }; +} // namespace nexo::ecs diff --git a/engine/src/ecs/SingletonComponentMixin.hpp b/engine/src/ecs/SingletonComponentMixin.hpp index a85f68b5a..f96d40074 100644 --- a/engine/src/ecs/SingletonComponentMixin.hpp +++ b/engine/src/ecs/SingletonComponentMixin.hpp @@ -13,9 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include #include "Access.hpp" #include "SingletonComponent.hpp" -#include namespace nexo::ecs { @@ -27,119 +27,117 @@ namespace nexo::ecs { */ template class SingletonComponentMixin { - private: - - /** - * @brief Helper to check if a singleton component has read-only access in the parameter pack - * - * This checks all SingletonAccessTypes to see if any match the given type T with ReadSingleton access. - * - * @tparam T The singleton component type to check - */ - template - struct HasReadSingletonAccessImpl { - static constexpr bool value = (... || (IsReadSingleton::value && - std::is_same_v)); - }; - - public: - /** - * @brief Rebind this mixin with a different derived class - * - * @tparam NewDerived The new derived class to use - */ - template - using RebindWithDerived = SingletonComponentMixin; - - /** - * @brief Rebind this mixin with an additional singleton component - * - * @tparam NewComponent The new singleton component access type to add - */ - template - using RebindWithComponent = SingletonComponentMixin; - - protected: - /** - * @brief Cache of strongly-typed singleton components for faster access - * - * Stores components with their specific types to avoid dynamic casting - */ - template - using SingletonComponentPtr = std::shared_ptr>; - - // Type-specific cache for fast access without dynamic_cast - template - struct TypedComponentCache { - static inline SingletonComponentPtr instance = nullptr; - }; - - /** - * @brief Initializes singleton components for this system - */ - void initializeSingletonComponents() - { - // Cache singleton components for faster access - (cacheSingletonComponent(), ...); + private: + /** + * @brief Helper to check if a singleton component has read-only access in the parameter pack + * + * This checks all SingletonAccessTypes to see if any match the given type T with ReadSingleton access. + * + * @tparam T The singleton component type to check + */ + template + struct HasReadSingletonAccessImpl { + static constexpr bool value = (... || (IsReadSingleton::value && + std::is_same_v)); + }; + + public: + /** + * @brief Rebind this mixin with a different derived class + * + * @tparam NewDerived The new derived class to use + */ + template + using RebindWithDerived = SingletonComponentMixin; + + /** + * @brief Rebind this mixin with an additional singleton component + * + * @tparam NewComponent The new singleton component access type to add + */ + template + using RebindWithComponent = SingletonComponentMixin; + + protected: + /** + * @brief Cache of strongly-typed singleton components for faster access + * + * Stores components with their specific types to avoid dynamic casting + */ + template + using SingletonComponentPtr = std::shared_ptr>; + + // Type-specific cache for fast access without dynamic_cast + template + struct TypedComponentCache { + static inline SingletonComponentPtr instance = nullptr; + }; + + /** + * @brief Initializes singleton components for this system + */ + void initializeSingletonComponents() + { + // Cache singleton components for faster access + (cacheSingletonComponent(), ...); + } + + /** + * @brief Caches a specific singleton component + * + * @tparam T The singleton component type + */ + template + void cacheSingletonComponent() + { + auto* derived = static_cast(this); + const std::shared_ptr instance = + derived->coord->template getRawSingletonComponent(); + + // Store in the type-specific cache + auto typedInstance = std::static_pointer_cast>(instance); + TypedComponentCache::instance = typedInstance; + } + + public: + /** + * @brief Checks if a singleton component has read-only access + * + * @tparam T The singleton component type to check + * @return true if the component has read-only access, false if it has read-write access + */ + template + static constexpr bool hasReadSingletonAccess() + { + return HasReadSingletonAccessImpl::value; + } + + /** + * @brief Get a singleton component with access type determined at compile time + * + * @tparam T The singleton component type + * @return Reference to the singleton component with appropriate const-ness + * + * @warning MUST be captured with auto& or const auto& to preserve access restrictions! + */ + template + std::conditional_t(), const T&, T&> getSingleton() + { + if (!TypedComponentCache::instance) { + // Late binding in case the singleton was registered after system creation + cacheSingletonComponent(); } - /** - * @brief Caches a specific singleton component - * - * @tparam T The singleton component type - */ - template - void cacheSingletonComponent() - { - auto* derived = static_cast(this); - const std::shared_ptr instance = derived->coord->template getRawSingletonComponent(); - - // Store in the type-specific cache - auto typedInstance = std::static_pointer_cast>(instance); - TypedComponentCache::instance = typedInstance; - } + auto& typedComponent = TypedComponentCache::instance; - public: - - /** - * @brief Checks if a singleton component has read-only access - * - * @tparam T The singleton component type to check - * @return true if the component has read-only access, false if it has read-write access - */ - template - static constexpr bool hasReadSingletonAccess() - { - return HasReadSingletonAccessImpl::value; - } + if (!typedComponent) THROW_EXCEPTION(SingletonComponentNotRegistered); - /** - * @brief Get a singleton component with access type determined at compile time - * - * @tparam T The singleton component type - * @return Reference to the singleton component with appropriate const-ness - * - * @warning MUST be captured with auto& or const auto& to preserve access restrictions! - */ - template - std::conditional_t(), const T&, T&> getSingleton() - { - if (!TypedComponentCache::instance) { - // Late binding in case the singleton was registered after system creation - cacheSingletonComponent(); - } - - auto& typedComponent = TypedComponentCache::instance; - - if (!typedComponent) - THROW_EXCEPTION(SingletonComponentNotRegistered); - - // Return the reference with appropriate constness - if constexpr (hasReadSingletonAccess()) - return const_cast(typedComponent->getInstance()); - else - return typedComponent->getInstance(); - } + // Return the reference with appropriate constness + if constexpr (hasReadSingletonAccess()) + return const_cast(typedComponent->getInstance()); + else + return typedComponent->getInstance(); + } }; /** @@ -152,40 +150,40 @@ namespace nexo::ecs { */ template<> class SingletonComponentMixin { - public: - /** - * @brief Rebind this base mixin with a derived class type - * - * This allows transitioning from the void placeholder to a concrete - * derived type when no singleton components were found. - * - * @tparam NewDerived The derived class type to bind to - */ - template - using RebindWithDerived = SingletonComponentMixin; - - /** - * @brief Begin building a mixin with a singleton component - * - * This is called when the first singleton component is found during - * template recursion, transitioning from the void base case to a - * mixin with actual components. - * - * @tparam NewComponent The first singleton component access type - */ - template - using RebindWithComponent = SingletonComponentMixin; - - protected: - /** - * @brief No-op implementation of singleton component initialization - * - * Since this specialization represents a mixin with no singleton components, - * there's nothing to initialize. - */ - void initializeSingletonComponents() - { - // No-op method - } + public: + /** + * @brief Rebind this base mixin with a derived class type + * + * This allows transitioning from the void placeholder to a concrete + * derived type when no singleton components were found. + * + * @tparam NewDerived The derived class type to bind to + */ + template + using RebindWithDerived = SingletonComponentMixin; + + /** + * @brief Begin building a mixin with a singleton component + * + * This is called when the first singleton component is found during + * template recursion, transitioning from the void base case to a + * mixin with actual components. + * + * @tparam NewComponent The first singleton component access type + */ + template + using RebindWithComponent = SingletonComponentMixin; + + protected: + /** + * @brief No-op implementation of singleton component initialization + * + * Since this specialization represents a mixin with no singleton components, + * there's nothing to initialize. + */ + static void initializeSingletonComponents() + { + // No-op method + } }; -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/System.cpp b/engine/src/ecs/System.cpp index 7f9b4344c..6f38e1857 100644 --- a/engine/src/ecs/System.cpp +++ b/engine/src/ecs/System.cpp @@ -18,10 +18,16 @@ namespace nexo::ecs { + /** + * @brief Inserts an entity into the sparse set. + * + * If the entity already exists in the set, a warning is logged and no action is taken. + * + * @param entity The entity to insert. + */ void SparseSet::insert(Entity entity) { - if (contains(entity)) - { + if (contains(entity)) { LOG(NEXO_WARN, "Entity {} already added to the sparse set", entity); return; } @@ -30,35 +36,65 @@ namespace nexo::ecs { dense.push_back(entity); } + /** + * @brief Erases an entity from the sparse set. + * + * If the entity does not exist in the set, a warning is logged and no action is taken. + * The entity is removed by swapping it with the last entity in the dense array + * and updating the sparse mapping accordingly. + * + * @param entity The entity to erase. + */ void SparseSet::erase(Entity entity) { - if (!contains(entity)) - { + if (!contains(entity)) { LOG(NEXO_WARN, "Entity {} does not exist in the sparse set", entity); return; } - const size_t index = sparse[entity]; - const size_t lastIndex = dense.size() - 1; + const size_t index = sparse[entity]; + const size_t lastIndex = dense.size() - 1; const Entity lastEntity = dense[lastIndex]; - dense[index] = lastEntity; + dense[index] = lastEntity; sparse[lastEntity] = index; dense.pop_back(); sparse.erase(entity); } + /** + * @brief Removes an entity from all systems whose signature matches the entity's signature. + * + * This function iterates through all registered systems and checks if the entity's + * signature matches the system's signature. If a match is found, the entity is + * removed from the system's set of entities. + * + * @param entity The entity to be removed. + * @param signature The signature of the entity, representing its component composition. + */ void SystemManager::entityDestroyed(const Entity entity, const Signature signature) const { for (const auto& system : std::ranges::views::values(m_querySystems)) { - if (const Signature &systemSignature = system->getSignature(); (signature & systemSignature) == systemSignature) + if (const Signature& systemSignature = system->getSignature(); + (signature & systemSignature) == systemSignature) system->entities.erase(entity); } } - void SystemManager::entitySignatureChanged(const Entity entity, - const Signature oldSignature, - const Signature newSignature) + /** + * @brief Updates systems when an entity's signature changes. + * + * This function checks each registered system to see if the entity's new signature + * qualifies it for inclusion in the system based on the system's signature. If the + * entity now qualifies but did not before, it is added to the system. Conversely, + * if the entity no longer qualifies but did before, it is removed from the system. + * + * @param entity The entity whose signature has changed. + * @param oldSignature The previous signature of the entity. + * @param newSignature The updated signature of the entity. + */ + void SystemManager::entitySignatureChanged(const Entity entity, const Signature oldSignature, + const Signature newSignature) { for (const auto& system : std::ranges::views::values(m_querySystems)) { const Signature systemSignature = system->getSignature(); @@ -74,4 +110,4 @@ namespace nexo::ecs { } } } -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/System.hpp b/engine/src/ecs/System.hpp index b52914ed0..76af1b1b3 100644 --- a/engine/src/ecs/System.hpp +++ b/engine/src/ecs/System.hpp @@ -14,13 +14,13 @@ #pragma once -#include -#include #include +#include +#include #include "Definitions.hpp" -#include "Logger.hpp" #include "ECSExceptions.hpp" +#include "Logger.hpp" namespace nexo::ecs { class Coordinator; @@ -28,101 +28,119 @@ namespace nexo::ecs { namespace nexo::ecs { - /** - * @class SparseSet - * - * @brief A sparse set implementation for efficient entity storage and lookup - * - * This class provides O(1) insertion, removal, and lookup operations for entities. - * It uses a sparse-dense pattern where entities are stored contiguously in a dense array, - * while maintaining a sparse lookup map to quickly find entity positions. - */ + /** + * @class SparseSet + * + * @brief A sparse set implementation for efficient entity storage and lookup + * + * This class provides O(1) insertion, removal, and lookup operations for entities. + * It uses a sparse-dense pattern where entities are stored contiguously in a dense array, + * while maintaining a sparse lookup map to quickly find entity positions. + */ class SparseSet { - public: - /** - * @brief Insert an entity into the set - * - * @param entity The entity to insert - */ - void insert(Entity entity); - - /** - * @brief Remove an entity from the set - * - * @param entity The entity to remove - */ - void erase(Entity entity); - - /** - * @brief Check if the set is empty - * - * @return true if the set contains no entities, false otherwise - */ - bool empty() const { return dense.empty(); } - - /** - * @brief Check if an entity exists in the set - * - * @param entity The entity to check - * @return true if the entity exists in the set, false otherwise - */ - bool contains(const Entity entity) const { return sparse.contains(entity); } - - /** - * @brief Get the number of entities in the set - * - * @return The number of entities - */ - size_t size() const { return dense.size(); } - - /** - * @brief Get the dense array of entities - * - * @return Const reference to the vector of entities - */ - const std::vector& getDense() const { return dense; } - - /** - * @brief Get an iterator to the beginning of the entity collection - * - * @return Iterator to the first entity - */ - auto begin() const { return dense.begin(); } - - /** - * @brief Get an iterator to the end of the entity collection - * - * @return Iterator to the position after the last entity - */ - auto end() const { return dense.end(); } - - private: - /** - * @brief Dense array of entities in insertion order - */ - std::vector dense; - - /** - * @brief Sparse lookup map from entity ID to position in dense array - */ - std::unordered_map sparse; + public: + /** + * @brief Insert an entity into the set + * + * @param entity The entity to insert + */ + void insert(Entity entity); + + /** + * @brief Remove an entity from the set + * + * @param entity The entity to remove + */ + void erase(Entity entity); + + /** + * @brief Check if the set is empty + * + * @return true if the set contains no entities, false otherwise + */ + [[nodiscard]] bool empty() const + { + return dense.empty(); + } + + /** + * @brief Check if an entity exists in the set + * + * @param entity The entity to check + * @return true if the entity exists in the set, false otherwise + */ + [[nodiscard]] bool contains(const Entity entity) const + { + return sparse.contains(entity); + } + + /** + * @brief Get the number of entities in the set + * + * @return The number of entities + */ + [[nodiscard]] size_t size() const + { + return dense.size(); + } + + /** + * @brief Get the dense array of entities + * + * @return Const reference to the vector of entities + */ + [[nodiscard]] const std::vector& getDense() const + { + return dense; + } + + /** + * @brief Get an iterator to the beginning of the entity collection + * + * @return Iterator to the first entity + */ + [[nodiscard]] auto begin() const + { + return dense.begin(); + } + + /** + * @brief Get an iterator to the end of the entity collection + * + * @return Iterator to the position after the last entity + */ + [[nodiscard]] auto end() const + { + return dense.end(); + } + + private: + /** + * @brief Dense array of entities in insertion order + */ + std::vector dense; + + /** + * @brief Sparse lookup map from entity ID to position in dense array + */ + std::unordered_map sparse; }; /** - * @class System - * - * @brief Base class for systems in an ECS (Entity-Component-System) architecture. - * - * Systems are responsible for processing entities that have a specific set of components. - * This class provides the basic structure for a system, mainly a set of entities that the system will process. - */ + * @class System + * + * @brief Base class for systems in an ECS (Entity-Component-System) architecture. + * + * Systems are responsible for processing entities that have a specific set of components. + * This class provides the basic structure for a system, mainly a set of entities that the system will process. + */ class System { - public: - virtual ~System() = default; - /** - * @brief Global coordinator instance shared by all systems - */ - static std::shared_ptr coord; + public: + virtual ~System() = default; + /** + * @brief Global coordinator instance shared by all systems + */ + static std::shared_ptr coord; }; /** @@ -130,15 +148,14 @@ namespace nexo::ecs { * @brief Base abstract for all query-based systems */ class AQuerySystem : public System { - public: - ~AQuerySystem() override = default; - virtual const Signature &getSignature() const = 0; + public: + ~AQuerySystem() override = default; + [[nodiscard]] virtual const Signature& getSignature() const = 0; /** * @brief Entities that match this system's signature */ SparseSet entities; - }; /** @@ -146,114 +163,113 @@ namespace nexo::ecs { * @brief Abstract base class for all group-based systems */ class AGroupSystem : public System { - public: + public: ~AGroupSystem() override = default; }; /** - * @class SystemManager - * - * @brief Manages systems in the ECS architecture. - * - * This class is responsible for registering systems, setting their signatures, - * and updating systems with relevant entities based on entity signature changes. - */ + * @class SystemManager + * + * @brief Manages systems in the ECS architecture. + * + * This class is responsible for registering systems, setting their signatures, + * and updating systems with relevant entities based on entity signature changes. + */ class SystemManager { - public: - /** - * @brief Registers a new system of type T in the ECS framework. - * - * @tparam T - The type of the system to be registered. - * @return std::shared_ptr - Shared pointer to the newly registered system. - */ - template - std::shared_ptr registerQuerySystem(Args&&... args) - { - static_assert(std::is_base_of_v, "T must derive from AQuerySystem"); - std::type_index typeName(typeid(T)); - - if (m_querySystems.contains(typeName)) - { - LOG(NEXO_WARN, "ECS::SystemManager::registerSystem: trying to register a system more than once"); - return nullptr; - } - - auto system = std::make_shared(std::forward(args)...); - m_querySystems.insert({typeName, system}); - return system; + public: + /** + * @brief Registers a new system of type T in the ECS framework. + * + * @tparam T - The type of the system to be registered. + * @return std::shared_ptr - Shared pointer to the newly registered system. + */ + template + std::shared_ptr registerQuerySystem(Args&&... args) + { + static_assert(std::is_base_of_v, "T must derive from AQuerySystem"); + std::type_index typeName(typeid(T)); + + if (m_querySystems.contains(typeName)) { + LOG(NEXO_WARN, "ECS::SystemManager::registerSystem: trying to register a system more than once"); + return nullptr; } - /** - * @brief Registers a new group-based system of type T in the ECS framework - * - * @tparam T The type of the system to be registered (must derive from AGroupSystem) - * @tparam Args Constructor argument types for the system - * @param args Constructor arguments for the system - * @return std::shared_ptr Shared pointer to the newly registered system - */ - template - std::shared_ptr registerGroupSystem(Args&&... args) - { - static_assert(std::is_base_of_v, "T must derive from AGroupSystem"); - std::type_index typeName(typeid(T)); - - if (m_groupSystems.contains(typeName)) - { - LOG(NEXO_WARN, "ECS::SystemManager::registerSystem: trying to register a system more than once"); - return nullptr; - } - - auto system = std::make_shared(std::forward(args)...); - m_groupSystems.insert({typeName, system}); - return system; - } + auto system = std::make_shared(std::forward(args)...); + m_querySystems.insert({typeName, system}); + return system; + } - /** - * @brief Sets the signature for a system. - * - * The signature determines which entities the system will process based on their component makeup. - * @tparam T - The type of the system for which the signature is being set. - * @param signature - The signature to associate with the system. - */ - template - void setSignature(Signature signature) - { - std::type_index typeName(typeid(T)); - - m_signatures.insert({typeName, signature}); + /** + * @brief Registers a new group-based system of type T in the ECS framework + * + * @tparam T The type of the system to be registered (must derive from AGroupSystem) + * @tparam Args Constructor argument types for the system + * @param args Constructor arguments for the system + * @return std::shared_ptr Shared pointer to the newly registered system + */ + template + std::shared_ptr registerGroupSystem(Args&&... args) + { + static_assert(std::is_base_of_v, "T must derive from AGroupSystem"); + std::type_index typeName(typeid(T)); + + if (m_groupSystems.contains(typeName)) { + LOG(NEXO_WARN, "ECS::SystemManager::registerSystem: trying to register a system more than once"); + return nullptr; } - /** - * @brief Handles the destruction of an entity by removing it from all systems. - * - * @param entity - The ID of the destroyed entity. - * @param signature - The signature of the entity. - */ - void entityDestroyed(Entity entity, Signature signature) const; - - /** - * @brief Updates the systems with an entity when its signature changes. - * - * This ensures that systems process only relevant entities based on their current components. - * @param entity - The ID of the entity whose signature has changed. - * @param oldSignature - The old signature of the entity. - * @param newSignature - The new signature of the entity. - */ - void entitySignatureChanged(Entity entity, Signature oldSignature, Signature newSignature); - private: - /** - * @brief Map of system type to component signature - */ - std::unordered_map m_signatures{}; - - /** - * @brief Map of query system type to system instance - */ - std::unordered_map> m_querySystems{}; - - /** - * @brief Map of group system type to system instance - */ - std::unordered_map> m_groupSystems{}; + auto system = std::make_shared(std::forward(args)...); + m_groupSystems.insert({typeName, system}); + return system; + } + + /** + * @brief Sets the signature for a system. + * + * The signature determines which entities the system will process based on their component makeup. + * @tparam T - The type of the system for which the signature is being set. + * @param signature - The signature to associate with the system. + */ + template + void setSignature(Signature signature) + { + std::type_index typeName(typeid(T)); + + m_signatures.insert({typeName, signature}); + } + + /** + * @brief Handles the destruction of an entity by removing it from all systems. + * + * @param entity - The ID of the destroyed entity. + * @param signature - The signature of the entity. + */ + void entityDestroyed(Entity entity, Signature signature) const; + + /** + * @brief Updates the systems with an entity when its signature changes. + * + * This ensures that systems process only relevant entities based on their current components. + * @param entity - The ID of the entity whose signature has changed. + * @param oldSignature - The old signature of the entity. + * @param newSignature - The new signature of the entity. + */ + void entitySignatureChanged(Entity entity, Signature oldSignature, Signature newSignature); + + private: + /** + * @brief Map of system type to component signature + */ + std::unordered_map m_signatures{}; + + /** + * @brief Map of query system type to system instance + */ + std::unordered_map> m_querySystems{}; + + /** + * @brief Map of group system type to system instance + */ + std::unordered_map> m_groupSystems{}; }; -} +} // namespace nexo::ecs diff --git a/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp b/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp index 9d5ce75cb..69287aadc 100644 --- a/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp +++ b/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp @@ -24,8 +24,8 @@ namespace nexo::ecs { struct ComponentDescription { - std::string name; // Name of the component - std::vector fields; // List of fields in the component + std::string name; // Name of the component + std::vector fields; // List of fields in the component }; } // namespace nexo::ecs diff --git a/engine/src/ecs/TypeErasedComponent/Field.hpp b/engine/src/ecs/TypeErasedComponent/Field.hpp index a0d7ae9ec..e1944269a 100644 --- a/engine/src/ecs/TypeErasedComponent/Field.hpp +++ b/engine/src/ecs/TypeErasedComponent/Field.hpp @@ -24,10 +24,10 @@ namespace nexo::ecs { struct Field { - std::string name; // Pointer to the name of the field - FieldType type; // Type of the field (e.g., Int, Float, String, etc.) - uint64_t size; // Size of the field in bytes - uint64_t offset; // Offset of the field in the component + std::string name; // Pointer to the name of the field + FieldType type; // Type of the field (e.g., Int, Float, String, etc.) + uint64_t size; // Size of the field in bytes + uint64_t offset; // Offset of the field in the component }; } // namespace nexo::ecs diff --git a/engine/src/renderPasses/ForwardPass.cpp b/engine/src/renderPasses/ForwardPass.cpp index ac7738aed..e8375b642 100644 --- a/engine/src/renderPasses/ForwardPass.cpp +++ b/engine/src/renderPasses/ForwardPass.cpp @@ -15,33 +15,28 @@ #include "ForwardPass.hpp" #include "DrawCommand.hpp" #include "Framebuffer.hpp" +#include "Passes.hpp" #include "RenderCommand.hpp" #include "renderPasses/Masks.hpp" #include "renderer/RenderPipeline.hpp" #include "renderer/Renderer3D.hpp" -#include "Passes.hpp" - -#include namespace nexo::renderer { ForwardPass::ForwardPass() : RenderPass(Passes::FORWARD, "Forward Pass") - { - - } + {} - void ForwardPass::execute(RenderPipeline& pipeline) + void ForwardPass::execute(RenderPipeline &pipeline) { const std::shared_ptr renderTarget = pipeline.getRenderTarget(); renderTarget->bind(); - NxRenderCommand::setClearColor(pipeline.getCameraClearColor()); - NxRenderCommand::clear(); + NxRenderCommand::setClearColor(pipeline.getCameraClearColor()); + NxRenderCommand::clear(); renderTarget->clearAttachment(1, -1); NxRenderer3D::get().bindTextures(); const std::vector &drawCommands = pipeline.getDrawCommands(); for (const auto &cmd : drawCommands) { - if (cmd.filterMask & F_FORWARD_PASS) - cmd.execute(); + if (cmd.filterMask & F_FORWARD_PASS) cmd.execute(); } renderTarget->unbind(); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderPasses/ForwardPass.hpp b/engine/src/renderPasses/ForwardPass.hpp index 4e18fc6c0..5c828fdf4 100644 --- a/engine/src/renderPasses/ForwardPass.hpp +++ b/engine/src/renderPasses/ForwardPass.hpp @@ -13,16 +13,18 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "renderer/Framebuffer.hpp" #include "renderer/RenderPass.hpp" namespace nexo::renderer { class ForwardPass : public RenderPass { - public: - ForwardPass(); - ~ForwardPass() override = default; + public: + ForwardPass(); + ~ForwardPass() override = default; - void execute(RenderPipeline& pipeline) override; + /** + * @brief Executes the forward rendering pass. + */ + void execute(RenderPipeline& pipeline) override; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderPasses/GridPass.cpp b/engine/src/renderPasses/GridPass.cpp index 0a69b5db2..dd464166d 100644 --- a/engine/src/renderPasses/GridPass.cpp +++ b/engine/src/renderPasses/GridPass.cpp @@ -15,27 +15,24 @@ #include "GridPass.hpp" #include "DrawCommand.hpp" #include "Framebuffer.hpp" -#include "RenderCommand.hpp" -#include "renderer/RenderPipeline.hpp" #include "Masks.hpp" #include "Passes.hpp" +#include "RenderCommand.hpp" +#include "renderer/RenderPipeline.hpp" #include namespace nexo::renderer { GridPass::GridPass() : RenderPass(Passes::GRID, "Grid pass") - { - - } + {} - void GridPass::execute(RenderPipeline& pipeline) + void GridPass::execute(RenderPipeline &pipeline) { - std::shared_ptr renderTarget = pipeline.getRenderTarget(); - if (!renderTarget) - return; + const std::shared_ptr renderTarget = pipeline.getRenderTarget(); + if (!renderTarget) return; renderTarget->bind(); - constexpr GLenum singleDrawBuffer[] = { GL_COLOR_ATTACHMENT0 }; + constexpr GLenum singleDrawBuffer[] = {GL_COLOR_ATTACHMENT0}; glDrawBuffers(1, singleDrawBuffer); renderer::NxRenderCommand::setDepthMask(false); renderer::NxRenderCommand::setCulling(false); @@ -49,8 +46,8 @@ namespace nexo::renderer { renderer::NxRenderCommand::setCulling(true); renderer::NxRenderCommand::setCulledFace(CulledFace::BACK); - constexpr GLenum allBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; + constexpr GLenum allBuffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; glDrawBuffers(2, allBuffers); renderTarget->unbind(); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderPasses/GridPass.hpp b/engine/src/renderPasses/GridPass.hpp index ced5fd413..aa90fbbcf 100644 --- a/engine/src/renderPasses/GridPass.hpp +++ b/engine/src/renderPasses/GridPass.hpp @@ -14,15 +14,17 @@ #pragma once #include "renderer/RenderPass.hpp" -#include "renderer/Framebuffer.hpp" namespace nexo::renderer { class GridPass : public RenderPass { - public: - GridPass(); - ~GridPass() override = default; + public: + GridPass(); + ~GridPass() override = default; - void execute(RenderPipeline& pipeline) override; + /** + * @brief Executes the grid rendering pass. + */ + void execute(RenderPipeline& pipeline) override; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderPasses/MaskPass.cpp b/engine/src/renderPasses/MaskPass.cpp index d1cc1a4a8..ef3430331 100644 --- a/engine/src/renderPasses/MaskPass.cpp +++ b/engine/src/renderPasses/MaskPass.cpp @@ -16,43 +16,42 @@ #include #include "DrawCommand.hpp" #include "Framebuffer.hpp" -#include "renderer/RenderPipeline.hpp" -#include "renderer/RenderCommand.hpp" -#include "renderer/Renderer3D.hpp" #include "Masks.hpp" #include "Passes.hpp" +#include "renderer/RenderCommand.hpp" +#include "renderer/RenderPipeline.hpp" +#include "renderer/Renderer3D.hpp" namespace nexo::renderer { - MaskPass::MaskPass(unsigned int width, unsigned int height) : RenderPass(Passes::MASK, "Mask pass") + MaskPass::MaskPass(const unsigned int width, const unsigned int height) : RenderPass(Passes::MASK, "Mask pass") { renderer::NxFramebufferSpecs maskFramebufferSpecs; - maskFramebufferSpecs.attachments = { renderer::NxFrameBufferTextureFormats::RGBA8, renderer::NxFrameBufferTextureFormats::DEPTH24STENCIL8 }; - maskFramebufferSpecs.width = width; // Default size, will be resized as needed - maskFramebufferSpecs.height = height; - m_mask = renderer::NxFramebuffer::create(maskFramebufferSpecs); + maskFramebufferSpecs.attachments = {renderer::NxFrameBufferTextureFormats::RGBA8, + renderer::NxFrameBufferTextureFormats::DEPTH24STENCIL8}; + maskFramebufferSpecs.width = width; // Default size, will be resized as needed + maskFramebufferSpecs.height = height; + m_mask = renderer::NxFramebuffer::create(maskFramebufferSpecs); } - void MaskPass::execute(RenderPipeline& pipeline) + void MaskPass::execute(RenderPipeline &pipeline) { m_mask->bind(); renderer::NxRenderCommand::setClearColor({0.0f, 0.0f, 0.0f, 0.0f}); renderer::NxRenderCommand::clear(); - //IMPORTANT: Bind textures after binding the framebuffer, since binding can trigger a resize and invalidate the - // current texture slots + // IMPORTANT: Bind textures after binding the framebuffer, since binding can trigger a resize and invalidate the + // current texture slots renderer::NxRenderer3D::get().bindTextures(); const std::vector &drawCommands = pipeline.getDrawCommands(); for (const auto &cmd : drawCommands) { - if (cmd.filterMask & F_OUTLINE_MASK) - cmd.execute(); + if (cmd.filterMask & F_OUTLINE_MASK) cmd.execute(); } m_mask->unbind(); } void MaskPass::resize(unsigned int width, unsigned int height) { - if (!m_mask) - return; + if (!m_mask) return; m_mask->resize(width, height); } @@ -60,4 +59,4 @@ namespace nexo::renderer { { return m_mask; } -} +} // namespace nexo::renderer diff --git a/engine/src/renderPasses/MaskPass.hpp b/engine/src/renderPasses/MaskPass.hpp index 99f3d027f..2531577f5 100644 --- a/engine/src/renderPasses/MaskPass.hpp +++ b/engine/src/renderPasses/MaskPass.hpp @@ -13,20 +13,50 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "renderer/RenderPass.hpp" #include "renderer/Framebuffer.hpp" +#include "renderer/RenderPass.hpp" namespace nexo::renderer { class MaskPass : public RenderPass { - public: - MaskPass(unsigned int width, unsigned int height); - ~MaskPass() override = default; + public: + MaskPass(unsigned int width, unsigned int height); + ~MaskPass() override = default; + + /** + * @brief Executes the mask rendering pass. + * + * This method binds the internal framebuffer, clears it, and executes all draw commands + * that have the `F_OUTLINE_MASK` filter mask set. After rendering, it unbinds the framebuffer. + * + * @param pipeline The render pipeline containing draw commands and render target information. + */ + void execute(RenderPipeline& pipeline) override; + + /** + * @brief Resizes the internal framebuffer to the specified dimensions. + * + * This method adjusts the size of the internal framebuffer used for rendering the mask. + * It should be called whenever the viewport or render target size changes to ensure + * correct rendering. + * + * @param width The new width of the framebuffer in pixels. + * @param height The new height of the framebuffer in pixels. + */ + void resize(unsigned int width, unsigned int height) override; + + /** + * @brief Retrieves the output framebuffer containing the rendered mask. + * + * This method returns a shared pointer to the internal framebuffer that holds the + * result of the mask rendering pass. This framebuffer can be used as input for + * subsequent rendering passes or post-processing effects. + * + * @return A shared pointer to the `NxFramebuffer` containing the mask output. + */ + [[nodiscard]] std::shared_ptr getOutput() const override; - void execute(RenderPipeline& pipeline) override; - void resize(unsigned int width, unsigned int height) override; - std::shared_ptr getOutput() const override; - private: - std::shared_ptr m_mask; + private: + std::shared_ptr m_mask; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderPasses/Masks.hpp b/engine/src/renderPasses/Masks.hpp index 7b93f02f4..b45ba2b9e 100644 --- a/engine/src/renderPasses/Masks.hpp +++ b/engine/src/renderPasses/Masks.hpp @@ -16,8 +16,8 @@ #include namespace nexo::renderer { - constexpr uint32_t F_FORWARD_PASS = 1 << 0; - constexpr uint32_t F_OUTLINE_MASK = 1 << 1; - constexpr uint32_t F_OUTLINE_PASS = 1 << 2; - constexpr uint32_t F_GRID_PASS = 1 << 3; -} + constexpr uint32_t F_FORWARD_PASS = 1 << 0; + constexpr uint32_t F_OUTLINE_MASK = 1 << 1; + constexpr uint32_t F_OUTLINE_PASS = 1 << 2; + constexpr uint32_t F_GRID_PASS = 1 << 3; +} // namespace nexo::renderer diff --git a/engine/src/renderPasses/OutlinePass.cpp b/engine/src/renderPasses/OutlinePass.cpp index 6f9708798..3d007ea65 100644 --- a/engine/src/renderPasses/OutlinePass.cpp +++ b/engine/src/renderPasses/OutlinePass.cpp @@ -15,52 +15,46 @@ #include "OutlinePass.hpp" #include "DrawCommand.hpp" #include "Framebuffer.hpp" +#include "Masks.hpp" +#include "Passes.hpp" #include "RenderCommand.hpp" -#include "renderPasses/MaskPass.hpp" #include "renderer/RenderPipeline.hpp" #include "renderer/Renderer3D.hpp" -#include "Masks.hpp" -#include "Passes.hpp" #include namespace nexo::renderer { OutlinePass::OutlinePass() : RenderPass(Passes::OUTLINE, "Outline pass") - { - - } + {} void OutlinePass::execute(RenderPipeline& pipeline) { - const auto renderTarget = pipeline.getRenderTarget(); + const auto renderTarget = pipeline.getRenderTarget(); std::shared_ptr maskPass = nullptr; for (auto prereq : prerequisites) { auto p = pipeline.getRenderPass(prereq); - if (p->getId() == Passes::MASK) - maskPass = p->getOutput(); + if (p->getId() == Passes::MASK) maskPass = p->getOutput(); } - if (!renderTarget || !maskPass) - return; + if (!renderTarget || !maskPass) return; renderTarget->bind(); - constexpr GLenum ourDrawBuffers[] = { GL_COLOR_ATTACHMENT0 }; + constexpr GLenum ourDrawBuffers[] = {GL_COLOR_ATTACHMENT0}; glDrawBuffers(1, ourDrawBuffers); renderer::NxRenderCommand::setDepthTest(false); renderer::NxRenderCommand::setDepthMask(false); - maskPass->bindAsTexture(); // bound to unit 0 - renderTarget->bindDepthAsTexture(1); // bound to unit 1 - maskPass->bindDepthAsTexture(2); // bound to unit 2 + maskPass->bindAsTexture(); // bound to unit 0 + renderTarget->bindDepthAsTexture(1); // bound to unit 1 + maskPass->bindDepthAsTexture(2); // bound to unit 2 const auto& drawCommands = pipeline.getDrawCommands(); for (auto const& cmd : drawCommands) { - if (cmd.filterMask & F_OUTLINE_PASS) - cmd.execute(); + if (cmd.filterMask & F_OUTLINE_PASS) cmd.execute(); } - constexpr GLenum allBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; + constexpr GLenum allBuffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; glDrawBuffers(2, allBuffers); renderTarget->unbind(); renderer::NxRenderCommand::setDepthMask(true); renderer::NxRenderCommand::setDepthTest(true); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderPasses/OutlinePass.hpp b/engine/src/renderPasses/OutlinePass.hpp index 1a8d71b0f..8f56d864f 100644 --- a/engine/src/renderPasses/OutlinePass.hpp +++ b/engine/src/renderPasses/OutlinePass.hpp @@ -14,15 +14,17 @@ #pragma once #include "renderer/RenderPass.hpp" -#include "renderer/Framebuffer.hpp" namespace nexo::renderer { class OutlinePass : public RenderPass { - public: - OutlinePass(); - ~OutlinePass() override = default; + public: + OutlinePass(); + ~OutlinePass() override = default; - void execute(RenderPipeline& pipeline) override; + /** + * @brief Executes the outline rendering pass. + */ + void execute(RenderPipeline& pipeline) override; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderPasses/Passes.hpp b/engine/src/renderPasses/Passes.hpp index 82fa4d9fe..0902544c7 100644 --- a/engine/src/renderPasses/Passes.hpp +++ b/engine/src/renderPasses/Passes.hpp @@ -17,11 +17,5 @@ #include "RenderPass.hpp" namespace nexo::renderer { - enum Passes : PassId { - FORWARD, - GRID, - MASK, - OUTLINE, - NB_PASSES - }; + enum Passes : PassId { FORWARD, GRID, MASK, OUTLINE, NB_PASSES }; } diff --git a/engine/src/renderer/Attributes.hpp b/engine/src/renderer/Attributes.hpp index 09de8cdf0..36ccd600b 100644 --- a/engine/src/renderer/Attributes.hpp +++ b/engine/src/renderer/Attributes.hpp @@ -17,27 +17,60 @@ namespace nexo::renderer { - struct RequiredAttributes { - union BitFields { - struct Flags { - bool position : 1; - bool normal : 1; - bool tangent : 1; - bool bitangent : 1; - bool uv0 : 1; - bool lightmapUV : 1; - bool padding : 2; - } flags; - uint8_t bits = 0; - } bitsUnion; + struct RequiredAttributes { + union BitFields { + struct Flags { + bool position : 1; + bool normal : 1; + bool tangent : 1; + bool bitangent : 1; + bool uv0 : 1; + bool lightmapUV : 1; + bool padding : 2; + } flags; + uint8_t bits = 0; + } bitsUnion; - bool operator==(RequiredAttributes const& o) const { + /** + * @brief Equality operator to compare two RequiredAttributes instances. + * + * This operator checks if two RequiredAttributes instances have the same set of required attributes. + * + * @param o The other RequiredAttributes instance to compare against. + * @return true if both instances have the same required attributes, false otherwise. + * + * Example: + * ```cpp + * RequiredAttributes req1{.bitsUnion = {.flags = {true, false, true, false, true, false, false, false}}}; + * RequiredAttributes req2{.bitsUnion = {.flags = {true, false, true, false, true, false, false, false}}}; + * bool areEqual = (req1 == req2); // returns true + * ``` + */ + bool operator==(RequiredAttributes const& o) const + { return bitsUnion.bits == o.bitsUnion.bits; } - [[nodiscard]] bool compatibleWith(RequiredAttributes const& o) const { + /** + * @brief Checks if the current RequiredAttributes is compatible with another. + * + * Compatibility means that all attributes required by the current instance + * are also present in the other instance. + * + * @param o The other RequiredAttributes to compare against. + * @return true if compatible, false otherwise. + * + * Example: + * ```cpp + * RequiredAttributes req1{.bitsUnion = {.flags = {true, false, true, false, true, false, false, false}}}; + * RequiredAttributes req2{.bitsUnion = {.flags = {true, true, true, false, true, true, false, false}}}; + * bool isCompatible = req1.compatibleWith(req2); // returns true + * ``` + */ + [[nodiscard]] bool compatibleWith(RequiredAttributes const& o) const + { return (bitsUnion.bits & o.bitsUnion.bits) == bitsUnion.bits; } - }; + }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Buffer.cpp b/engine/src/renderer/Buffer.cpp index 91b3c2b3e..2bc51e3f8 100644 --- a/engine/src/renderer/Buffer.cpp +++ b/engine/src/renderer/Buffer.cpp @@ -17,34 +17,33 @@ #include "opengl/OpenGlBuffer.hpp" #endif - namespace nexo::renderer { std::shared_ptr createVertexBuffer(float *vertices, unsigned int size) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(vertices, size); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(vertices, size); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } std::shared_ptr createVertexBuffer(unsigned int size) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(size); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(size); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } std::shared_ptr createIndexBuffer() { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Buffer.hpp b/engine/src/renderer/Buffer.hpp index 4829265d1..1dcb1dcd0 100644 --- a/engine/src/renderer/Buffer.hpp +++ b/engine/src/renderer/Buffer.hpp @@ -14,10 +14,10 @@ #pragma once #include -#include -#include #include #include +#include +#include namespace nexo::renderer { @@ -36,20 +36,7 @@ namespace nexo::renderer { * - INT, INT2, INT3, INT4: Represents one or more integer values. * - BOOL: Represents a boolean value. */ - enum class NxShaderDataType { - NONE = 0, - FLOAT, - FLOAT2, - FLOAT3, - FLOAT4, - MAT3, - MAT4, - INT, - INT2, - INT3, - INT4, - BOOL - }; + enum class NxShaderDataType { NONE = 0, FLOAT, FLOAT2, FLOAT3, FLOAT4, MAT3, MAT4, INT, INT2, INT3, INT4, BOOL }; /** * @brief Returns the size (in bytes) of a given NxShaderDataType. @@ -66,20 +53,32 @@ namespace nexo::renderer { */ static unsigned int shaderDataTypeSize(const NxShaderDataType type) { - switch (type) - { - case NxShaderDataType::FLOAT: return 4; // 1 float (4 bytes) - case NxShaderDataType::FLOAT2: return 4 * 2; // 2 floats (8 bytes) - case NxShaderDataType::FLOAT3: return 4 * 3; // 3 floats (12 bytes) - case NxShaderDataType::FLOAT4: return 4 * 4; // 4 floats (16 bytes) - case NxShaderDataType::MAT3: return 4 * 3 * 3; // 3x3 matrix (36 bytes) - case NxShaderDataType::MAT4: return 4 * 4 * 4; // 4x4 matrix (64 bytes) - case NxShaderDataType::INT: return 4; // 1 int (4 bytes) - case NxShaderDataType::INT2: return 4 * 2; // 2 ints (8 bytes) - case NxShaderDataType::INT3: return 4 * 3; // 3 ints (12 bytes) - case NxShaderDataType::INT4: return 4 * 4; // 4 ints (16 bytes) - case NxShaderDataType::BOOL: return 1; // 1 byte (1 bool) - case NxShaderDataType::NONE: return 0; // No type, return 0 + using enum NxShaderDataType; + switch (type) { + case FLOAT: + return 4; // 1 float (4 bytes) + case FLOAT2: + return 4 * 2; // 2 floats (8 bytes) + case FLOAT3: + return 4 * 3; // 3 floats (12 bytes) + case FLOAT4: + return 4 * 4; // 4 floats (16 bytes) + case MAT3: + return 4 * 3 * 3; // 3x3 matrix (36 bytes) + case MAT4: + return 4 * 4 * 4; // 4x4 matrix (64 bytes) + case INT: + return 4; // 1 int (4 bytes) + case INT2: + return 4 * 2; // 2 ints (8 bytes) + case INT3: + return 4 * 3; // 3 ints (12 bytes) + case INT4: + return 4 * 4; // 4 ints (16 bytes) + case BOOL: + return 1; // 1 byte (1 bool) + case NONE: + return 0; // No type, return 0 } return 0; // Default case for undefined types } @@ -110,27 +109,37 @@ namespace nexo::renderer { NxBufferElements() = default; NxBufferElements(const NxShaderDataType Type, std::string name, const bool normalized = false) - : name(std::move(name)), type(Type), size(shaderDataTypeSize(type)), offset(0) , normalized(normalized) - { - - } + : name(std::move(name)), type(Type), size(shaderDataTypeSize(type)), offset(0), normalized(normalized) + {} [[nodiscard]] unsigned int getComponentCount() const { - switch(type) - { - case NxShaderDataType::FLOAT: return 1; - case NxShaderDataType::FLOAT2: return 2; - case NxShaderDataType::FLOAT3: return 3; - case NxShaderDataType::FLOAT4: return 4; - case NxShaderDataType::INT: return 1; - case NxShaderDataType::INT2: return 2; - case NxShaderDataType::INT3: return 3; - case NxShaderDataType::INT4: return 4; - case NxShaderDataType::MAT3: return 3 * 3; - case NxShaderDataType::MAT4: return 4 * 4; - case NxShaderDataType::BOOL: return 1; - default: return 0; // Undefined type, return 0 + using enum NxShaderDataType; + switch (type) { + case FLOAT: + return 1; + case FLOAT2: + return 2; + case FLOAT3: + return 3; + case FLOAT4: + return 4; + case INT: + return 1; + case INT2: + return 2; + case INT3: + return 3; + case INT4: + return 4; + case MAT3: + return 3 * 3; + case MAT4: + return 4 * 4; + case BOOL: + return 1; + default: + return 0; // Undefined type, return 0 } } }; @@ -159,36 +168,88 @@ namespace nexo::renderer { * - Supports begin() and end() iterators for easy iteration over elements. */ class NxBufferLayout { - public: - NxBufferLayout() = default; - NxBufferLayout(const std::initializer_list elements) - : _elements(elements) - { - calculateOffsetAndStride(); - }; - - [[nodiscard]] std::vector getElements() const { return _elements; }; - [[nodiscard]] unsigned int getStride() const { return _stride; }; - - std::vector::iterator begin() { return _elements.begin(); }; - std::vector::iterator end() { return _elements.end(); }; - [[nodiscard]] std::vector::const_iterator begin() const { return _elements.begin(); }; - [[nodiscard]] std::vector::const_iterator end() const { return _elements.end(); }; - private: - std::vector _elements; - unsigned int _stride{}; - - void calculateOffsetAndStride() - { - unsigned int offset = 0; - _stride = 0; - for (auto &element : _elements) - { - element.offset = offset; - offset += element.size; - _stride += element.size; - } + public: + NxBufferLayout() = default; + NxBufferLayout(const std::initializer_list elements) : _elements(elements) + { + calculateOffsetAndStride(); + } + + /** + * @brief Gets the vector of buffer elements in the layout. + * @return Vector containing all buffer elements. + */ + [[nodiscard]] std::vector getElements() const + { + return _elements; + } + + /** + * @brief Gets the total stride (size in bytes) of one vertex. + * @return Total size in bytes of all elements in one vertex. + */ + [[nodiscard]] unsigned int getStride() const + { + return _stride; + } + + /** + * @brief Gets iterator to beginning of buffer elements. + * @return Iterator pointing to first element. + */ + std::vector::iterator begin() + { + return _elements.begin(); + } + + /** + * @brief Gets iterator to end of buffer elements. + * @return Iterator pointing past last element. + */ + std::vector::iterator end() + { + return _elements.end(); + } + + /** + * @brief Gets const iterator to beginning of buffer elements. + * @return Const iterator pointing to first element. + */ + [[nodiscard]] std::vector::const_iterator begin() const + { + return _elements.begin(); + } + + /** + * @brief Gets const iterator to end of buffer elements. + * @return Const iterator pointing past last element. + */ + [[nodiscard]] std::vector::const_iterator end() const + { + return _elements.end(); + } + private: + std::vector _elements; + unsigned int _stride{}; + + /** + * @brief Calculates the offset and stride for each element in the layout. + * + * This function iterates through all buffer elements, calculating their offsets + * based on their sizes and updating the overall stride of the layout. The offset + * indicates where each element starts within a vertex, while the stride represents + * the total size of one vertex in bytes. + */ + void calculateOffsetAndStride() + { + unsigned int offset = 0; + _stride = 0; + for (auto &element : _elements) { + element.offset = offset; + offset += element.size; + _stride += element.size; } + } }; /** @@ -201,81 +262,92 @@ namespace nexo::renderer { * implementation across various graphics APIs. */ class NxVertexBuffer { - public: - /** - * @brief Destroys the vertex buffer. - * - * This virtual destructor ensures that derived classes properly release any - * resources associated with the vertex buffer, such as GPU memory. - * - * Usage: - * - Automatically called when a VertexBuffer object goes out of scope. - */ - virtual ~NxVertexBuffer() = default; - - /** - * @brief Binds the vertex buffer as the active buffer in the graphics pipeline. - * - * Binding the vertex buffer ensures that subsequent rendering commands will use - * the data stored in this buffer. - * - * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses (e.g., NxOpenGlVertexBuffer). - */ - virtual void bind() const = 0; - - /** - * @brief Unbinds the currently bound vertex buffer. - * - * Unbinding the vertex buffer ensures that no unintended operations are performed - * on the buffer. This is optional but useful for debugging or ensuring clean state management. - * - * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses (e.g., NxOpenGlVertexBuffer). - */ - virtual void unbind() const = 0; - - /** - * @brief Sets the layout of the vertex buffer. - * - * The layout defines the structure of the data stored in the buffer (e.g., positions, - * normals, colors) and how they are passed to the vertex shader. - * - * @param layout The NxBufferLayout object defining the structure of the buffer data. - * - * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses. - */ - virtual void setLayout(const NxBufferLayout &layout) = 0; - - /** - * @brief Retrieves the layout of the vertex buffer. - * - * Provides information about the data structure stored in the buffer, including - * element types, sizes, and offsets. - * - * @return The NxBufferLayout object associated with this vertex buffer. - * - * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses. - */ - [[nodiscard]] virtual NxBufferLayout getLayout() const = 0; - - /** - * @brief Uploads new data to the vertex buffer. - * - * This method replaces the contents of the vertex buffer with new data. It is - * typically used for dynamic buffers where the data changes frequently. - * - * @param data Pointer to the new data to upload. - * @param size The size (in bytes) of the new data. - * - * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses. - */ - virtual void setData(void *data, size_t size) = 0; - - [[nodiscard]] virtual unsigned int getId() const = 0; + public: + /** + * @brief Destroys the vertex buffer. + * + * This virtual destructor ensures that derived classes properly release any + * resources associated with the vertex buffer, such as GPU memory. + * + * Usage: + * - Automatically called when a VertexBuffer object goes out of scope. + */ + virtual ~NxVertexBuffer() = default; + + /** + * @brief Binds the vertex buffer as the active buffer in the graphics pipeline. + * + * Binding the vertex buffer ensures that subsequent rendering commands will use + * the data stored in this buffer. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses (e.g., NxOpenGlVertexBuffer). + */ + virtual void bind() const = 0; + + /** + * @brief Unbinds the currently bound vertex buffer. + * + * Unbinding the vertex buffer ensures that no unintended operations are performed + * on the buffer. This is optional but useful for debugging or ensuring clean state management. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses (e.g., NxOpenGlVertexBuffer). + */ + virtual void unbind() const = 0; + + /** + * @brief Sets the layout of the vertex buffer. + * + * The layout defines the structure of the data stored in the buffer (e.g., positions, + * normals, colors) and how they are passed to the vertex shader. + * + * @param layout The NxBufferLayout object defining the structure of the buffer data. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses. + */ + virtual void setLayout(const NxBufferLayout &layout) = 0; + + /** + * @brief Retrieves the layout of the vertex buffer. + * + * Provides information about the data structure stored in the buffer, including + * element types, sizes, and offsets. + * + * @return The NxBufferLayout object associated with this vertex buffer. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses. + */ + [[nodiscard]] virtual NxBufferLayout getLayout() const = 0; + + /** + * @brief Uploads new data to the vertex buffer. + * + * This method replaces the contents of the vertex buffer with new data. It is + * typically used for dynamic buffers where the data changes frequently. + * + * @param data Pointer to the new data to upload. + * @param size The size (in bytes) of the new data. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses. + */ + virtual void setData(void *data, size_t size) = 0; + + /** + * @brief Retrieves the unique identifier of the vertex buffer. + * + * The ID is typically assigned by the graphics API (e.g., OpenGL) when the buffer + * is created and is used for binding and managing the buffer in the graphics pipeline. + * + * @return The unique ID of the vertex buffer. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses. + */ + [[nodiscard]] virtual unsigned int getId() const = 0; }; /** @@ -287,68 +359,79 @@ namespace nexo::renderer { * and managing index buffers, enabling compatibility with multiple graphics APIs. */ class NxIndexBuffer { - public: - /** - * @brief Destroys the index buffer. - * - * This virtual destructor ensures that derived classes properly release any - * resources associated with the index buffer, such as GPU memory. - * - * Usage: - * - Automatically called when an IndexBuffer object goes out of scope. - */ - virtual ~NxIndexBuffer() = default; - - /** - * @brief Binds the index buffer as the active buffer in the graphics pipeline. - * - * Binding the index buffer ensures that subsequent rendering commands will use - * the indices stored in this buffer to draw primitives. - * - * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses (e.g., OpenGLIndexBuffer). - */ - virtual void bind() const = 0; - - /** - * @brief Unbinds the currently bound index buffer. - * - * Unbinding the index buffer ensures that no unintended operations are performed - * on the buffer. This is optional but useful for debugging or ensuring clean state management. - * - * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses (e.g., OpenGLIndexBuffer). - */ - virtual void unbind() const = 0; - - /** - * @brief Uploads new index data to the index buffer. - * - * This method replaces the contents of the index buffer with new data. It is - * commonly used to define how vertices are connected to form primitives. - * - * @param data Pointer to the array of indices to upload. - * @param size The number of indices to upload. - * - * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses. - */ - virtual void setData(unsigned int *data, size_t size) = 0; - - /** - * @brief Retrieves the number of indices in the index buffer. - * - * The count specifies how many indices are stored in the buffer, which is - * necessary for rendering indexed primitives. - * - * @return The number of indices in the buffer. - * - * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses. - */ - [[nodiscard]] virtual size_t getCount() const = 0; - - [[nodiscard]] virtual unsigned int getId() const = 0; + public: + /** + * @brief Destroys the index buffer. + * + * This virtual destructor ensures that derived classes properly release any + * resources associated with the index buffer, such as GPU memory. + * + * Usage: + * - Automatically called when an IndexBuffer object goes out of scope. + */ + virtual ~NxIndexBuffer() = default; + + /** + * @brief Binds the index buffer as the active buffer in the graphics pipeline. + * + * Binding the index buffer ensures that subsequent rendering commands will use + * the indices stored in this buffer to draw primitives. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses (e.g., OpenGLIndexBuffer). + */ + virtual void bind() const = 0; + + /** + * @brief Unbinds the currently bound index buffer. + * + * Unbinding the index buffer ensures that no unintended operations are performed + * on the buffer. This is optional but useful for debugging or ensuring clean state management. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses (e.g., OpenGLIndexBuffer). + */ + virtual void unbind() const = 0; + + /** + * @brief Uploads new index data to the index buffer. + * + * This method replaces the contents of the index buffer with new data. It is + * commonly used to define how vertices are connected to form primitives. + * + * @param data Pointer to the array of indices to upload. + * @param size The number of indices to upload. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses. + */ + virtual void setData(unsigned int *data, size_t size) = 0; + + /** + * @brief Retrieves the number of indices in the index buffer. + * + * The count specifies how many indices are stored in the buffer, which is + * necessary for rendering indexed primitives. + * + * @return The number of indices in the buffer. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses. + */ + [[nodiscard]] virtual size_t getCount() const = 0; + + /** + * @brief Retrieves the unique identifier of the index buffer. + * + * The ID is typically assigned by the graphics API (e.g., OpenGL) when the buffer + * is created and is used for binding and managing the buffer in the graphics pipeline. + * + * @return The unique ID of the index buffer. + * + * Pure Virtual Function: + * - Must be implemented by platform-specific subclasses. + */ + [[nodiscard]] virtual unsigned int getId() const = 0; }; /** @@ -395,5 +478,4 @@ namespace nexo::renderer { */ std::shared_ptr createIndexBuffer(); - -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/DrawCommand.cpp b/engine/src/renderer/DrawCommand.cpp index 2fde5cb0a..ee7fdadef 100644 --- a/engine/src/renderer/DrawCommand.cpp +++ b/engine/src/renderer/DrawCommand.cpp @@ -28,10 +28,9 @@ namespace nexo::renderer { } // Bind VAO for mesh, or use full-screen quad - if (type == CommandType::MESH && vao && currentVAO != vao->getId()) { + if ((type == CommandType::MESH || type == CommandType::LINE) && vao && currentVAO != vao->getId()) { vao->bind(); - for (const auto &vbo : vao->getVertexBuffers()) - vbo->bind(); + for (const auto& vbo : vao->getVertexBuffers()) vbo->bind(); currentVAO = vao->getId(); } else if (type == CommandType::FULL_SCREEN) { auto quad = getFullscreenQuad(); @@ -42,7 +41,7 @@ namespace nexo::renderer { // Set uniforms if (shader) { for (auto const& [name, val] : uniforms) { - std::visit([&](auto&& v){ shader->setUniform(name, v); }, val); + std::visit([&](auto&& v) { shader->setUniform(name, v); }, val); } } @@ -50,6 +49,9 @@ namespace nexo::renderer { NxRenderCommand::drawIndexed(vao, vao->getIndexBuffer()->getCount()); } else if (type == CommandType::FULL_SCREEN) { NxRenderCommand::drawUnIndexed(6); + } else if (type == CommandType::LINE) { + NxRenderCommand::setLineWidth(lineWidth); + NxRenderCommand::drawIndexed(vao, vao->getIndexBuffer()->getCount(), CommandType::LINE); } } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/DrawCommand.hpp b/engine/src/renderer/DrawCommand.hpp index 3bf4045c8..967cd00a4 100644 --- a/engine/src/renderer/DrawCommand.hpp +++ b/engine/src/renderer/DrawCommand.hpp @@ -18,29 +18,33 @@ #include "VertexArray.hpp" namespace nexo::renderer { - - // Function to get the quad, initializing it on first use + /** + * @brief Retrieves a shared pointer to a fullscreen quad vertex array. + * + * This function initializes a fullscreen quad vertex array on its first invocation, + * creating a simple 2-triangle quad that covers the normalized device coordinates (NDC) + * from -1 to 1. The quad is defined with position and texture coordinate attributes. + * + * @return A shared pointer to the NxVertexArray representing the fullscreen quad. + * + * Example usage: + * ```cpp + * auto fullscreenQuad = getFullscreenQuad(); + * fullscreenQuad->bind(); + * ``` + */ inline std::shared_ptr getFullscreenQuad() { static std::shared_ptr s_fullscreenQuad = nullptr; if (!s_fullscreenQuad) { // Create a simple 2-triangle quad covering NDC [-1..1] - s_fullscreenQuad = createVertexArray(); - static float quadVertices[] = { - // positions // tex coords - -1.0f, 1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 0.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, - -1.0f, 1.0f, 0.0f, 1.0f, - 1.0f, -1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 1.0f - }; - const auto vb = createVertexBuffer(sizeof(quadVertices)); + s_fullscreenQuad = createVertexArray(); + static float quadVertices[] = {// positions // tex coords + -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + const auto vb = createVertexBuffer(sizeof(quadVertices)); vb->setData(quadVertices, sizeof(quadVertices)); - vb->setLayout({ - {NxShaderDataType::FLOAT2, "aPosition"}, - {NxShaderDataType::FLOAT2, "aTexCoord"} - }); + vb->setLayout({{NxShaderDataType::FLOAT2, "aPosition"}, {NxShaderDataType::FLOAT2, "aTexCoord"}}); s_fullscreenQuad->addVertexBuffer(vb); } return s_fullscreenQuad; @@ -48,6 +52,7 @@ namespace nexo::renderer { enum class CommandType { MESH, + LINE, FULL_SCREEN, }; @@ -59,8 +64,20 @@ namespace nexo::renderer { std::unordered_map uniforms; uint32_t filterMask = 0xFFFFFFFF; - bool isOpaque = true; + bool isOpaque = true; + + float lineWidth = 1.5f; + /** + * @brief Executes the draw command. + * + * This method binds the necessary shader and vertex array object (VAO), + * sets the required uniforms, and issues the appropriate draw call based + * on the command type (mesh or fullscreen). + * + * It optimizes state changes by tracking the currently bound shader and VAO, + * only rebinding them if they differ from the last used ones. + */ void execute() const; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Framebuffer.cpp b/engine/src/renderer/Framebuffer.cpp index ac87ca7c1..46abaf50a 100644 --- a/engine/src/renderer/Framebuffer.cpp +++ b/engine/src/renderer/Framebuffer.cpp @@ -21,11 +21,11 @@ namespace nexo::renderer { std::shared_ptr NxFramebuffer::create(const NxFramebufferSpecs &specs) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(specs); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(specs); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Framebuffer.hpp b/engine/src/renderer/Framebuffer.hpp index d9047d45c..137fa8be9 100644 --- a/engine/src/renderer/Framebuffer.hpp +++ b/engine/src/renderer/Framebuffer.hpp @@ -13,9 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include #include #include -#include namespace nexo::renderer { @@ -59,11 +59,12 @@ namespace nexo::renderer { * * Constructors: * - FrameBufferTextureSpecifications(): Default constructor with no format. - * - FrameBufferTextureSpecifications(const NxFrameBufferTextureFormats format): Initializes with a specified texture format. + * - FrameBufferTextureSpecifications(const NxFrameBufferTextureFormats format): Initializes with a specified + * texture format. */ struct NxFrameBufferTextureSpecifications { NxFrameBufferTextureSpecifications() = default; - NxFrameBufferTextureSpecifications(const NxFrameBufferTextureFormats format) : textureFormat(format) {}; + NxFrameBufferTextureSpecifications(const NxFrameBufferTextureFormats format) : textureFormat(format){}; NxFrameBufferTextureFormats textureFormat = NxFrameBufferTextureFormats::NONE; }; @@ -85,7 +86,10 @@ namespace nexo::renderer { */ struct NxFrameBufferAttachmentsSpecifications { NxFrameBufferAttachmentsSpecifications() = default; - NxFrameBufferAttachmentsSpecifications(const std::initializer_list attachments) : attachments(attachments) {}; + NxFrameBufferAttachmentsSpecifications( + const std::initializer_list attachments) + : attachments(attachments) + {} std::vector attachments; }; @@ -147,159 +151,255 @@ namespace nexo::renderer { * 4. Unbind the framebuffer to render to the default framebuffer (screen). */ class NxFramebuffer { - public: - /** - * @brief Destroys the framebuffer and releases associated resources. - * - * This virtual destructor ensures that derived classes properly clean up - * framebuffer resources (e.g., OpenGL framebuffers). - */ - virtual ~NxFramebuffer() = default; - - /** - * @brief Binds the framebuffer as the active rendering target. - * - * When a framebuffer is bound, subsequent rendering operations will be directed - * to this framebuffer instead of the default frame buffer (e.g., the screen). - */ - virtual void bind() = 0; - - virtual void bindAsTexture(unsigned int slot = 0, unsigned int attachment = 0) = 0; - virtual void bindDepthAsTexture(unsigned int slot = 0) = 0; - - /** - * @brief Unbinds the current framebuffer, reverting to the default framebuffer. - * - * After unbinding, subsequent rendering operations will target the default framebuffer - * (usually the screen or swap chain). - */ - virtual void unbind() = 0; - - virtual void setClearColor(const glm::vec4 &color) = 0; - - virtual void copy(std::shared_ptr source) = 0; - - /** - * @brief Retrieves the unique OpenGL ID of the framebuffer. - * - * The framebuffer ID is used internally by OpenGL to identify the framebuffer object. - * - * @return The OpenGL ID of the framebuffer. - */ - [[nodiscard]] virtual unsigned int getFramebufferId() const = 0; - - /** - * @brief Resizes the framebuffer to new dimensions. - * - * Resizing the framebuffer adjusts the dimensions of all associated texture attachments. - * This is commonly used when the viewport size changes. - * - * @param width The new width of the framebuffer in pixels. - * @param height The new height of the framebuffer in pixels. - */ - virtual void resize(unsigned int width, unsigned int height) = 0; - - [[nodiscard]] virtual glm::vec2 getSize() const = 0; - - virtual void getPixelWrapper(unsigned int attachementIndex, int x, int y, void *result, const std::type_info &ti) const = 0; - - - /** - * @brief Retrieves the pixel data from a specified attachment. - * - * Template version of getPixelWrapper. - * - * @tparam T The expected type of the pixel data. - * @param attachmentIndex The index of the attachment. - * @param x X-coordinate of the pixel. - * @param y Y-coordinate of the pixel. - * @return T The pixel data. - */ - template - T getPixel(const unsigned int attachmentIndex, const int x, const int y) const - { - T result; - getPixelWrapper(attachmentIndex, x, y, &result, typeid(T)); - return result; - } - - virtual void clearAttachmentWrapper(unsigned int attachmentIndex, const void *value, const std::type_info &ti) const = 0; - - - /** - * @brief Clears a specified attachment to a given value. - * - * Template version of clearAttachmentWrapper. - * - * @tparam T The type of the clear value. - * @param attachmentIndex The index of the attachment. - * @param value The value to clear the attachment to. - */ - template - void clearAttachment(unsigned int attachmentIndex, T value) const - { - clearAttachmentWrapper(attachmentIndex, &value, typeid(T)); - } - - /** - * @brief Retrieves the specifications of the framebuffer. - * - * This method provides access to the framebuffer's specifications, including - * dimensions, attachments, and sampling options. - * - * @return A reference to the NxFramebufferSpecs struct. - */ - [[nodiscard]] virtual NxFramebufferSpecs &getSpecs() = 0; - - /** - * @brief Retrieves the specifications of the framebuffer (const version). - * - * This method provides read-only access to the framebuffer's specifications. - * - * @return A constant reference to the NxFramebufferSpecs struct. - */ - [[nodiscard]] virtual const NxFramebufferSpecs &getSpecs() const = 0; - - [[nodiscard]] virtual unsigned int getNbColorAttachments() const = 0; - /** - * @brief Retrieves the OpenGL ID of a specific color attachment. - * - * Color attachments are textures that store the rendered color output. This method - * retrieves the OpenGL ID of the texture for the specified attachment index. - * - * @param index The index of the color attachment (default is 0). - * @return The OpenGL ID of the texture attachment. - * - * Notes: - * - If the framebuffer has multiple color attachments, specify the index accordingly. - * - An invalid index may result in undefined behavior. - */ - [[nodiscard]] virtual unsigned int getColorAttachmentId(unsigned int index = 0) const = 0; - - [[nodiscard]] virtual unsigned int getDepthAttachmentId() const = 0; - - [[nodiscard]] virtual bool hasDepthAttachment() const = 0; - [[nodiscard]] virtual bool hasStencilAttachment() const = 0; - [[nodiscard]] virtual bool hasDepthStencilAttachment() const = 0; - - /** - * @brief Creates a framebuffer based on the provided specifications. - * - * This static method abstracts the creation of a framebuffer, delegating the actual - * implementation to the active graphics API (e.g., OpenGL). - * - * @param specs The specifications for creating the framebuffer, including dimensions, - * attachments, and sampling options. - * @return A shared pointer to the created NxFramebuffer instance. - * - * Throws: - * - Implementation-specific exceptions if framebuffer creation fails. - * - * Notes: - * - This function is typically implemented in a platform-specific or API-specific source file. - */ - static std::shared_ptr create(const NxFramebufferSpecs& specs); + public: + /** + * @brief Destroys the framebuffer and releases associated resources. + * + * This virtual destructor ensures that derived classes properly clean up + * framebuffer resources (e.g., OpenGL framebuffers). + */ + virtual ~NxFramebuffer() = default; + + /** + * @brief Binds the framebuffer as the active rendering target. + * + * When a framebuffer is bound, subsequent rendering operations will be directed + * to this framebuffer instead of the default frame buffer (e.g., the screen). + */ + virtual void bind() = 0; + + /** + * @brief Binds a specific color attachment of the framebuffer as a texture. + * + * This method allows binding one of the framebuffer's color attachments + * to a specified texture slot for use in shaders. + * + * @param slot The texture slot to bind the attachment to (default is 0). + * @param attachment The index of the color attachment to bind (default is 0). + */ + virtual void bindAsTexture(unsigned int slot = 0, unsigned int attachment = 0) = 0; + + /** + * @brief Binds the depth attachment of the framebuffer as a texture. + * + * This method allows binding the framebuffer's depth attachment + * to a specified texture slot for use in shaders. + * + * @param slot The texture slot to bind the depth attachment to (default is 0). + */ + virtual void bindDepthAsTexture(unsigned int slot = 0) = 0; + + /** + * @brief Unbinds the current framebuffer, reverting to the default framebuffer. + * + * After unbinding, subsequent rendering operations will target the default framebuffer + * (usually the screen or swap chain). + */ + virtual void unbind() = 0; + + /** + * @brief Sets the clear color for the framebuffer. + * + * The clear color is used when clearing the framebuffer's color attachments. + * + * @param color A `glm::vec4` representing the RGBA clear color. + */ + virtual void setClearColor(const glm::vec4 &color) = 0; + + /** + * @brief Copies the contents from another framebuffer to this framebuffer. + * + * This method performs a blit operation, copying color and depth data + * from the source framebuffer to this framebuffer. + * + * @param source A shared pointer to the source `NxFramebuffer` to copy from. + */ + virtual void copy(std::shared_ptr source) = 0; + + /** + * @brief Retrieves the unique OpenGL ID of the framebuffer. + * + * The framebuffer ID is used internally by OpenGL to identify the framebuffer object. + * + * @return The OpenGL ID of the framebuffer. + */ + [[nodiscard]] virtual unsigned int getFramebufferId() const = 0; + + /** + * @brief Resizes the framebuffer to new dimensions. + * + * Resizing the framebuffer adjusts the dimensions of all associated texture attachments. + * This is commonly used when the viewport size changes. + * + * @param width The new width of the framebuffer in pixels. + * @param height The new height of the framebuffer in pixels. + */ + virtual void resize(unsigned int width, unsigned int height) = 0; + + /** + * @brief Retrieves the size of the framebuffer. + * + * @return A `glm::vec2` representing the width and height of the framebuffer. + */ + [[nodiscard]] virtual glm::vec2 getSize() const = 0; + + /** + * @brief Retrieves the pixel data from a specified attachment. + * + * This method extracts the pixel data at the given (x, y) coordinates from + * the specified attachment and stores it in the provided result pointer. + * + * @param attachementIndex The index of the attachment to read from. + * @param x X-coordinate of the pixel to retrieve. + * @param y Y-coordinate of the pixel to retrieve. + * @param result Pointer to store the retrieved pixel data. + * @param ti The type information of the expected pixel data type. + */ + virtual void getPixelWrapper(unsigned int attachementIndex, int x, int y, void *result, + const std::type_info &ti) const = 0; + + /** + * @brief Retrieves the pixel data from a specified attachment. + * + * Template version of getPixelWrapper. + * + * @tparam T The expected type of the pixel data. + * @param attachmentIndex The index of the attachment. + * @param x X-coordinate of the pixel. + * @param y Y-coordinate of the pixel. + * @return T The pixel data. + */ + template + T getPixel(const unsigned int attachmentIndex, const int x, const int y) const + { + T result; + getPixelWrapper(attachmentIndex, x, y, &result, typeid(T)); + return result; + } + + /** + * @brief Clears a specified attachment to a given value. + * + * This method clears the attachment at the specified index to the provided value. + * The type of the value is determined by the type information passed in. + * + * @param attachmentIndex The index of the attachment to clear. + * @param value Pointer to the value to clear the attachment to. + * @param ti The type information of the value. + */ + virtual void clearAttachmentWrapper(unsigned int attachmentIndex, const void *value, + const std::type_info &ti) const = 0; + + /** + * @brief Clears a specified attachment to a given value. + * + * Template version of clearAttachmentWrapper. + * + * @tparam T The type of the clear value. + * @param attachmentIndex The index of the attachment. + * @param value The value to clear the attachment to. + */ + template + void clearAttachment(unsigned int attachmentIndex, T value) const + { + clearAttachmentWrapper(attachmentIndex, &value, typeid(T)); + } + + /** + * @brief Retrieves the specifications of the framebuffer. + * + * This method provides access to the framebuffer's specifications, including + * dimensions, attachments, and sampling options. + * + * @return A reference to the NxFramebufferSpecs struct. + */ + [[nodiscard]] virtual NxFramebufferSpecs &getSpecs() = 0; + + /** + * @brief Retrieves the specifications of the framebuffer (const version). + * + * This method provides read-only access to the framebuffer's specifications. + * + * @return A constant reference to the NxFramebufferSpecs struct. + */ + [[nodiscard]] virtual const NxFramebufferSpecs &getSpecs() const = 0; + + /** + * @brief Retrieves the number of color attachments in the framebuffer. + * + * Color attachments are textures that store the rendered color output. This method + * returns the count of such attachments. + * + * @return The number of color attachments. + */ + [[nodiscard]] virtual unsigned int getNbColorAttachments() const = 0; + + /** + * @brief Retrieves the OpenGL ID of a specific color attachment. + * + * Color attachments are textures that store the rendered color output. This method + * retrieves the OpenGL ID of the texture for the specified attachment index. + * + * @param index The index of the color attachment (default is 0). + * @return The OpenGL ID of the texture attachment. + * + * Notes: + * - If the framebuffer has multiple color attachments, specify the index accordingly. + * - An invalid index may result in undefined behavior. + */ + [[nodiscard]] virtual unsigned int getColorAttachmentId(unsigned int index = 0) const = 0; + + /** + * @brief Retrieves the OpenGL ID of the depth attachment. + * + * The depth attachment is a texture that stores depth information for depth testing. + * This method retrieves the OpenGL ID of the depth texture attachment. + * + * @return The OpenGL ID of the depth texture attachment. + * + * Notes: + * - If the framebuffer does not have a depth attachment, the behavior is undefined. + */ + [[nodiscard]] virtual unsigned int getDepthAttachmentId() const = 0; + + /** + * @brief Checks if the framebuffer has a depth attachment. + * + * @return True if the framebuffer includes a depth attachment; otherwise, false. + */ + [[nodiscard]] virtual bool hasDepthAttachment() const = 0; + + /** + * @brief Checks if the framebuffer has a stencil attachment. + * + * @return True if the framebuffer includes a stencil attachment; otherwise, false. + */ + [[nodiscard]] virtual bool hasStencilAttachment() const = 0; + + /** + * @brief Checks if the framebuffer has a combined depth-stencil attachment. + * + * @return True if the framebuffer includes a depth-stencil attachment; otherwise, false. + */ + [[nodiscard]] virtual bool hasDepthStencilAttachment() const = 0; + + /** + * @brief Creates a framebuffer based on the provided specifications. + * + * This static method abstracts the creation of a framebuffer, delegating the actual + * implementation to the active graphics API (e.g., OpenGL). + * + * @param specs The specifications for creating the framebuffer, including dimensions, + * attachments, and sampling options. + * @return A shared pointer to the created NxFramebuffer instance. + * + * Throws: + * - Implementation-specific exceptions if framebuffer creation fails. + * + * Notes: + * - This function is typically implemented in a platform-specific or API-specific source file. + */ + static std::shared_ptr create(const NxFramebufferSpecs &specs); }; - -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/RenderCommand.cpp b/engine/src/renderer/RenderCommand.cpp index 6f494617f..1e79e04cf 100644 --- a/engine/src/renderer/RenderCommand.cpp +++ b/engine/src/renderer/RenderCommand.cpp @@ -19,14 +19,13 @@ namespace nexo::renderer { - #ifdef NX_GRAPHICS_API_OPENGL - NxRendererApi *NxRenderCommand::_rendererApi = new NxOpenGlRendererApi; - #endif +#ifdef NX_GRAPHICS_API_OPENGL + NxRendererApi *NxRenderCommand::_rendererApi = new NxOpenGlRendererApi; +#endif void NxRenderCommand::init() { - if (!_rendererApi) - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); + if (!_rendererApi) THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); _rendererApi->init(); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/RenderCommand.hpp b/engine/src/renderer/RenderCommand.hpp index 40388be19..cc7bf54b7 100644 --- a/engine/src/renderer/RenderCommand.hpp +++ b/engine/src/renderer/RenderCommand.hpp @@ -14,6 +14,7 @@ #pragma once #include "RendererAPI.hpp" +#include "DrawCommand.hpp" namespace nexo::renderer { /** @@ -44,203 +45,326 @@ namespace nexo::renderer { * determined by the preprocessor directive `NX_GRAPHICS_API_OPENGL` or similar. */ class NxRenderCommand { - public: - /** - * @brief Initializes the rendering API. - * - * This method initializes the underlying `NxRendererApi` instance, ensuring that the - * graphics API is ready to accept rendering commands. It must be called before issuing - * any other render commands. - * - * Throws: - * - NxUnknownGraphicsApi exception if the `_rendererApi` instance is null. - * - * Usage: - * - Typically called once during application initialization. - */ - static void init(); - - /** - * @brief Sets the viewport dimensions and position. - * - * The viewport defines the rectangular region of the window where rendering will - * take place. This command is delegated to the `NxRendererApi` implementation. - * - * @param x The x-coordinate of the lower-left corner of the viewport. - * @param y The y-coordinate of the lower-left corner of the viewport. - * @param width The width of the viewport in pixels. - * @param height The height of the viewport in pixels. - * - * Usage: - * - Call this method whenever the window is resized to adjust the rendering area. - */ - static void setViewport(const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height) { _rendererApi->setViewport(x, y, width, height); }; - - /** - * @brief Sets the clear color for the rendering context. - * - * The clear color is used when clearing the color buffer (e.g., during a call to - * `clear`). This command is delegated to the `NxRendererApi` implementation. - * - * @param color The color to set as the clear color, represented as a `glm::vec4` - * (RGBA format with components in the range [0, 1]). - * - * Usage: - * - Typically called once during application initialization or when changing the - * background color. - */ - static void setClearColor(const glm::vec4 &color) { _rendererApi->setClearColor(color); }; - - static void setClearDepth(const float depth) { _rendererApi->setClearDepth(depth); }; - - /** - * @brief Clears the screen using the current clear color. - * - * This method clears the color and/or depth buffers, preparing the screen for - * rendering the next frame. This command is delegated to the `NxRendererApi` - * implementation. - * - * Usage: - * - Call this method at the beginning of each frame to reset the screen. - */ - static void clear() { _rendererApi->clear(); }; - - /** - * @brief Draws indexed primitives using the specified vertex array. - * - * This method issues a draw call for rendering indexed geometry. The indices - * are provided by the index buffer bound to the vertex array. This command is - * delegated to the `NxRendererApi` implementation. - * - * @param vertexArray A shared pointer to the vertex array containing the geometry data. - * @param indexCount The number of indices to draw. If set to 0, the method will use - * the total index count from the bound index buffer. - * - * Usage: - * - Use this method to draw meshes or primitives with indexed geometry. - */ - static void drawIndexed(const std::shared_ptr &vertexArray, const size_t indexCount = 0) - { - _rendererApi->drawIndexed(vertexArray, indexCount); - } - - static void drawUnIndexed(const size_t verticesCount) - { - _rendererApi->drawUnIndexed(verticesCount); - } - - static void setDepthTest(const bool enable) - { - _rendererApi->setDepthTest(enable); - } - - static void setDepthMask(const bool enable) - { - _rendererApi->setDepthMask(enable); - } - - static void setDepthFunc(const unsigned int func) - { - _rendererApi->setDepthFunc(func); - } - - /** - * @brief Enables or disables the stencil test. - * - * The stencil test allows for masking certain portions of the screen during rendering. - * When enabled, fragments are drawn only if they pass a comparison test against the - * corresponding value in the stencil buffer. - * - * @param enable True to enable stencil testing, false to disable it. - * - * Usage: - * - Enable the stencil test before performing operations that will write to or use the stencil buffer. - * - Disable the stencil test when regular rendering should resume. - */ - static void setStencilTest(const bool enable) { _rendererApi->setStencilTest(enable); } - - /** - * @brief Sets the stencil mask that controls which bits of the stencil buffer are updated. - * - * The stencil mask determines which bits in the stencil buffer can be modified when - * stencil operations are performed. Only the bits that have a 1 in the corresponding - * position of the mask will be affected. - * - * @param mask The bit mask to use for stencil write operations. - * - * Usage: - * - Set a specific mask before performing stencil operations to control which bits are affected. - */ - static void setStencilMask(const unsigned int mask) { _rendererApi->setStencilMask(mask); } - - /** - * @brief Configures the stencil function used for stencil testing. - * - * The stencil function defines how the stencil test compares a reference value to the - * current value in the stencil buffer. The comparison result determines whether a fragment - * passes the stencil test and how the stencil buffer is updated. - * - * @param func The comparison function to use (e.g., GL_EQUAL, GL_ALWAYS, GL_LESS). - * @param ref The reference value to compare against. - * @param mask The mask that is ANDed with both the reference value and stored stencil value before comparison. - * - * Usage: - * - Configure before performing operations that rely on specific stencil buffer values. - */ - static void setStencilFunc(const unsigned int func, const int ref, const unsigned int mask) - { - _rendererApi->setStencilFunc(func, ref, mask); - } - - /** - * @brief Sets the operations to perform on the stencil buffer based on test outcomes. - * - * This method configures what happens to the stencil buffer value when the stencil test: - * - fails (sfail) - * - passes, but the depth test fails (dpfail) - * - passes, and the depth test also passes (dppass) - * - * @param sfail Operation to perform when the stencil test fails. - * @param dpfail Operation to perform when the stencil test passes but depth test fails. - * @param dppass Operation to perform when both stencil and depth tests pass. - * - * Usage: - * - Set before performing complex stencil operations like object outlining or shadow volumes. - */ - static void setStencilOp(const unsigned int sfail, const unsigned int dpfail, const unsigned int dppass) - { - _rendererApi->setStencilOp(sfail, dpfail, dppass); - } - - static void setCulling(const bool enable) - { - _rendererApi->setCulling(enable); - } - - static void setCulledFace(const CulledFace face) - { - _rendererApi->setCulledFace(face); - } - - static void setWindingOrder(const WindingOrder order) - { - _rendererApi->setWindingOrder(order); - } - - private: - /** - * @brief Static pointer to the active `NxRendererApi` implementation. - * - * This member holds a pointer to the concrete `NxRendererApi` instance (e.g., - * `NxOpenGlRendererApi`). It is initialized based on the active graphics API, - * as determined by preprocessor directives (e.g., `NX_GRAPHICS_API_OPENGL`). - * - * Notes: - * - The `_rendererApi` instance is statically allocated and shared across all - * `NxRenderCommand` methods. - * - The application must ensure that `_rendererApi` is initialized via `init()` - * before issuing any render commands. - */ - static NxRendererApi *_rendererApi; + public: + /** + * @brief Initializes the rendering API. + * + * This method initializes the underlying `NxRendererApi` instance, ensuring that the + * graphics API is ready to accept rendering commands. It must be called before issuing + * any other render commands. + * + * Throws: + * - NxUnknownGraphicsApi exception if the `_rendererApi` instance is null. + * + * Usage: + * - Typically called once during application initialization. + */ + static void init(); + + /** + * @brief Sets the viewport dimensions and position. + * + * The viewport defines the rectangular region of the window where rendering will + * take place. This command is delegated to the `NxRendererApi` implementation. + * + * @param x The x-coordinate of the lower-left corner of the viewport. + * @param y The y-coordinate of the lower-left corner of the viewport. + * @param width The width of the viewport in pixels. + * @param height The height of the viewport in pixels. + * + * Usage: + * - Call this method whenever the window is resized to adjust the rendering area. + */ + static void setViewport(const unsigned int x, const unsigned int y, const unsigned int width, + const unsigned int height) + { + _rendererApi->setViewport(x, y, width, height); + } + + /** + * @brief Sets the clear color for the rendering context. + * + * The clear color is used when clearing the color buffer (e.g., during a call to + * `clear`). This command is delegated to the `NxRendererApi` implementation. + * + * @param color The color to set as the clear color, represented as a `glm::vec4` + * (RGBA format with components in the range [0, 1]). + * + * Usage: + * - Typically called once during application initialization or when changing the + * background color. + */ + static void setClearColor(const glm::vec4 &color) + { + _rendererApi->setClearColor(color); + } + + /** + * @brief Sets the clear depth value for the depth buffer. + * + * The clear depth value is used when clearing the depth buffer (e.g., during a call to + * `clear`). This command is delegated to the `NxRendererApi` implementation. + * + * @param depth The depth value to set as the clear depth, typically in the range [0, 1]. + * + * Usage: + * - Typically called once during application initialization or when changing the + * depth clearing behavior. + */ + static void setClearDepth(const float depth) + { + _rendererApi->setClearDepth(depth); + } + + static void setLineWidth(float lineWidth) + { + _rendererApi->setLineWidth(lineWidth); + } + + /** + * @brief Clears the screen using the current clear color. + * + * This method clears the color and/or depth buffers, preparing the screen for + * rendering the next frame. This command is delegated to the `NxRendererApi` + * implementation. + * + * Usage: + * - Call this method at the beginning of each frame to reset the screen. + */ + static void clear() + { + _rendererApi->clear(); + } + + /** + * @brief Draws indexed primitives using the specified vertex array. + * + * This method issues a draw call for rendering indexed geometry. The indices + * are provided by the index buffer bound to the vertex array. This command is + * delegated to the `NxRendererApi` implementation. + * + * @param vertexArray A shared pointer to the vertex array containing the geometry data. + * @param indexCount The number of indices to draw. If set to 0, the method will use + * the total index count from the bound index buffer. + * + * Usage: + * - Use this method to draw meshes or primitives with indexed geometry. + */ + static void drawIndexed(const std::shared_ptr &vertexArray, const size_t indexCount = 0, CommandType primitiveType = CommandType::MESH) + { + _rendererApi->drawIndexed(vertexArray, indexCount, primitiveType); + } + + /** + * @brief Draws non-indexed primitives. + * + * This method issues a draw call for rendering non-indexed geometry. It uses + * the vertex data directly from the bound vertex buffer. This command is + * delegated to the `NxRendererApi` implementation. + * + * @param verticesCount The number of vertices to draw. + * + * Usage: + * - Use this method to draw simple primitives or geometry without an index buffer. + */ + static void drawUnIndexed(const size_t verticesCount) + { + _rendererApi->drawUnIndexed(verticesCount); + } + + /** + * @brief Enables or disables the depth test. + * + * The depth test determines whether a fragment should be drawn based on its depth + * value compared to the existing value in the depth buffer. When enabled, fragments + * that are behind existing geometry will be discarded. + * + * @param enable True to enable depth testing, false to disable it. + * + * Usage: + * - Enable the depth test when rendering 3D scenes to ensure correct occlusion. + * - Disable the depth test for 2D rendering or when rendering transparent objects. + */ + static void setDepthTest(const bool enable) + { + _rendererApi->setDepthTest(enable); + } + + /** + * @brief Enables or disables writing to the depth buffer. + * + * When depth writing is enabled, the depth values of rendered fragments will + * be written to the depth buffer. Disabling depth writing can be useful for + * rendering transparent objects or overlays where depth information should not + * be updated. + * + * @param enable True to enable depth writing, false to disable it. + * + * Usage: + * - Enable depth writing when rendering opaque geometry. + * - Disable depth writing when rendering transparent objects or UI elements. + */ + static void setDepthMask(const bool enable) + { + _rendererApi->setDepthMask(enable); + } + + /** + * @brief Sets the depth comparison function used for depth testing. + * + * The depth function determines how the depth of incoming fragments is compared + * to the existing depth values in the depth buffer. Common functions include + * GL_LESS, GL_LEQUAL, GL_GREATER, etc. + * + * @param func The depth comparison function to use (e.g., GL_LESS, GL_ALWAYS). + * + * Usage: + * - Set the depth function based on the desired depth testing behavior for your scene. + */ + static void setDepthFunc(const unsigned int func) + { + _rendererApi->setDepthFunc(func); + } + + /** + * @brief Enables or disables the stencil test. + * + * The stencil test allows for masking certain portions of the screen during rendering. + * When enabled, fragments are drawn only if they pass a comparison test against the + * corresponding value in the stencil buffer. + * + * @param enable True to enable stencil testing, false to disable it. + * + * Usage: + * - Enable the stencil test before performing operations that will write to or use the stencil buffer. + * - Disable the stencil test when regular rendering should resume. + */ + static void setStencilTest(const bool enable) + { + _rendererApi->setStencilTest(enable); + } + + /** + * @brief Sets the stencil mask that controls which bits of the stencil buffer are updated. + * + * The stencil mask determines which bits in the stencil buffer can be modified when + * stencil operations are performed. Only the bits that have a 1 in the corresponding + * position of the mask will be affected. + * + * @param mask The bit mask to use for stencil write operations. + * + * Usage: + * - Set a specific mask before performing stencil operations to control which bits are affected. + */ + static void setStencilMask(const unsigned int mask) + { + _rendererApi->setStencilMask(mask); + } + + /** + * @brief Configures the stencil function used for stencil testing. + * + * The stencil function defines how the stencil test compares a reference value to the + * current value in the stencil buffer. The comparison result determines whether a fragment + * passes the stencil test and how the stencil buffer is updated. + * + * @param func The comparison function to use (e.g., GL_EQUAL, GL_ALWAYS, GL_LESS). + * @param ref The reference value to compare against. + * @param mask The mask that is ANDed with both the reference value and stored stencil value before comparison. + * + * Usage: + * - Configure before performing operations that rely on specific stencil buffer values. + */ + static void setStencilFunc(const unsigned int func, const int ref, const unsigned int mask) + { + _rendererApi->setStencilFunc(func, ref, mask); + } + + /** + * @brief Sets the operations to perform on the stencil buffer based on test outcomes. + * + * This method configures what happens to the stencil buffer value when the stencil test: + * - fails (sfail) + * - passes, but the depth test fails (dpfail) + * - passes, and the depth test also passes (dppass) + * + * @param sfail Operation to perform when the stencil test fails. + * @param dpfail Operation to perform when the stencil test passes but depth test fails. + * @param dppass Operation to perform when both stencil and depth tests pass. + * + * Usage: + * - Set before performing complex stencil operations like object outlining or shadow volumes. + */ + static void setStencilOp(const unsigned int sfail, const unsigned int dpfail, const unsigned int dppass) + { + _rendererApi->setStencilOp(sfail, dpfail, dppass); + } + + /** + * @brief Enables or disables face culling. + * + * Face culling is a performance optimization technique that prevents the rendering + * of faces (triangles) that are not visible to the camera. When enabled, back faces + * (or front faces, depending on the winding order) are not drawn. + * + * @param enable True to enable face culling, false to disable it. + * + * Usage: + * - Enable face culling when rendering closed 3D models to improve performance. + * - Disable face culling for transparent objects or when both sides of a surface need to be visible. + */ + static void setCulling(const bool enable) + { + _rendererApi->setCulling(enable); + } + + /** + * @brief Specifies which face (front or back) should be culled. + * + * This method sets whether front faces or back faces of polygons should be culled + * when face culling is enabled. The choice depends on the winding order of the vertices. + * + * @param face The face to cull (e.g., BACK_FACE, FRONT_FACE). + * + * Usage: + * - Set according to the winding order of your models and the desired culling behavior. + */ + static void setCulledFace(const CulledFace face) + { + _rendererApi->setCulledFace(face); + } + + /** + * @brief Sets the winding order used to determine front-facing polygons. + * + * The winding order defines the order in which vertices are specified for a polygon + * (clockwise or counter-clockwise). This setting is used to determine which faces + * are considered front-facing and which are back-facing for culling purposes. + * + * @param order The winding order to use (e.g., CLOCKWISE, COUNTER_CLOCKWISE). + * + * Usage: + * - Set according to the vertex order used in your models to ensure correct face culling. + */ + static void setWindingOrder(const WindingOrder order) + { + _rendererApi->setWindingOrder(order); + } + + private: + /** + * @brief Static pointer to the active `NxRendererApi` implementation. + * + * This member holds a pointer to the concrete `NxRendererApi` instance (e.g., + * `NxOpenGlRendererApi`). It is initialized based on the active graphics API, + * as determined by preprocessor directives (e.g., `NX_GRAPHICS_API_OPENGL`). + * + * Notes: + * - The `_rendererApi` instance is statically allocated and shared across all + * `NxRenderCommand` methods. + * - The application must ensure that `_rendererApi` is initialized via `init()` + * before issuing any render commands. + */ + static NxRendererApi *_rendererApi; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/RenderPass.hpp b/engine/src/renderer/RenderPass.hpp index 63bc25d47..96263a529 100644 --- a/engine/src/renderer/RenderPass.hpp +++ b/engine/src/renderer/RenderPass.hpp @@ -12,45 +12,98 @@ // /////////////////////////////////////////////////////////////////////////////// #pragma once +#include #include #include #include -#include #include "Framebuffer.hpp" - namespace nexo::renderer { using PassId = uint32_t; class RenderPipeline; class RenderPass { - public: - explicit RenderPass(const PassId id, std::string debugName = "") : id(id), name(std::move(debugName)) {} - virtual ~RenderPass() = default; - - // The actual rendering work - virtual void execute(RenderPipeline& pipeline) = 0; - virtual void resize([[maybe_unused]] unsigned int width, [[maybe_unused]] unsigned int height) {}; - void setFinal(const bool isFinal) {m_isFinal = isFinal;}; - [[nodiscard]] bool isFinal() const {return m_isFinal;} - - [[nodiscard]] PassId getId() const { return id; } - [[nodiscard]] const std::string& getName() const { return name; } - std::vector &getPrerequisites() { return prerequisites; } - [[nodiscard]] const std::vector &getPrerequisites() const { return prerequisites; } - std::vector &getEffects() { return effects; } - [[nodiscard]] const std::vector &getEffects() const { return effects; } - - virtual std::shared_ptr getOutput() const { return nullptr; }; - protected: - bool m_isFinal = false; - PassId id; - std::string name; - - // Prerequisites - which passes must run before this one - std::vector prerequisites; - // Effects - which passes this one enables - std::vector effects; + public: + explicit RenderPass(const PassId id, std::string debugName = "") : id(id), name(std::move(debugName)) + {} + virtual ~RenderPass() = default; + + // The actual rendering work + /// Executes the render pass processing + /// @param pipeline Reference to the render pipeline this pass belongs to + virtual void execute(RenderPipeline &pipeline) = 0; + + /// Handles resize events for the render pass + /// @param width New width in pixels + /// @param height New height in pixels + virtual void resize([[maybe_unused]] unsigned int width, [[maybe_unused]] unsigned int height){ + // Default implementation does nothing + }; + + /// Sets whether this pass is the final one in the pipeline + /// @param isFinal True if this is the final pass + void setFinal(const bool isFinal) + { + m_isFinal = isFinal; + } + + /// @returns True if this is the final pass in the pipeline + [[nodiscard]] bool isFinal() const + { + return m_isFinal; + } + + /// @returns The unique identifier of this pass + [[nodiscard]] PassId getId() const + { + return id; + } + + /// @returns The debug name of this pass + [[nodiscard]] const std::string &getName() const + { + return name; + } + + /// @returns Reference to the list of prerequisite pass IDs + std::vector &getPrerequisites() + { + return prerequisites; + } + + /// @returns Const reference to the list of prerequisite pass IDs + [[nodiscard]] const std::vector &getPrerequisites() const + { + return prerequisites; + } + + /// @returns Reference to the list of pass IDs that depend on this one + std::vector &getEffects() + { + return effects; + } + + /// @returns Const reference to the list of pass IDs that depend on this one + [[nodiscard]] const std::vector &getEffects() const + { + return effects; + } + + /// @returns The framebuffer output of this pass, or nullptr if none + [[nodiscard]] virtual std::shared_ptr getOutput() const + { + return nullptr; + } + + protected: + bool m_isFinal = false; + PassId id; + std::string name; + + // Prerequisites - which passes must run before this one + std::vector prerequisites; + // Effects - which passes this one enables + std::vector effects; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/RenderPipeline.cpp b/engine/src/renderer/RenderPipeline.cpp index 56d17264a..e642892d8 100644 --- a/engine/src/renderer/RenderPipeline.cpp +++ b/engine/src/renderer/RenderPipeline.cpp @@ -12,36 +12,35 @@ // /////////////////////////////////////////////////////////////////////////////// #include "RenderPipeline.hpp" -#include "Exception.hpp" -#include "Framebuffer.hpp" -#include "RenderCommand.hpp" -#include "RendererExceptions.hpp" -#include "Renderer3D.hpp" #include +#include #include #include +#include "Exception.hpp" +#include "Framebuffer.hpp" +// #include "RenderCommand.hpp" +#include "Renderer3D.hpp" +#include "RendererExceptions.hpp" namespace nexo::renderer { PassId RenderPipeline::addRenderPass(std::shared_ptr pass) { // If this is the first pass, set it as the final output const PassId id = pass->getId(); - passes[id] = std::move(pass); - if (passes.size() == 1) - setFinalOutputPass(id); + passes[id] = std::move(pass); + if (passes.size() == 1) setFinalOutputPass(id); m_isDirty = true; return id; } void RenderPipeline::removeRenderPass(const PassId id) { - if (!passes.contains(id)) - return; - const auto &pass = passes[id]; + if (!passes.contains(id)) return; + const auto& pass = passes[id]; // Save dependencies before removal - std::vector prerequisites = pass->getPrerequisites(); - std::vector effects = pass->getEffects(); + const std::vector prerequisites = pass->getPrerequisites(); + const std::vector effects = pass->getEffects(); // For each prerequisite -> effect pair, create a new relationship for (const PassId prereqId : prerequisites) { @@ -52,13 +51,13 @@ namespace nexo::renderer { } // Remove this pass from all prerequisites lists - for (const auto &[passId, p] : passes) { + for (const auto& p : passes | std::views::values) { auto& prereqs = p->getPrerequisites(); std::erase(prereqs, id); } // Remove this pass from all effects lists - for (const auto& [passId, p] : passes) { + for (const auto& p : passes | std::views::values) { auto& effs = p->getEffects(); std::erase(effs, id); } @@ -89,18 +88,15 @@ namespace nexo::renderer { void RenderPipeline::addPrerequisite(const PassId pass, const PassId prerequisite) { - if (!passes.contains(pass) || !passes.contains(prerequisite)) - return; + if (!passes.contains(pass) || !passes.contains(prerequisite)) return; auto& prereqs = passes[pass]->getPrerequisites(); - if (std::find(prereqs.begin(), prereqs.end(), prerequisite) == prereqs.end()) - prereqs.push_back(prerequisite); + if (std::ranges::find(prereqs, prerequisite) == prereqs.end()) prereqs.push_back(prerequisite); m_isDirty = true; } void RenderPipeline::removePrerequisite(const PassId pass, const PassId prerequisite) { - if (!passes.contains(pass)) - return; + if (!passes.contains(pass)) return; auto& prereqs = passes[pass]->getPrerequisites(); std::erase(prereqs, prerequisite); m_isDirty = true; @@ -108,18 +104,15 @@ namespace nexo::renderer { void RenderPipeline::addEffect(const PassId pass, const PassId effect) { - if (!passes.contains(pass) || !passes.contains(effect)) - return; + if (!passes.contains(pass) || !passes.contains(effect)) return; auto& effects = passes[pass]->getEffects(); - if (std::find(effects.begin(), effects.end(), effect) == effects.end()) - effects.push_back(effect); + if (std::ranges::find(effects, effect) == effects.end()) effects.push_back(effect); m_isDirty = true; } void RenderPipeline::removeEffect(const PassId pass, const PassId effect) { - if (!passes.contains(pass)) - return; + if (!passes.contains(pass)) return; auto& effects = passes[pass]->getEffects(); std::erase(effects, effect); m_isDirty = true; @@ -128,8 +121,7 @@ namespace nexo::renderer { std::shared_ptr RenderPipeline::getRenderPass(const PassId id) { const auto it = passes.find(id); - if (it != passes.end()) - return it->second; + if (it != passes.end()) return it->second; return nullptr; } @@ -146,8 +138,7 @@ namespace nexo::renderer { void RenderPipeline::setFinalOutputPass(const PassId id) { if (passes.contains(id)) { - if (finalOutputPass != -1 && passes.contains(finalOutputPass)) - passes[finalOutputPass]->setFinal(false); + if (finalOutputPass != -1 && passes.contains(finalOutputPass)) passes[finalOutputPass]->setFinal(false); passes[id]->setFinal(true); finalOutputPass = static_cast(id); @@ -188,13 +179,11 @@ namespace nexo::renderer { std::set visited; // DFS helper to build execution plan std::function buildPlan = [&](const PassId current) { - if (visited.contains(current)) - return; + if (visited.contains(current)) return; // First process all prerequisites for (const PassId prereq : passes[current]->getPrerequisites()) { - if (passes.contains(prereq)) - buildPlan(prereq); + if (passes.contains(prereq)) buildPlan(prereq); } visited.insert(current); result.push_back(current); @@ -205,13 +194,11 @@ namespace nexo::renderer { } else { auto terminals = findTerminalPasses(); if (terminals.empty()) { - for (const auto& [id, _] : passes) - terminals.push_back(id); + for (const auto& [id, _] : passes) terminals.push_back(id); } // Process each terminal pass - for (const PassId term : terminals) - buildPlan(term); + for (const PassId term : terminals) buildPlan(term); } m_isDirty = false; @@ -220,15 +207,12 @@ namespace nexo::renderer { void RenderPipeline::execute() { - if (m_isDirty) - m_plan = createExecutionPlan(); + if (m_isDirty) m_plan = createExecutionPlan(); - if (!m_renderTarget) - THROW_EXCEPTION(NxPipelineRenderTargetNotSetException); + if (!m_renderTarget) THROW_EXCEPTION(NxPipelineRenderTargetNotSetException); for (PassId id : m_plan) { - if (passes.contains(id)) - passes[id]->execute(*this); + if (passes.contains(id)) passes[id]->execute(*this); } m_drawCommands.clear(); } @@ -261,10 +245,8 @@ namespace nexo::renderer { void RenderPipeline::resize(const unsigned int width, const unsigned int height) const { - if (!m_renderTarget) - return; + if (!m_renderTarget) return; m_renderTarget->resize(width, height); - for (const auto& [_, pass] : passes) - pass->resize(width, height); + for (const auto& pass : passes | std::views::values) pass->resize(width, height); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/RenderPipeline.hpp b/engine/src/renderer/RenderPipeline.hpp index 7aa184c32..54c01585e 100644 --- a/engine/src/renderer/RenderPipeline.hpp +++ b/engine/src/renderer/RenderPipeline.hpp @@ -12,86 +12,319 @@ // /////////////////////////////////////////////////////////////////////////////// #pragma once +#include +#include +#include +#include "DrawCommand.hpp" #include "Framebuffer.hpp" #include "RenderPass.hpp" -#include "DrawCommand.hpp" -#include -#include -#include namespace nexo::renderer { class RenderPipeline { - public: - // Add a render pass to the pipeline - PassId addRenderPass(std::shared_ptr pass); + public: + /** + * @brief Adds a render pass to the rendering pipeline. + * + * This method allows you to add a new render pass to the pipeline. Each render pass + * represents a distinct stage in the rendering process, such as shadow mapping, + * post-processing effects, or final composition. By adding a render pass, you can + * define the sequence of operations that will be executed during rendering. + * + * The method takes a shared pointer to a `RenderPass` object and returns a unique + * identifier (PassId) for the added pass. This ID can be used later to reference + * the pass for operations like setting prerequisites, effects, or removal. + * + * @param pass A shared pointer to the `RenderPass` object to be added to the pipeline. + * @return A unique identifier (PassId) for the added render pass. + */ + PassId addRenderPass(std::shared_ptr pass); + + /** + * @brief Removes a render pass from the rendering pipeline and reconnects its dependencies. + * + * This method removes a specified render pass from the pipeline. Before removal, it saves the + * prerequisites and effects of the pass to be removed. It then creates new relationships between + * each prerequisite and each effect of the removed pass, effectively reconnecting the dependencies + * in the pipeline. This ensures that the overall rendering sequence remains intact even after the + * removal of a pass. + * + * If the specified render pass does not exist in the pipeline, the method does nothing. + * + * @param id The unique identifier (PassId) of the render pass to be removed. + */ + void removeRenderPass(PassId id); + + /** + * @brief Adds a prerequisite to a specified render pass in the pipeline. + * + * This method establishes a relationship between two render passes, where one pass (the prerequisite) + * must be executed before another pass (the dependent pass). By adding a prerequisite, you are + * indicating that the specified render pass should be executed after the given prerequisite pass, + * allowing for complex rendering sequences and dependencies within the pipeline. + * + * If either the specified render pass or prerequisite does not exist in the pipeline, the method does nothing. + * + * @param pass The unique identifier (PassId) of the render pass to which the prerequisite is being added. + * @param prerequisite The unique identifier (PassId) of the prerequisite (render pass) that must be executed + * before the given pass. + */ + void addPrerequisite(PassId pass, PassId prerequisite); + + /** + * @brief Removes a prerequisite from a specified render pass in the pipeline. + * + * This method removes a specified prerequisite (another render pass) from the list of prerequisites + * associated with a given render pass. If the specified render pass does not exist in the pipeline, + * the method does nothing. Removing a prerequisite means that the specified render pass will no longer + * be considered as a dependency that must be executed before the given render pass. + * + * @param pass The unique identifier (PassId) of the render pass from which to remove the prerequisite. + * @param prerequisite The unique identifier (PassId) of the prerequisite (render pass) to be removed. + */ + void removePrerequisite(PassId pass, PassId prerequisite); + + /** + * @brief Adds an effect to a specified render pass in the pipeline. + * + * This method establishes a relationship between two render passes, where one pass (the effect) + * is dependent on the output of another pass (the prerequisite). By adding an effect, you are + * indicating that the specified render pass should be executed after the given prerequisite pass, + * allowing for complex rendering sequences and dependencies within the pipeline. + * + * If either the specified render pass or effect does not exist in the pipeline, the method does nothing. + * + * @param pass The unique identifier (PassId) of the render pass to which the effect is being added. + * @param effect The unique identifier (PassId) of the effect (render pass) that depends on the output of the + * given pass. + */ + void addEffect(PassId pass, PassId effect); - // Remove a render pass from the pipeline and automatically reconnect dependencies - void removeRenderPass(PassId id); + /** + * @brief Removes an effect from a specified render pass in the pipeline. + * + * This method removes a specified effect (another render pass) from the list of effects + * associated with a given render pass. If the specified render pass or effect does not exist + * in the pipeline, the method does nothing. Removing an effect means that the specified + * render pass will no longer be considered as a subsequent pass that depends on the output + * of the given render pass. + * + * @param pass The unique identifier (PassId) of the render pass from which to remove the effect. + * @param effect The unique identifier (PassId) of the effect (render pass) to be removed. + */ + void removeEffect(PassId pass, PassId effect); - // Add a prerequisite - void addPrerequisite(PassId pass, PassId prerequisite); + /** + * @brief Retrieves a render pass by its unique identifier. + * + * This method searches for a render pass within the pipeline using its unique identifier (PassId). + * If a render pass with the specified ID exists, it returns a shared pointer to that render pass. + * If no such render pass is found, it returns a null pointer. + * + * @param id The unique identifier of the render pass to retrieve. + * @return A shared pointer to the render pass if found, or nullptr if not found. + */ + std::shared_ptr getRenderPass(PassId id); - // Remove a prerequisite - void removePrerequisite(PassId pass, PassId prerequisite); + /** + * @brief Sets the render target framebuffer for the pipeline. + * + * This method allows you to specify a framebuffer that will be used as the render target + * for the rendering pipeline. The render target is where the final rendered image or output + * will be directed. By setting a render target, you can control where the rendering results + * are stored, whether it's to the screen, an off-screen buffer, or a texture. + * + * @param finalRenderTarget A shared pointer to the framebuffer to be used as the render target. + */ + void setRenderTarget(std::shared_ptr finalRenderTarget); - // Add an effect - void addEffect(PassId pass, PassId effect); + /** + * @brief Retrieves the current render target framebuffer. + * + * This method returns a shared pointer to the framebuffer that is currently set as the render target + * for the rendering pipeline. The render target is where the final rendered image or output will be + * directed. If no render target has been set, this method may return a null pointer. + * + * @return A shared pointer to the current render target framebuffer, or nullptr if none is set. + */ + [[nodiscard]] std::shared_ptr getRenderTarget() const; - // Remove an effect - void removeEffect(PassId pass, PassId effect); + /** + * @brief Sets the PassId of the final output render pass in the pipeline. + * + * This method designates a specific render pass as the final output pass in the rendering pipeline. + * The final output pass is typically responsible for producing the final rendered image or result + * that will be presented to the screen or further processed. By setting this pass, you ensure that + * it is recognized as the last step in the rendering sequence. + * + * @param id The PassId of the render pass to be set as the final output pass. + */ + void setFinalOutputPass(PassId id); - // Get a render pass by ID - std::shared_ptr getRenderPass(PassId id); + /** + * @brief Retrieves the PassId of the final output render pass in the pipeline. + * + * This method returns the unique identifier (PassId) of the render pass that is designated + * as the final output pass in the rendering pipeline. The final output pass is typically + * responsible for producing the final rendered image or result that will be presented to + * the screen or further processed. + * + * @return The PassId of the final output render pass. + */ + [[nodiscard]] PassId getFinalOutputPass() const + { + return finalOutputPass; + } - void setRenderTarget(std::shared_ptr finalRenderTarget); - std::shared_ptr getRenderTarget() const; + /** + * @brief Creates an execution plan for the render pipeline. + * + * This method generates a valid execution order for the render passes in the pipeline, + * taking into account their prerequisites and effects. The execution plan ensures that + * all dependencies are respected, allowing for correct rendering results. + * + * The method uses a topological sorting algorithm to determine the order of execution. + * If a cycle is detected in the dependencies, an exception is thrown to indicate that + * the pipeline configuration is invalid. + * + * @throws Exception if a cycle is detected in the render pass dependencies. + * @return A vector containing the ordered list of PassId representing the execution plan. + */ + std::vector createExecutionPlan(); - // Set the final output pass - void setFinalOutputPass(PassId id); + /** + * @brief Executes the render pipeline according to the established execution plan. + * + * This method processes each render pass in the order defined by the execution plan, + * ensuring that all prerequisites are met before executing each pass. The final output + * is directed to the designated render target framebuffer. + * + * If the execution plan is marked as dirty (i.e., it has changed since the last execution), + * the method will regenerate the plan before proceeding with execution. + * + * @throws Exception if any render pass fails during execution. + */ + void execute(); - // Get the final output pass - PassId getFinalOutputPass() const { return finalOutputPass; } + /** + * @brief Finds all terminal render passes in the pipeline. + * + * This method identifies and returns a list of all terminal render passes within the pipeline. + * A terminal render pass is defined as a pass that does not have any effects, meaning it does + * not lead to any subsequent passes. Terminal passes are typically the final steps in the + * rendering process, producing the final output or results. + * + * @return A vector containing the unique identifiers (PassId) of all terminal render passes. + */ + [[nodiscard]] std::vector findTerminalPasses() const; - // Calculate execution plan using DFS - std::vector createExecutionPlan(); + /** + * @brief Checks if a render pass has any prerequisites. + * + * This method determines whether the specified render pass has any prerequisites associated with it. + * Prerequisites are other passes that must be executed before the current pass can run. If a pass has + * prerequisites, it indicates that there are dependencies in the rendering pipeline that need to be + * resolved first. + * + * @param id The unique identifier of the render pass to check. + * @return True if the render pass has prerequisites, false otherwise. + */ + [[nodiscard]] bool hasPrerequisites(PassId id) const; - // Execute the pipeline - void execute(); + /** + * @brief Checks if a render pass has any effects. + * + * This method determines whether the specified render pass has any effects associated with it. + * Effects are subsequent passes that depend on the output of the current pass. If a pass has + * effects, it indicates that there are other passes in the pipeline that utilize its results. + * + * @param id The unique identifier of the render pass to check. + * @return True if the render pass has effects + **/ + [[nodiscard]] bool hasEffects(PassId id) const; - // Find terminal passes (passes with no effects) - std::vector findTerminalPasses() const; + /** + * @brief Adds multiple draw commands to the render pipeline. + * + * This method allows you to add a collection of `DrawCommand` objects to the render pipeline. + * Each `DrawCommand` represents a specific drawing operation that will be executed during + * the rendering process. By adding multiple commands at once, you can efficiently batch + * rendering operations. + * + * @param drawCommands A vector of `DrawCommand` objects to be added to the pipeline. + */ + void addDrawCommands(const std::vector &drawCommands); - // Check if a pass has prerequisites - bool hasPrerequisites(PassId id) const; + /** + * @brief Adds a single draw command to the render pipeline. + * + * This method allows you to add an individual `DrawCommand` object to the render pipeline. + * The `DrawCommand` represents a specific drawing operation that will be executed during + * the rendering process. This is useful for adding commands one at a time. + * + * @param drawCommand A `DrawCommand` object to be added to the pipeline. + */ + void addDrawCommand(const DrawCommand &drawCommand); - // Check if a pass has effects - bool hasEffects(PassId id) const; + /** + * @brief Retrieves the list of draw commands added to the pipeline. + * + * This method returns a constant reference to the vector of `DrawCommand` objects + * that have been added to the render pipeline. These commands represent the drawing + * operations that will be executed during the rendering process. + * + * @return A constant reference to a vector of `DrawCommand` objects. + */ + [[nodiscard]] const std::vector &getDrawCommands() const; - void addDrawCommands(const std::vector &drawCommands); - void addDrawCommand(const DrawCommand &drawCommand); - const std::vector &getDrawCommands() const; + /** + * @brief Sets the clear color used by the camera. + * + * This method allows you to specify the RGBA color vector that will be used to clear the screen + * at the start of each frame. The clear color is typically set to a background color for the scene. + * + * @param clearColor A `glm::vec4` representing the clear color in RGBA format. + */ + void setCameraClearColor(const glm::vec4 &clearColor); - void setCameraClearColor(const glm::vec4 &clearColor); - const glm::vec4 &getCameraClearColor() const; + /** + * @brief Retrieves the clear color used by the camera. + * + * This method returns the RGBA color vector that is used to clear the screen + * at the start of each frame. The clear color is typically set to a background + * color for the scene. + * + * @return A constant reference to a `glm::vec4` representing the clear color. + */ + [[nodiscard]] const glm::vec4 &getCameraClearColor() const; - void resize(unsigned int width, unsigned int height) const; + /** + * @brief Resizes the render target and all associated render passes. + * + * This method adjusts the dimensions of the framebuffer used as the final render target, + * as well as resizing all render passes that depend on it. + * + * @param width The new width for the render target and passes. + * @param height The new height for the render target and passes. + * If no render target is set, the method exits early without performing any actions. + */ + void resize(unsigned int width, unsigned int height) const; - private: - std::vector m_drawCommands; - glm::vec4 m_cameraClearColor{}; - std::vector m_plan{}; - bool m_isDirty = true; + private: + std::vector m_drawCommands; + glm::vec4 m_cameraClearColor{}; + std::vector m_plan{}; + bool m_isDirty = true; - // Store all render passes - std::unordered_map> passes; + // Store all render passes + std::unordered_map> passes; - // For generating unique IDs - PassId nextPassId = 0; + // For generating unique IDs + PassId nextPassId = 0; - std::shared_ptr m_renderTarget = nullptr; + std::shared_ptr m_renderTarget = nullptr; - // The final output pass (what gets rendered to screen) - int finalOutputPass = -1; + // The final output pass (what gets rendered to screen) + int finalOutputPass = -1; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Renderer.cpp b/engine/src/renderer/Renderer.cpp index 40ee26a16..677f82825 100644 --- a/engine/src/renderer/Renderer.cpp +++ b/engine/src/renderer/Renderer.cpp @@ -28,4 +28,4 @@ namespace nexo::renderer { { NxRenderCommand::setViewport(0, 0, width, height); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Renderer.hpp b/engine/src/renderer/Renderer.hpp index 07e28b0c5..f1b6fdef2 100644 --- a/engine/src/renderer/Renderer.hpp +++ b/engine/src/renderer/Renderer.hpp @@ -18,14 +18,36 @@ namespace nexo::renderer { class NxRenderer { - public: - static void init(); - static void onWindowResize(unsigned int width, unsigned int height); + public: + /** + * @brief Initializes the renderer and its associated resources. + * + * This method sets up the rendering context and prepares any necessary resources + * for rendering operations. It should be called once during application initialization. + * + * Usage: + * - Call this method before performing any rendering operations. + */ + static void init(); - struct NxSceneData { - glm::mat4 projectionMatrix; - }; - static NxSceneData *_sceneData; + /** + * @brief Handles window resize events to adjust the viewport. + * + * This method should be called whenever the application window is resized. + * It updates the rendering viewport to match the new window dimensions, + * ensuring that rendered content scales appropriately. + * + * @param width The new width of the window in pixels. + * @param height The new height of the window in pixels. + * + * Usage: + * - Typically called from the windowing system's resize callback. + */ + static void onWindowResize(unsigned int width, unsigned int height); + struct NxSceneData { + glm::mat4 projectionMatrix; + }; + static NxSceneData *_sceneData; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index e21f334f0..a11a66c85 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -14,16 +14,16 @@ #include "ShaderLibrary.hpp" #define GLM_ENABLE_EXPERIMENTAL -#include #include +#include -#include "Renderer3D.hpp" -#include "RenderCommand.hpp" +#include #include "Logger.hpp" +#include "Path.hpp" +#include "RenderCommand.hpp" +#include "Renderer3D.hpp" #include "Shader.hpp" #include "renderer/RendererExceptions.hpp" -#include -#include "Path.hpp" namespace nexo::renderer { @@ -31,18 +31,14 @@ namespace nexo::renderer { { m_storage = std::make_shared(); - m_storage->vertexArray = createVertexArray(); + m_storage->vertexArray = createVertexArray(); m_storage->vertexBuffer = createVertexBuffer(m_storage->maxVertices * sizeof(NxVertex)); // Layout const NxBufferLayout cubeVertexBufferLayout = { - {NxShaderDataType::FLOAT3, "aPos"}, - {NxShaderDataType::FLOAT2, "aTexCoord"}, - {NxShaderDataType::FLOAT3, "aNormal"}, - {NxShaderDataType::FLOAT3, "aTangent"}, - {NxShaderDataType::FLOAT3, "aBiTangent"}, - {NxShaderDataType::INT, "aEntityID"} - }; + {NxShaderDataType::FLOAT3, "aPos"}, {NxShaderDataType::FLOAT2, "aTexCoord"}, + {NxShaderDataType::FLOAT3, "aNormal"}, {NxShaderDataType::FLOAT3, "aTangent"}, + {NxShaderDataType::FLOAT3, "aBiTangent"}, {NxShaderDataType::INT, "aEntityID"}}; m_storage->vertexBuffer->setLayout(cubeVertexBufferLayout); m_storage->vertexArray->addVertexBuffer(m_storage->vertexBuffer); @@ -50,26 +46,28 @@ namespace nexo::renderer { m_storage->vertexArray->setIndexBuffer(m_storage->indexBuffer); // Texture - m_storage->whiteTexture = NxTexture2D::create(1, 1); + m_storage->whiteTexture = NxTexture2D::create(1, 1); unsigned int whiteTextureData = 0xffffffff; m_storage->whiteTexture->setData(&whiteTextureData, sizeof(unsigned int)); // Shader std::array samplers{}; - for (int i = 0; i < static_cast(NxRenderer3DStorage::maxTextureSlots); ++i) - samplers[i] = i; + for (int i = 0; i < static_cast(NxRenderer3DStorage::maxTextureSlots); ++i) samplers[i] = i; - const auto phong = ShaderLibrary::getInstance().get("Phong"); + const auto phong = ShaderLibrary::getInstance().get("Phong"); const auto outlinePulseTransparentFlat = ShaderLibrary::getInstance().get("Outline pulse transparent flat"); - const auto albedoUnshadedTransparent = ShaderLibrary::getInstance().get("Albedo unshaded transparent"); + const auto albedoUnshadedTransparent = ShaderLibrary::getInstance().get("Albedo unshaded transparent"); phong->bind(); - phong->setUniformIntArray(NxShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); + phong->setUniformIntArray(NxShaderUniforms::TEXTURE_SAMPLER, samplers.data(), + NxRenderer3DStorage::maxTextureSlots); phong->unbind(); outlinePulseTransparentFlat->bind(); - outlinePulseTransparentFlat->setUniformIntArray(NxShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); + outlinePulseTransparentFlat->setUniformIntArray(NxShaderUniforms::TEXTURE_SAMPLER, samplers.data(), + NxRenderer3DStorage::maxTextureSlots); outlinePulseTransparentFlat->unbind(); albedoUnshadedTransparent->bind(); - albedoUnshadedTransparent->setUniformIntArray(NxShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); + albedoUnshadedTransparent->setUniformIntArray(NxShaderUniforms::TEXTURE_SAMPLER, samplers.data(), + NxRenderer3DStorage::maxTextureSlots); albedoUnshadedTransparent->unbind(); m_storage->textureSlots[0] = m_storage->whiteTexture; @@ -79,32 +77,29 @@ namespace nexo::renderer { void NxRenderer3D::shutdown() { - if (!m_storage) - THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); + if (!m_storage) THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); m_storage.reset(); } void NxRenderer3D::bindTextures() const { - for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) - { + for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) { m_storage->textureSlots[i]->bind(i); } } void NxRenderer3D::unbindTextures() const { - for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) - { + for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) { m_storage->textureSlots[i]->unbind(i); } m_storage->textureSlotIndex = 1; } - void NxRenderer3D::beginScene(const glm::mat4 &viewProjection, const glm::vec3 &cameraPos, const std::string &shader) + void NxRenderer3D::beginScene(const glm::mat4 &viewProjection, const glm::vec3 &cameraPos, + const std::string &shader) { - if (!m_storage) - THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); + if (!m_storage) THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); if (shader.empty()) m_storage->currentSceneShader = ShaderLibrary::getInstance().get("Phong"); else @@ -115,25 +110,22 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uViewProjection", viewProjection); m_storage->cameraPosition = cameraPos; m_storage->currentSceneShader->setUniformFloat3("uCamPos", cameraPos); - m_storage->indexCount = 0; - m_storage->vertexBufferPtr = m_storage->vertexBufferBase.data(); - m_storage->indexBufferPtr = m_storage->indexBufferBase.data(); + m_storage->indexCount = 0; + m_storage->vertexBufferPtr = m_storage->vertexBufferBase.data(); + m_storage->indexBufferPtr = m_storage->indexBufferBase.data(); m_storage->textureSlotIndex = 1; - m_renderingScene = true; + m_renderingScene = true; } void NxRenderer3D::endScene() const { - if (!m_storage) - THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); + if (!m_storage) THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); if (!m_renderingScene) THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, - "Renderer not rendering a scene, make sure to call beginScene first"); - const auto vertexDataSize = static_cast( - reinterpret_cast(m_storage->vertexBufferPtr) - - reinterpret_cast(m_storage->vertexBufferBase.data()) - ); - + "Renderer not rendering a scene, make sure to call beginScene first"); + const auto vertexDataSize = + static_cast(reinterpret_cast(m_storage->vertexBufferPtr) - + reinterpret_cast(m_storage->vertexBufferBase.data())); m_storage->vertexBuffer->setData(m_storage->vertexBufferBase.data(), vertexDataSize); @@ -145,8 +137,7 @@ namespace nexo::renderer { void NxRenderer3D::flush() const { m_storage->currentSceneShader->bind(); - for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) - { + for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) { m_storage->textureSlots[i]->bind(i); } NxRenderCommand::drawIndexed(m_storage->vertexArray, m_storage->indexCount); @@ -154,8 +145,7 @@ namespace nexo::renderer { m_storage->vertexArray->unbind(); m_storage->vertexBuffer->unbind(); m_storage->currentSceneShader->unbind(); - for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) - { + for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) { m_storage->textureSlots[i]->unbind(i); } } @@ -163,9 +153,9 @@ namespace nexo::renderer { void NxRenderer3D::flushAndReset() const { flush(); - m_storage->indexCount = 0; - m_storage->vertexBufferPtr = m_storage->vertexBufferBase.data(); - m_storage->indexBufferPtr = m_storage->indexBufferBase.data(); + m_storage->indexCount = 0; + m_storage->vertexBufferPtr = m_storage->vertexBufferBase.data(); + m_storage->indexBufferPtr = m_storage->indexBufferBase.data(); m_storage->textureSlotIndex = 1; } @@ -173,21 +163,17 @@ namespace nexo::renderer { { int textureIndex = 0; - if (!texture) - return textureIndex; + if (!texture) return textureIndex; - for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) - { - if (*m_storage->textureSlots[i].get() == *texture) - { + for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) { + if (*m_storage->textureSlots[i] == *texture) { textureIndex = static_cast(i); break; } } - if (textureIndex == 0) - { - textureIndex = static_cast(m_storage->textureSlotIndex); + if (textureIndex == 0) { + textureIndex = static_cast(m_storage->textureSlotIndex); m_storage->textureSlots[m_storage->textureSlotIndex] = texture; m_storage->textureSlotIndex++; } @@ -195,10 +181,9 @@ namespace nexo::renderer { return textureIndex; } - void NxRenderer3D::setMaterialUniforms(const NxIndexedMaterial& material) const + void NxRenderer3D::setMaterialUniforms(const NxIndexedMaterial &material) const { - if (!m_storage) - THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); + if (!m_storage) THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); m_storage->currentSceneShader->setUniformFloat4("uMaterial.albedoColor", material.albedoColor); m_storage->currentSceneShader->setUniformInt("uMaterial.albedoTexIndex", material.albedoTexIndex); @@ -216,17 +201,15 @@ namespace nexo::renderer { void NxRenderer3D::resetStats() const { - if (!m_storage) - THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); + if (!m_storage) THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); m_storage->stats.drawCalls = 0; m_storage->stats.cubeCount = 0; } NxRenderer3DStats NxRenderer3D::getStats() const { - if (!m_storage) - THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); + if (!m_storage) THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); return m_storage->stats; } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index 75ec3e5ff..8d8865014 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -14,16 +14,14 @@ #pragma once #include "Shader.hpp" -#include "VertexArray.hpp" #include "Texture.hpp" +#include "VertexArray.hpp" #include #include -namespace nexo::renderer -{ - struct NxVertex - { +namespace nexo::renderer { + struct NxVertex { glm::vec3 position; glm::vec2 texCoord; glm::vec3 normal; @@ -33,49 +31,71 @@ namespace nexo::renderer int entityID; }; - struct NxIndexedMaterial - { - glm::vec4 albedoColor = glm::vec4(1.0f); - int albedoTexIndex = 0; // Default: 0 (white texture) + struct NxIndexedMaterial { + glm::vec4 albedoColor = glm::vec4(1.0f); + int albedoTexIndex = 0; // Default: 0 (white texture) glm::vec4 specularColor = glm::vec4(1.0f); - int specularTexIndex = 0; // Default: 0 (white texture) + int specularTexIndex = 0; // Default: 0 (white texture) glm::vec3 emissiveColor = glm::vec3(0.0f); - int emissiveTexIndex = 0; // Default: 0 (white texture) - float roughness = 0.5f; - int roughnessTexIndex = 0; // Default: 0 (white texture) - float metallic = 0.0f; - int metallicTexIndex = 0; // Default: 0 (white texture) - float opacity = 1.0f; - int opacityTexIndex = 0; // Default: 0 (white texture) + int emissiveTexIndex = 0; // Default: 0 (white texture) + float roughness = 0.5f; + int roughnessTexIndex = 0; // Default: 0 (white texture) + float metallic = 0.0f; + int metallicTexIndex = 0; // Default: 0 (white texture) + float opacity = 1.0f; + int opacityTexIndex = 0; // Default: 0 (white texture) }; - struct NxMaterial - { - glm::vec4 albedoColor = glm::vec4(1.0f); + struct NxMaterial { + glm::vec4 albedoColor = glm::vec4(1.0f); glm::vec4 specularColor = glm::vec4(1.0f); glm::vec3 emissiveColor = glm::vec3(0.0f); float roughness = 0.0f; // 0 = smooth, 1 = rough - float metallic = 0.0f; // 0 = non-metal, 1 = fully metallic - float opacity = 1.0f; // 1 = opaque, 0 = fully transparent + float metallic = 0.0f; // 0 = non-metal, 1 = fully metallic + float opacity = 1.0f; // 1 = opaque, 0 = fully transparent std::shared_ptr albedoTexture = nullptr; - std::shared_ptr normalMap = nullptr; - std::shared_ptr metallicMap = nullptr; - std::shared_ptr roughnessMap = nullptr; - std::shared_ptr emissiveMap = nullptr; + std::shared_ptr normalMap = nullptr; + std::shared_ptr metallicMap = nullptr; + std::shared_ptr roughnessMap = nullptr; + std::shared_ptr emissiveMap = nullptr; std::string shader; }; - //TODO: Add stats for the meshes - struct NxRenderer3DStats - { + // TODO: Add stats for the meshes + struct NxRenderer3DStats { unsigned int drawCalls = 0; unsigned int cubeCount = 0; - [[nodiscard]] unsigned int getTotalVertexCount() const { return cubeCount * 8; } - [[nodiscard]] unsigned int getTotalIndexCount() const { return cubeCount * 36; } + /** + * @brief Calculates the total number of vertices used in the current rendering session. + * + * This method computes the total vertex count based on the number of cubes rendered. + * Each cube consists of 8 vertices, so the total vertex count is derived by multiplying + * the cube count by 8. + * + * @return The total number of vertices used in the current rendering session. + */ + [[nodiscard]] unsigned int getTotalVertexCount() const + { + return cubeCount * 8; + } + + /** + * @brief Calculates the total number of indices used in the current rendering session. + * + * This method computes the total index count based on the number of cubes rendered. + * Each cube consists of 36 indices (6 faces, 2 triangles per face, 3 indices per triangle), + * so the total index count is derived by multiplying the cube count by 36. + * + * @return The total number of indices used in the current rendering session. + */ + [[nodiscard]] unsigned int getTotalIndexCount() const + { + return cubeCount * 36; + } }; /** @@ -92,13 +112,12 @@ namespace nexo::renderer * - `vertexBufferPtr`, `indexBufferPtr`: Current pointers for batching vertices and indices. * - `stats`: Rendering statistics. */ - struct NxRenderer3DStorage - { - const unsigned int maxCubes = 10000; - const unsigned int maxVertices = maxCubes * 8; - const unsigned int maxIndices = maxCubes * 36; + struct NxRenderer3DStorage { + const unsigned int maxCubes = 10000; + const unsigned int maxVertices = maxCubes * 8; + const unsigned int maxIndices = maxCubes * 36; static constexpr unsigned int maxTextureSlots = 32; - static constexpr unsigned int maxTransforms = 1024; + static constexpr unsigned int maxTransforms = 1024; glm::vec3 cameraPosition; @@ -111,7 +130,7 @@ namespace nexo::renderer unsigned int indexCount = 0; std::array vertexBufferBase; std::array indexBufferBase; - NxVertex* vertexBufferPtr = nullptr; + NxVertex* vertexBufferPtr = nullptr; unsigned int* indexBufferPtr = nullptr; std::array, maxTextureSlots> textureSlots; @@ -146,11 +165,10 @@ namespace nexo::renderer * 4. Call `endScene()` to finalize the rendering and issue draw calls. * 5. Call `shutdown()` to release resources when the renderer is no longer needed. */ - class NxRenderer3D - { - public: - - static NxRenderer3D& get() { + class NxRenderer3D { + public: + static NxRenderer3D& get() + { static NxRenderer3D instance; return instance; } @@ -184,7 +202,27 @@ namespace nexo::renderer */ void shutdown(); + /** + * @brief Binds all currently used textures to their respective texture slots. + * + * Iterates through the texture slots and binds each texture to the corresponding + * OpenGL texture unit. This prepares the textures for use in the shader during rendering. + * + * Throws: + * - NxRendererNotInitialized if the renderer is not initialized. + */ void bindTextures() const; + + /** + * @brief Unbinds all currently bound textures and resets the texture slot index. + * + * Iterates through the texture slots and unbinds each texture from its OpenGL + * texture unit. Resets the texture slot index to 1, keeping the default white + * texture at slot 0. + * + * Throws: + * - NxRendererNotInitialized if the renderer is not initialized. + */ void unbindTextures() const; /** @@ -214,13 +252,55 @@ namespace nexo::renderer */ void endScene() const; + /** + * @brief Returns a shared Vertex Array Object (VAO) for rendering a cube. + * @return Shared pointer to a pre-configured cube VAO. + */ static std::shared_ptr getCubeVAO(); + + /** + * @brief Returns a shared Vertex Array Object (VAO) for rendering a box (used for debugging AABB) + * @return Shared pointer to a pre-configured box VAO + */ + static std::shared_ptr getBoxVAO(); + + /** + * @brief Returns a shared VAO for rendering a billboard (quad facing camera). + * @return Shared pointer to a pre-configured billboard VAO. + */ static std::shared_ptr getBillboardVAO(); + + /** + * @brief Returns a shared VAO for rendering video content on a quad. + * @return Shared pointer to a pre-configured video quad VAO. + */ + static std::shared_ptr getVideoVAO(); + + /** + * @brief Returns a shared VAO for rendering a tetrahedron. + * @return Shared pointer to a pre-configured tetrahedron VAO. + */ static std::shared_ptr getTetrahedronVAO(); + + /** + * @brief Returns a shared VAO for rendering a pyramid. + * @return Shared pointer to a pre-configured pyramid VAO. + */ static std::shared_ptr getPyramidVAO(); + + /** + * @brief Returns a shared VAO for rendering a cylinder with specified segments. + * @param nbSegment Number of segments around the cylinder's circumference. + * @return Shared pointer to a pre-configured cylinder VAO. + */ static std::shared_ptr getCylinderVAO(unsigned int nbSegment); - static std::shared_ptr getSphereVAO(unsigned int nbSubdivision); + /** + * @brief Returns a shared VAO for rendering a UV sphere with specified subdivisions. + * @param nbSubdivision Number of subdivisions for the sphere's mesh resolution. + * @return Shared pointer to a pre-configured sphere VAO. + */ + static std::shared_ptr getSphereVAO(unsigned int nbSubdivision); /** * @brief Resets rendering statistics. * @@ -230,6 +310,7 @@ namespace nexo::renderer * - NxRendererNotInitialized if the renderer is not initialized. */ void resetStats() const; + /** * @brief Retrieves the current rendering statistics. * @@ -241,9 +322,38 @@ namespace nexo::renderer */ [[nodiscard]] NxRenderer3DStats getStats() const; - [[nodiscard]] std::shared_ptr& getShader() const { return m_storage->currentSceneShader; }; + /** + * @brief Provides access to the current shader used for rendering. + * + * This method returns a shared pointer to the `NxShader` instance currently + * bound for rendering operations. It allows users to modify shader uniforms + * or inspect shader properties as needed. + * + * @return A shared pointer to the current `NxShader`. + * + * Throws: + * - NxRendererNotInitialized if the renderer is not initialized. + */ + [[nodiscard]] std::shared_ptr& getShader() const + { + return m_storage->currentSceneShader; + } - [[nodiscard]] std::shared_ptr getInternalStorage() const { return m_storage; }; + /** + * @brief Provides access to the internal storage of the renderer. + * + * This method returns a shared pointer to the `NxRenderer3DStorage` instance, + * which contains all internal data and resources used by the renderer. + * + * @return A shared pointer to the `NxRenderer3DStorage`. + * + * Throws: + * - NxRendererNotInitialized if the renderer is not initialized. + */ + [[nodiscard]] std::shared_ptr getInternalStorage() const + { + return m_storage; + } /** * @brief Returns the texture index for a given texture. @@ -254,7 +364,8 @@ namespace nexo::renderer * @return float The texture index. */ [[nodiscard]] int getTextureIndex(const std::shared_ptr& texture) const; - private: + + private: std::shared_ptr m_storage; bool m_renderingScene = false; @@ -270,8 +381,6 @@ namespace nexo::renderer */ void flushAndReset() const; - - /** * @brief Sets material-related uniforms in the texture shader. * @@ -283,4 +392,4 @@ namespace nexo::renderer */ void setMaterialUniforms(const NxIndexedMaterial& material) const; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/RendererAPI.cpp b/engine/src/renderer/RendererAPI.cpp index c7c4f2690..695cbc7d5 100644 --- a/engine/src/renderer/RendererAPI.cpp +++ b/engine/src/renderer/RendererAPI.cpp @@ -13,6 +13,4 @@ /////////////////////////////////////////////////////////////////////////////// #include "RendererAPI.hpp" -namespace nexo::renderer { - -} +namespace nexo::renderer {} diff --git a/engine/src/renderer/RendererAPI.hpp b/engine/src/renderer/RendererAPI.hpp index 13aad5e5a..2ae79dae4 100644 --- a/engine/src/renderer/RendererAPI.hpp +++ b/engine/src/renderer/RendererAPI.hpp @@ -17,138 +17,231 @@ #include #include "VertexArray.hpp" +#include "DrawCommand.hpp" namespace nexo::renderer { - enum class CulledFace { - BACK, - FRONT, - FRONT_AND_BACK - }; + enum class CulledFace { BACK, FRONT, FRONT_AND_BACK }; - enum class WindingOrder { - CW, - CCW - }; + enum class WindingOrder { CW, CCW }; /** - * @class NxRendererApi - * @brief Abstract interface for low-level rendering API implementations. - * - * The `NxRendererApi` class defines the essential methods required for interacting - * with the graphics pipeline, such as initializing the API, configuring the - * viewport, clearing buffers, and issuing draw commands. Specific graphics APIs, - * like OpenGL, DirectX, or Vulkan, should implement this interface to ensure - * compatibility with the renderer framework. - * - * Responsibilities: - * - Provide a uniform interface for graphics API interaction. - * - Define methods for initializing and managing rendering settings. - * - Support commands for clearing buffers, setting viewport size, and drawing. - * - * Subclasses: - * - `NxOpenGlRendererApi`: Implements this interface using OpenGL commands. - */ + * @class NxRendererApi + * @brief Abstract interface for low-level rendering API implementations. + * + * The `NxRendererApi` class defines the essential methods required for interacting + * with the graphics pipeline, such as initializing the API, configuring the + * viewport, clearing buffers, and issuing draw commands. Specific graphics APIs, + * like OpenGL, DirectX, or Vulkan, should implement this interface to ensure + * compatibility with the renderer framework. + * + * Responsibilities: + * - Provide a uniform interface for graphics API interaction. + * - Define methods for initializing and managing rendering settings. + * - Support commands for clearing buffers, setting viewport size, and drawing. + * + * Subclasses: + * - `NxOpenGlRendererApi`: Implements this interface using OpenGL commands. + */ class NxRendererApi { - public: - virtual ~NxRendererApi() = default; - - /** - * @brief Initializes the graphics API. - * - * This method sets up the necessary states and configurations required for - * rendering, such as enabling blending, depth testing, and face culling. - * - * Must be implemented by subclasses. - * - * Throws: - * - Exceptions specific to the API if initialization fails. - */ - virtual void init() = 0; - - /** - * @brief Sets the dimensions and position of the viewport. - * - * Configures the viewport area where rendering will occur. The width and height - * define the size of the viewport, and x and y specify its position. - * - * @param x The x-coordinate of the viewport's bottom-left corner. - * @param y The y-coordinate of the viewport's bottom-left corner. - * @param width The width of the viewport in pixels. - * @param height The height of the viewport in pixels. - * - * Must be implemented by subclasses. - */ - virtual void setViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height) = 0; - - /** - * @brief Retrieves the maximum dimensions supported by the graphics API for the viewport. - * - * @param[out] width Pointer to store the maximum viewport width. - * @param[out] height Pointer to store the maximum viewport height. - * - * Must be implemented by subclasses. - */ - virtual void getMaxViewportSize(unsigned int *width, unsigned int *height) = 0; - - /** - * @brief Clears the color and depth buffers. - * - * Resets the frame buffer by clearing the current color and depth data. - * - * Must be implemented by subclasses. - */ - virtual void clear() = 0; - - /** - * @brief Sets the color used to clear the frame buffer. - * - * Configures the RGBA color that will fill the frame buffer during clearing. - * - * @param color A `glm::vec4` containing the red, green, blue, and alpha components of the clear color. - * - * Must be implemented by subclasses. - */ - virtual void setClearColor(const glm::vec4 &color) = 0; - - /** - * @brief Sets the depth value used to clear the depth buffer. - * - * Configures the depth value that will fill the depth buffer during clearing. - * - * @param depth A float value representing the clear depth. - * - * Must be implemented by subclasses. - */ - virtual void setClearDepth(float depth) = 0; - - virtual void setDepthTest(bool enable) = 0; - virtual void setDepthFunc(unsigned int func) = 0; - virtual void setDepthMask(bool enable) = 0; - - /** - * @brief Issues a draw call for indexed geometry. - * - * Renders geometry using indices stored in the index buffer attached to the - * specified `NxVertexArray`. - * - * @param vertexArray A shared pointer to the `NxVertexArray` containing vertex and index data. - * @param count The number of indices to draw. If zero, all indices in the buffer are used. - * - * Must be implemented by subclasses. - */ - virtual void drawIndexed(const std::shared_ptr &vertexArray, size_t count = 0) = 0; - - virtual void drawUnIndexed(size_t verticesCount) = 0; - - virtual void setStencilTest(bool enable) = 0; - virtual void setStencilMask(unsigned int mask) = 0; - virtual void setStencilFunc(unsigned int func, int ref, unsigned int mask) = 0; - virtual void setStencilOp(unsigned int sfail, unsigned int dpfail, unsigned int dppass) = 0; - - virtual void setCulling(bool enable) = 0; - virtual void setCulledFace(CulledFace face) = 0; - virtual void setWindingOrder(WindingOrder order) = 0; - + public: + virtual ~NxRendererApi() = default; + + /** + * @brief Initializes the graphics API. + * + * This method sets up the necessary states and configurations required for + * rendering, such as enabling blending, depth testing, and face culling. + * + * Must be implemented by subclasses. + * + * Throws: + * - Exceptions specific to the API if initialization fails. + */ + virtual void init() = 0; + + /** + * @brief Sets the dimensions and position of the viewport. + * + * Configures the viewport area where rendering will occur. The width and height + * define the size of the viewport, and x and y specify its position. + * + * @param x The x-coordinate of the viewport's bottom-left corner. + * @param y The y-coordinate of the viewport's bottom-left corner. + * @param width The width of the viewport in pixels. + * @param height The height of the viewport in pixels. + * + * Must be implemented by subclasses. + */ + virtual void setViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height) = 0; + + /** + * @brief Retrieves the maximum dimensions supported by the graphics API for the viewport. + * + * @param[out] width Pointer to store the maximum viewport width. + * @param[out] height Pointer to store the maximum viewport height. + * + * Must be implemented by subclasses. + */ + virtual void getMaxViewportSize(unsigned int *width, unsigned int *height) = 0; + + /** + * @brief Clears the color and depth buffers. + * + * Resets the frame buffer by clearing the current color and depth data. + * + * Must be implemented by subclasses. + */ + virtual void clear() = 0; + + /** + * @brief Sets the color used to clear the frame buffer. + * + * Configures the RGBA color that will fill the frame buffer during clearing. + * + * @param color A `glm::vec4` containing the red, green, blue, and alpha components of the clear color. + * + * Must be implemented by subclasses. + */ + virtual void setClearColor(const glm::vec4 &color) = 0; + + /** + * @brief Sets the depth value used to clear the depth buffer. + * + * Configures the depth value that will fill the depth buffer during clearing. + * + * @param depth A float value representing the clear depth. + * + * Must be implemented by subclasses. + */ + virtual void setClearDepth(float depth) = 0; + + /** + * @brief Controls the depth testing operation. + * + * Enables or disables depth testing during rendering operations. + * + * @param enable True to enable depth testing, false to disable it. + * + * Must be implemented by subclasses. + */ + virtual void setDepthTest(bool enable) = 0; + + /** + * @brief Sets the depth comparison function. + * + * Configures how the depth values of incoming fragments are compared with + * the values stored in the depth buffer. + * + * @param func The depth comparison function to use (e.g., LESS, GREATER, EQUAL). + * + * Must be implemented by subclasses. + */ + virtual void setDepthFunc(unsigned int func) = 0; + + /** + * @brief Controls writing to the depth buffer. + * + * Enables or disables writing depth values to the depth buffer during rendering. + * + * @param enable True to enable depth writes, false to disable them. + * + * Must be implemented by subclasses. + */ + virtual void setDepthMask(bool enable) = 0; + + virtual void setLineWidth(float lineWidth) = 0; + + /** + * @brief Issues a draw call for indexed geometry. + * + * Renders geometry using indices stored in the index buffer attached to the + * specified `NxVertexArray`. + * + * @param vertexArray A shared pointer to the `NxVertexArray` containing vertex and index data. + * @param count The number of indices to draw. If zero, all indices in the buffer are used. + * + * Must be implemented by subclasses. + */ + virtual void drawIndexed(const std::shared_ptr &vertexArray, size_t count = 0, CommandType primitiveType = CommandType::MESH) = 0; + + /** + * @brief Draws geometry without using indices. + * + * Renders vertices directly from the vertex buffer in sequential order. + * + * @param verticesCount The number of vertices to draw. + * + * Must be implemented by subclasses. + */ + virtual void drawUnIndexed(size_t verticesCount) = 0; + + /** + * @brief Controls the stencil testing operation. + * + * @param enable True to enable stencil testing, false to disable it. + * + * Must be implemented by subclasses. + */ + virtual void setStencilTest(bool enable) = 0; + + /** + * @brief Sets the mask for writing to the stencil buffer. + * + * @param mask The mask that controls which bits are written to the stencil buffer. + * + * Must be implemented by subclasses. + */ + virtual void setStencilMask(unsigned int mask) = 0; + + /** + * @brief Configures the stencil test function. + * + * @param func The stencil comparison function to use. + * @param ref The reference value for stencil testing. + * @param mask The mask that is ANDed with both the reference value and stored stencil value. + * + * Must be implemented by subclasses. + */ + virtual void setStencilFunc(unsigned int func, int ref, unsigned int mask) = 0; + + /** + * @brief Sets the stencil buffer operations. + * + * @param sfail Operation to perform when stencil test fails. + * @param dpfail Operation when stencil test passes but depth test fails. + * @param dppass Operation when both stencil and depth tests pass. + * + * Must be implemented by subclasses. + */ + virtual void setStencilOp(unsigned int sfail, unsigned int dpfail, unsigned int dppass) = 0; + + /** + * @brief Enables or disables face culling. + * + * @param enable True to enable face culling, false to disable it. + * + * Must be implemented by subclasses. + */ + virtual void setCulling(bool enable) = 0; + + /** + * @brief Sets which faces should be culled. + * + * @param face The face(s) to be culled (BACK, FRONT, or FRONT_AND_BACK). + * + * Must be implemented by subclasses. + */ + virtual void setCulledFace(CulledFace face) = 0; + + /** + * @brief Sets the winding order for front face determination. + * + * @param order The winding order (CW for clockwise, CCW for counter-clockwise). + * + * Must be implemented by subclasses. + */ + virtual void setWindingOrder(WindingOrder order) = 0; }; -} + +} // namespace nexo::renderer diff --git a/engine/src/renderer/RendererExceptions.hpp b/engine/src/renderer/RendererExceptions.hpp index 57f3025c5..e7118985f 100644 --- a/engine/src/renderer/RendererExceptions.hpp +++ b/engine/src/renderer/RendererExceptions.hpp @@ -15,205 +15,222 @@ #include "Exception.hpp" -#include #include +#include namespace nexo::renderer { - class NxOutOfRangeException final : public Exception { - public: - explicit NxOutOfRangeException(size_t index, size_t size, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Index {} is out of range [0, {})", index, size), loc) {} + class NxOutOfRangeException final : public Exception { + public: + explicit NxOutOfRangeException(size_t index, size_t size, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Index {} is out of range [0, {})", index, size), loc) + {} }; class NxFileNotFoundException final : public Exception { - public: - explicit NxFileNotFoundException(const std::string &filePath, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("File not found: {}", filePath), loc) {} + public: + explicit NxFileNotFoundException(const std::string &filePath, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("File not found: {}", filePath), loc) + {} }; class NxUnknownGraphicsApi final : public Exception { - public: - explicit NxUnknownGraphicsApi(const std::string &backendApiName, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Unknown graphics API: {}", backendApiName), loc) {} + public: + explicit NxUnknownGraphicsApi(const std::string &backendApiName, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Unknown graphics API: {}", backendApiName), loc) + {} }; class NxGraphicsApiInitFailure final : public Exception { - public: - explicit NxGraphicsApiInitFailure(const std::string &backendApiName, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Failed to initialize graphics API: {}", backendApiName), loc) {} + public: + explicit NxGraphicsApiInitFailure(const std::string &backendApiName, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Failed to initialize graphics API: {}", backendApiName), loc) + {} }; class NxGraphicsApiNotInitialized final : public Exception { - public: - explicit NxGraphicsApiNotInitialized(const std::string &backendApiName, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("[{}] API is not initialized, call the init function first", backendApiName), - loc) - {} + public: + explicit NxGraphicsApiNotInitialized(const std::string &backendApiName, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] API is not initialized, call the init function first", backendApiName), loc) + {} }; class NxGraphicsApiViewportResizingFailure final : public Exception { - public: - explicit NxGraphicsApiViewportResizingFailure(const std::string &backendApi, const bool tooBig, - const unsigned int width, const unsigned int height, - const std::source_location loc = - std::source_location::current()) - : Exception(std::format("[{}] Viewport resizing failed: {}x{} is too {}", - backendApi, width, height, (tooBig ? "big" : "small")), loc) {} + public: + explicit NxGraphicsApiViewportResizingFailure(const std::string &backendApi, const bool tooBig, + const unsigned int width, const unsigned int height, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Viewport resizing failed: {}x{} is too {}", backendApi, width, height, + (tooBig ? "big" : "small")), + loc) + {} }; class NxGraphicsApiWindowInitFailure final : public Exception { - public: - explicit NxGraphicsApiWindowInitFailure(const std::string &backendApiName, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Failed to initialize graphics API: {}", backendApiName), loc) {} + public: + explicit NxGraphicsApiWindowInitFailure(const std::string &backendApiName, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Failed to initialize graphics API: {}", backendApiName), loc) + {} }; class NxInvalidValue final : public Exception { - public: - explicit NxInvalidValue(const std::string &backendApiName, const std::string &msg, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("[{}] Invalid value: {}", backendApiName, msg), loc) {} + public: + explicit NxInvalidValue(const std::string &backendApiName, const std::string &msg, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Invalid value: {}", backendApiName, msg), loc) + {} }; class NxShaderCreationFailed final : public Exception { - public: - explicit NxShaderCreationFailed(const std::string &backendApi, const std::string &message, - const std::string &path = "", - const std::source_location loc = std::source_location::current()) - : Exception(std::format("[{}] Failed to create the shader ({}): {}", backendApi, path, message), loc) - {} + public: + explicit NxShaderCreationFailed(const std::string &backendApi, const std::string &message, + const std::string &path = "", + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Failed to create the shader ({}): {}", backendApi, path, message), loc) + {} }; class NxShaderInvalidUniform final : public Exception { - public: - explicit NxShaderInvalidUniform(const std::string &backendApi, const std::string &shaderName, - const std::string &uniformName, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("[{}] Failed to retrieve uniform \"{}\" in shader: {}", backendApi, uniformName, - shaderName), loc) {} + public: + explicit NxShaderInvalidUniform(const std::string &backendApi, const std::string &shaderName, + const std::string &uniformName, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Failed to retrieve uniform \"{}\" in shader: {}", backendApi, uniformName, + shaderName), + loc) + {} }; class NxFramebufferCreationFailed final : public Exception { - public: - explicit NxFramebufferCreationFailed(const std::string &backendApi, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("[{}] Failed to create the framebuffer", backendApi), loc) - {} + public: + explicit NxFramebufferCreationFailed(const std::string &backendApi, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Failed to create the framebuffer", backendApi), loc) + {} }; class NxFramebufferResizingFailed final : public Exception { - public: - explicit NxFramebufferResizingFailed(const std::string &backendApi, const bool tooBig, - const unsigned int width, const unsigned int height, - const std::source_location loc = - std::source_location::current()) - : Exception(std::format("[{}] Framebuffer resizing failed: {}x{} is too {}", - backendApi, width, height, (tooBig ? "big" : "small")), loc) {} + public: + explicit NxFramebufferResizingFailed(const std::string &backendApi, const bool tooBig, const unsigned int width, + const unsigned int height, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Framebuffer resizing failed: {}x{} is too {}", backendApi, width, height, + (tooBig ? "big" : "small")), + loc) + {} }; class NxFramebufferUnsupportedColorFormat final : public Exception { - public: - explicit NxFramebufferUnsupportedColorFormat(const std::string &backendApiName, - const std::source_location loc = - std::source_location::current()) - : Exception(std::format("[{}] Unsupported framebuffer color attachment format", backendApiName), loc) {} + public: + explicit NxFramebufferUnsupportedColorFormat(const std::string &backendApiName, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Unsupported framebuffer color attachment format", backendApiName), loc) + {} }; class NxFramebufferUnsupportedDepthFormat final : public Exception { - public: - explicit NxFramebufferUnsupportedDepthFormat(const std::string &backendApiName, - const std::source_location loc = - std::source_location::current()) - : Exception(std::format("[{}] Unsupported framebuffer depth attachment format", backendApiName), loc) {} + public: + explicit NxFramebufferUnsupportedDepthFormat(const std::string &backendApiName, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Unsupported framebuffer depth attachment format", backendApiName), loc) + {} }; class NxFramebufferReadFailure final : public Exception { - public: - explicit NxFramebufferReadFailure(const std::string &backendApiName, int index, int x, int y, const std::source_location loc = std::source_location::current()) : Exception(std::format("[{}] Unable to read framebuffer with index {} at coordinate ({}, {})", backendApiName, index, x, y), loc) {} + public: + explicit NxFramebufferReadFailure(const std::string &backendApiName, int index, int x, int y, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Unable to read framebuffer with index {} at coordinate ({}, {})", + backendApiName, index, x, y), + loc) + {} }; class NxFramebufferInvalidIndex final : public Exception { - public: - explicit NxFramebufferInvalidIndex(const std::string &backendApiName, int index, const std::source_location loc = std::source_location::current()) : Exception(std::format("[{}] Invalid attachment index : {}", backendApiName, index), loc) {}; + public: + explicit NxFramebufferInvalidIndex(const std::string &backendApiName, int index, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Invalid attachment index : {}", backendApiName, index), loc){}; }; class NxBufferLayoutEmpty final : public Exception { - public: - explicit NxBufferLayoutEmpty(const std::string &backendApi, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("[{}] Vertex buffer layout cannot be empty", backendApi), loc) {} + public: + explicit NxBufferLayoutEmpty(const std::string &backendApi, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Vertex buffer layout cannot be empty", backendApi), loc) + {} }; - enum class NxRendererType { - RENDERER_2D, - RENDERER_3D - }; + enum class NxRendererType { RENDERER_2D, RENDERER_3D }; class NxRendererNotInitialized final : public Exception { - public: - explicit NxRendererNotInitialized(const NxRendererType type, - const std::source_location loc = - std::source_location::current()) - : Exception(std::format("{} Renderer not initialized, call the init function first", - (type == NxRendererType::RENDERER_2D ? "[RENDERER 2D]" : "[RENDERER 3D]")), loc) - {} + public: + explicit NxRendererNotInitialized(const NxRendererType type, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("{} Renderer not initialized, call the init function first", + (type == NxRendererType::RENDERER_2D ? "[RENDERER 2D]" : "[RENDERER 3D]")), + loc) + {} }; class NxRendererSceneLifeCycleFailure final : public Exception { - public: - explicit NxRendererSceneLifeCycleFailure(const NxRendererType type, const std::string &msg, - const std::source_location loc = - std::source_location::current()) - : Exception(std::format("{} {}", - (type == NxRendererType::RENDERER_2D ? "[RENDERER 2D]" : "[RENDERER 3D]"), msg), - loc) {} + public: + explicit NxRendererSceneLifeCycleFailure(const NxRendererType type, const std::string &msg, + const std::source_location loc = std::source_location::current()) + : Exception( + std::format("{} {}", (type == NxRendererType::RENDERER_2D ? "[RENDERER 2D]" : "[RENDERER 3D]"), msg), + loc) + {} }; class NxTextureInvalidSize final : public Exception { - public: - explicit NxTextureInvalidSize(const std::string &backendApi, - const unsigned int width, const unsigned int height, - const unsigned int maxTextureSize, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("[{}] Invalid size for texture: {}x{} is too big, max texture size is : {}", - backendApi, width, height, maxTextureSize), loc) {} + public: + explicit NxTextureInvalidSize(const std::string &backendApi, const unsigned int width, + const unsigned int height, const unsigned int maxTextureSize, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Invalid size for texture: {}x{} is too big, max texture size is : {}", + backendApi, width, height, maxTextureSize), + loc) + {} }; class NxTextureUnsupportedFormat final : public Exception { - public: - explicit NxTextureUnsupportedFormat(const std::string &backendApi, const int channels, - const std::string &path, - const std::source_location loc = - std::source_location::current()) - : Exception(std::format("[{}] Unsupported image format with {} channels in {}", - backendApi, channels, path), loc) {} + public: + explicit NxTextureUnsupportedFormat(const std::string &backendApi, const int channels, const std::string &path, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Unsupported image format with {} channels in {}", backendApi, channels, path), + loc) + {} }; class NxTextureSizeMismatch final : public Exception { - public: - explicit NxTextureSizeMismatch(const std::string &backendApi, const size_t dataSize, const size_t expectedSize, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("[{}] Data size does not match the texture size: {} != {}", - backendApi, dataSize, expectedSize), loc) {} + public: + explicit NxTextureSizeMismatch(const std::string &backendApi, const size_t dataSize, const size_t expectedSize, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("[{}] Data size does not match the texture size: {} != {}", backendApi, dataSize, + expectedSize), + loc) + {} }; class NxStbiLoadException final : public Exception { - public: - explicit NxStbiLoadException(const std::string &msg, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("STBI load failed: {}", msg), loc) {} + public: + explicit NxStbiLoadException(const std::string &msg, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("STBI load failed: {}", msg), loc) + {} }; class NxPipelineRenderTargetNotSetException final : public Exception { - public: - explicit NxPipelineRenderTargetNotSetException(const std::source_location loc = - std::source_location::current()) - : Exception(std::format("Pipeline render target not set"), loc) {} + public: + explicit NxPipelineRenderTargetNotSetException(const std::source_location loc = std::source_location::current()) + : Exception(std::format("Pipeline render target not set"), loc) + {} }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Shader.cpp b/engine/src/renderer/Shader.cpp index d0e17744e..d0420a56c 100644 --- a/engine/src/renderer/Shader.cpp +++ b/engine/src/renderer/Shader.cpp @@ -12,10 +12,10 @@ // /////////////////////////////////////////////////////////////////////////////// #include "Shader.hpp" +#include #include "Attributes.hpp" -#include "renderer/RendererExceptions.hpp" #include "Logger.hpp" -#include +#include "renderer/RendererExceptions.hpp" #ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlShader.hpp" #endif @@ -24,48 +24,47 @@ namespace nexo::renderer { - std::shared_ptr NxShader::create(const std::string &path) + std::shared_ptr NxShader::create(const std::string& path) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(path); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(path); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } - std::shared_ptr NxShader::create(const std::string& name, const std::string &vertexSource, const std::string &fragmentSource) + std::shared_ptr NxShader::create(const std::string& name, const std::string& vertexSource, + const std::string& fragmentSource) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(name, vertexSource, fragmentSource); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(name, vertexSource, fragmentSource); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } - std::string NxShader::readFile(const std::string &filepath) + std::string NxShader::readFile(const std::string& filepath) { std::string result; - if (std::ifstream in(filepath, std::ios::in | std::ios::binary); in) - { + if (std::ifstream in(filepath, std::ios::in | std::ios::binary); in) { in.seekg(0, std::ios::end); result.resize(in.tellg()); in.seekg(0, std::ios::beg); - in.read(&result[0], result.size()); + in.read(&result[0], static_cast(result.size())); in.close(); return result; } THROW_EXCEPTION(NxFileNotFoundException, filepath); } - void NxShader::addStorageBuffer(const std::shared_ptr &buffer) + void NxShader::addStorageBuffer(const std::shared_ptr& buffer) { m_storageBuffers.push_back(buffer); } - void NxShader::setStorageBufferData(const size_t index, void* data, const size_t size) + void NxShader::setStorageBufferData(const size_t index, void* data, const size_t size) const { - if (index >= m_storageBuffers.size()) - THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); + if (index >= m_storageBuffers.size()) THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); m_storageBuffers[index]->setData(data, size); } @@ -74,8 +73,7 @@ namespace nexo::renderer { // Use uniform cache to avoid redundant state changes if (!m_uniformCache.isDirty(name)) { const auto optionalValue = m_uniformCache.getValue(name); - if (optionalValue.has_value() && - std::holds_alternative(*optionalValue) && + if (optionalValue.has_value() && std::holds_alternative(*optionalValue) && std::get(*optionalValue) == value) { return true; // Value hasn't changed, skip the update } @@ -89,8 +87,7 @@ namespace nexo::renderer { { if (!m_uniformCache.isDirty(name)) { const auto optionalValue = m_uniformCache.getValue(name); - if (optionalValue.has_value() && - std::holds_alternative(*optionalValue) && + if (optionalValue.has_value() && std::holds_alternative(*optionalValue) && std::get(*optionalValue) == values) { return true; } @@ -104,8 +101,7 @@ namespace nexo::renderer { { if (!m_uniformCache.isDirty(name)) { const auto optionalValue = m_uniformCache.getValue(name); - if (optionalValue.has_value() && - std::holds_alternative(*optionalValue) && + if (optionalValue.has_value() && std::holds_alternative(*optionalValue) && std::get(*optionalValue) == values) { return true; } @@ -119,8 +115,7 @@ namespace nexo::renderer { { if (!m_uniformCache.isDirty(name)) { const auto optionalValue = m_uniformCache.getValue(name); - if (optionalValue.has_value() && - std::holds_alternative(*optionalValue) && + if (optionalValue.has_value() && std::holds_alternative(*optionalValue) && std::get(*optionalValue) == values) { return true; } @@ -134,8 +129,7 @@ namespace nexo::renderer { { if (!m_uniformCache.isDirty(name)) { const auto optionalValue = m_uniformCache.getValue(name); - if (optionalValue.has_value() && - std::holds_alternative(*optionalValue) && + if (optionalValue.has_value() && std::holds_alternative(*optionalValue) && std::get(*optionalValue) == matrix) { return true; } @@ -149,8 +143,7 @@ namespace nexo::renderer { { if (!m_uniformCache.isDirty(name)) { const auto optionalValue = m_uniformCache.getValue(name); - if (optionalValue.has_value() && - std::holds_alternative(*optionalValue) && + if (optionalValue.has_value() && std::holds_alternative(*optionalValue) && std::get(*optionalValue) == value) { return true; } @@ -164,8 +157,7 @@ namespace nexo::renderer { { if (!m_uniformCache.isDirty(name)) { const auto optionalValue = m_uniformCache.getValue(name); - if (optionalValue.has_value() && - std::holds_alternative(*optionalValue) && + if (optionalValue.has_value() && std::holds_alternative(*optionalValue) && std::get(*optionalValue) == value) { return true; } @@ -175,37 +167,37 @@ namespace nexo::renderer { return false; } - bool NxShader::setUniformIntArray( - [[maybe_unused]] const std::string& name, - [[maybe_unused]] const int* values, - [[maybe_unused]] unsigned int count - ) const { + bool NxShader::setUniformIntArray([[maybe_unused]] const std::string& name, [[maybe_unused]] const int* values, + [[maybe_unused]] unsigned int count) const + { // Arrays are more complex for caching, so we'll always update them // In a real implementation, you might want to cache arrays too return false; } - bool NxShader::setUniform(const std::string &name, UniformValue value) const - { - return std::visit([this, &name](T&& arg) -> bool { - using DecayedT = std::decay_t; - if constexpr (std::is_same_v) - return setUniformFloat(name, std::forward(arg)); - else if constexpr (std::is_same_v) - return setUniformFloat2(name, std::forward(arg)); - else if constexpr (std::is_same_v) - return setUniformFloat3(name, std::forward(arg)); - else if constexpr (std::is_same_v) - return setUniformFloat4(name, std::forward(arg)); - else if constexpr (std::is_same_v) - return setUniformInt(name, std::forward(arg)); - else if constexpr (std::is_same_v) - return setUniformBool(name, std::forward(arg)); - else if constexpr (std::is_same_v) - return setUniformMatrix(name, std::forward(arg)); - else - return false; - }, value); + bool NxShader::setUniform(const std::string& name, UniformValue value) const + { + return std::visit( + [this, &name](T&& arg) -> bool { + using DecayedT = std::decay_t; + if constexpr (std::is_same_v) + return setUniformFloat(name, std::forward(arg)); + else if constexpr (std::is_same_v) + return setUniformFloat2(name, std::forward(arg)); + else if constexpr (std::is_same_v) + return setUniformFloat3(name, std::forward(arg)); + else if constexpr (std::is_same_v) + return setUniformFloat4(name, std::forward(arg)); + else if constexpr (std::is_same_v) + return setUniformInt(name, std::forward(arg)); + else if constexpr (std::is_same_v) + return setUniformBool(name, std::forward(arg)); + else if constexpr (std::is_same_v) + return setUniformMatrix(name, std::forward(arg)); + else + return false; + }, + value); } bool NxShader::hasUniform(const std::string& name) const @@ -223,8 +215,8 @@ namespace nexo::renderer { return attributes.compatibleWith(m_requiredAttributes); } - void NxShader::resetCache() + void NxShader::resetCache() const { m_uniformCache.clearAllDirtyFlags(); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Shader.hpp b/engine/src/renderer/Shader.hpp index 43721c64b..13d282305 100644 --- a/engine/src/renderer/Shader.hpp +++ b/engine/src/renderer/Shader.hpp @@ -14,19 +14,17 @@ #pragma once #include -#include #include #include +#include #include -#include "ShaderStorageBuffer.hpp" #include "Attributes.hpp" +#include "ShaderStorageBuffer.hpp" #include "UniformCache.hpp" -namespace nexo::renderer -{ - enum class NxShaderUniforms - { +namespace nexo::renderer { + enum class NxShaderUniforms { VIEW_PROJECTION, MODEL_MATRIX, CAMERA_POSITION, @@ -54,137 +52,300 @@ namespace nexo::renderer {NxShaderUniforms::NB_POINT_LIGHT, "uNbPointLights"}, {NxShaderUniforms::SPOT_LIGHT_ARRAY, "uSpotLights"}, {NxShaderUniforms::NB_SPOT_LIGHT, "uNbSpotLights"}, - {NxShaderUniforms::MATERIAL, "uMaterial"} - }; + {NxShaderUniforms::MATERIAL, "uMaterial"}}; - struct UniformInfo - { - std::string name; // Name of the uniform - int location; // Location in the shader + struct UniformInfo { + std::string name; // Name of the uniform + int location; // Location in the shader unsigned int type; // GL type (e.g., GL_FLOAT, GL_FLOAT_VEC3) - int size; // Size (for arrays) + int size; // Size (for arrays) }; - struct AttributeInfo - { - std::string name; // Name of the attribute - int location; // Location in the shader + struct AttributeInfo { + std::string name; // Name of the attribute + int location; // Location in the shader unsigned int type; // GL type - int size; // Size + int size; // Size }; /** - * @class NxShader - * @brief Abstract class representing a shader program in the rendering pipeline. - * - * The `NxShader` class provides a generic interface for creating and managing shader - * programs. These programs are used to execute rendering operations on the GPU. - * - * Responsibilities: - * - Create shader programs from source code or files. - * - Bind and unbind shader programs during rendering. - * - Set uniform variables to pass data from the CPU to the GPU. - * - * Subclasses: - * - `NxOpenGlShader`: Implements this interface using OpenGL-specific functionality. - * - * Example Usage: - * ```cpp - * auto shader = NxShader::create("path/to/shader.glsl"); - * shader->bind(); - * shader->setUniformFloat("uTime", 1.0f); - * ``` - */ - class NxShader - { - public: + * @class NxShader + * @brief Abstract class representing a shader program in the rendering pipeline. + * + * The `NxShader` class provides a generic interface for creating and managing shader + * programs. These programs are used to execute rendering operations on the GPU. + * + * Responsibilities: + * - Create shader programs from source code or files. + * - Bind and unbind shader programs during rendering. + * - Set uniform variables to pass data from the CPU to the GPU. + * + * Subclasses: + * - `NxOpenGlShader`: Implements this interface using OpenGL-specific functionality. + * + * Example Usage: + * ```cpp + * auto shader = NxShader::create("path/to/shader.glsl"); + * shader->bind(); + * shader->setUniformFloat("uTime", 1.0f); + * ``` + */ + class NxShader { + public: virtual ~NxShader() = default; /** - * @brief Creates a shader program from a source file. - * - * Loads and compiles a shader program from the specified file path. The file - * should contain shader stages marked with `#type` directives. - * - * @param path The file path to the shader source code. - * @return A shared pointer to the created `Shader` instance. - * - * Throws: - * - `NxUnknownGraphicsApi` if no graphics API is supported. - * - `NxShaderCreationFailed` if shader compilation fails. - */ + * @brief Creates a shader program from a source file. + * + * Loads and compiles a shader program from the specified file path. The file + * should contain shader stages marked with `#type` directives. + * + * @param path The file path to the shader source code. + * @return A shared pointer to the created `Shader` instance. + * + * Throws: + * - `NxUnknownGraphicsApi` if no graphics API is supported. + * - `NxShaderCreationFailed` if shader compilation fails. + */ static std::shared_ptr create(const std::string& path); /** - * @brief Creates a shader program from source code strings. - * - * Compiles and links a shader program from provided vertex and fragment shader source code. - * - * @param name The name of the shader program. - * @param vertexSource The source code for the vertex shader. - * @param fragmentSource The source code for the fragment shader. - * @return A shared pointer to the created `Shader` instance. - * - * Throws: - * - `NxUnknownGraphicsApi` if no graphics API is supported. - * - `NxShaderCreationFailed` if shader compilation fails. - */ + * @brief Creates a shader program from source code strings. + * + * Compiles and links a shader program from provided vertex and fragment shader source code. + * + * @param name The name of the shader program. + * @param vertexSource The source code for the vertex shader. + * @param fragmentSource The source code for the fragment shader. + * @return A shared pointer to the created `Shader` instance. + * + * Throws: + * - `NxUnknownGraphicsApi` if no graphics API is supported. + * - `NxShaderCreationFailed` if shader compilation fails. + */ static std::shared_ptr create(const std::string& name, const std::string& vertexSource, const std::string& fragmentSource); /** - * @brief Binds the shader program for use in the rendering pipeline. - * - * Makes the shader program active, so subsequent draw calls use this program. - * - * Must be implemented by subclasses. - */ + * @brief Binds the shader program for use in the rendering pipeline. + * + * Makes the shader program active, so subsequent draw calls use this program. + * + * Must be implemented by subclasses. + */ virtual void bind() const = 0; /** - * @brief Unbinds the shader program. - * - * Deactivates the currently bound shader program. - * - * Must be implemented by subclasses. - */ + * @brief Unbinds the shader program. + * + * Deactivates the currently bound shader program. + * + * Must be implemented by subclasses. + */ virtual void unbind() const = 0; + /** + * @brief Sets a float uniform value in the shader by name. + * @param name The name of the uniform variable. + * @param value The float value to set. + * @return true if the uniform was found and set, false otherwise. + */ virtual bool setUniformFloat(const std::string& name, float value) const; + + /** + * @brief Sets a vec2 uniform value in the shader by name. + * @param name The name of the uniform variable. + * @param values The 2D vector value to set. + * @return true if the uniform was found and set, false otherwise. + */ virtual bool setUniformFloat2(const std::string& name, const glm::vec2& values) const; + + /** + * @brief Sets a vec3 uniform value in the shader by name. + * @param name The name of the uniform variable. + * @param values The 3D vector value to set. + * @return true if the uniform was found and set, false otherwise. + */ virtual bool setUniformFloat3(const std::string& name, const glm::vec3& values) const; + + /** + * @brief Sets a vec4 uniform value in the shader by name. + * @param name The name of the uniform variable. + * @param values The 4D vector value to set. + * @return true if the uniform was found and set, false otherwise. + */ virtual bool setUniformFloat4(const std::string& name, const glm::vec4& values) const; + + /** + * @brief Sets a mat4 uniform value in the shader by name. + * @param name The name of the uniform variable. + * @param matrix The 4x4 matrix value to set. + * @return true if the uniform was found and set, false otherwise. + */ virtual bool setUniformMatrix(const std::string& name, const glm::mat4& matrix) const; + + /** + * @brief Sets a boolean uniform value in the shader by name. + * @param name The name of the uniform variable. + * @param value The boolean value to set. + * @return true if the uniform was found and set, false otherwise. + */ virtual bool setUniformBool(const std::string& name, bool value) const; + + /** + * @brief Sets an integer uniform value in the shader by name. + * @param name The name of the uniform variable. + * @param value The integer value to set. + * @return true if the uniform was found and set, false otherwise. + */ virtual bool setUniformInt(const std::string& name, int value) const; + + /** + * @brief Sets an array of integer uniform values in the shader by name. + * @param name The name of the uniform variable. + * @param values Pointer to the array of integer values. + * @param count Number of elements in the array. + * @return true if the uniform was found and set, false otherwise. + */ virtual bool setUniformIntArray(const std::string& name, const int* values, unsigned int count) const; + /** + * @brief Sets a float uniform value in the shader using enum identifier. + * @param uniform The uniform enum identifier. + * @param value The float value to set. + * @return true if the uniform was found and set. + */ virtual bool setUniformFloat(NxShaderUniforms uniform, float value) const = 0; + + /** + * @brief Sets a vec3 uniform value using enum identifier. + * @param uniform The uniform enum identifier. + * @param values The 3D vector value to set. + * @return true if the uniform was found and set. + */ virtual bool setUniformFloat3(NxShaderUniforms uniform, const glm::vec3& values) const = 0; + + /** + * @brief Sets a vec4 uniform value using enum identifier. + * @param uniform The uniform enum identifier. + * @param values The 4D vector value to set. + * @return true if the uniform was found and set. + */ virtual bool setUniformFloat4(NxShaderUniforms uniform, const glm::vec4& values) const = 0; + + /** + * @brief Sets a mat4 uniform value using enum identifier. + * @param uniform The uniform enum identifier. + * @param matrix The 4x4 matrix value to set. + * @return true if the uniform was found and set. + */ virtual bool setUniformMatrix(NxShaderUniforms uniform, const glm::mat4& matrix) const = 0; + + /** + * @brief Sets an integer uniform value using enum identifier. + * @param uniform The uniform enum identifier. + * @param value The integer value to set. + * @return true if the uniform was found and set. + */ virtual bool setUniformInt(NxShaderUniforms uniform, int value) const = 0; + + /** + * @brief Sets an array of integer values using enum identifier. + * @param uniform The uniform enum identifier. + * @param values Pointer to the integer array. + * @param count Number of elements. + * @return true if the uniform was found and set. + */ virtual bool setUniformIntArray(NxShaderUniforms uniform, const int* values, unsigned int count) const = 0; + /** + * @brief Sets a uniform value by name using UniformValue variant. + * @param name The uniform name. + * @param value The value to set. + * @return true if the uniform was found and set. + */ bool setUniform(const std::string& name, UniformValue value) const; + /** + * @brief Adds a shader storage buffer to the shader. + * @param buffer The buffer to add. + */ void addStorageBuffer(const std::shared_ptr& buffer); - void setStorageBufferData(size_t index, void* data, size_t size); + + /** + * @brief Updates data in a storage buffer at given index. + * @param index Buffer index. + * @param data Pointer to the data. + * @param size Size of the data in bytes. + */ + void setStorageBufferData(size_t index, void* data, size_t size) const; + + /** + * @brief Binds a storage buffer to a binding point. + * @param index Buffer index. + * @param bindingPoint Target binding point. + */ virtual void bindStorageBufferBase(unsigned int index, unsigned int bindingPoint) const = 0; + + /** + * @brief Binds a storage buffer. + * @param index Buffer index to bind. + */ virtual void bindStorageBuffer(unsigned int index) const = 0; + + /** + * @brief Unbinds a storage buffer. + * @param index Buffer index to unbind. + */ virtual void unbindStorageBuffer(unsigned int index) const = 0; + /** + * @brief Checks if a uniform exists in the shader. + * @param name Name of the uniform. + * @return true if the uniform exists. + */ bool hasUniform(const std::string& name) const; + + /** + * @brief Checks if an attribute exists at given location. + * @param location Attribute location. + * @return true if the attribute exists. + */ bool hasAttribute(int location) const; + /** + * @brief Checks if shader is compatible with mesh attributes. + * @param meshAttributes Required attributes. + * @return true if compatible. + */ bool isCompatibleWithMesh(const RequiredAttributes& meshAttributes) const; - void resetCache(); + /** + * @brief Resets the uniform cache. + */ + void resetCache() const; + /** + * @brief Gets the shader program name. + * @return The name of the shader. + */ [[nodiscard]] virtual const std::string& getName() const = 0; + + /** + * @brief Gets the shader program ID. + * @return The program ID. + */ virtual unsigned int getProgramId() const = 0; - protected: + protected: + /** + * @brief Reads the contents of a file into a string. + * @param filepath The path to the file. + * @return The file contents as a string. + * + * Throws: + * - `NxFileNotFoundException` if the file cannot be opened. + */ static std::string readFile(const std::string& filepath); std::vector> m_storageBuffers; RequiredAttributes m_requiredAttributes; @@ -192,4 +353,4 @@ namespace nexo::renderer std::unordered_map m_attributeInfos; mutable UniformCache m_uniformCache; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/ShaderLibrary.cpp b/engine/src/renderer/ShaderLibrary.cpp index 5da0014ef..2aa1f5344 100644 --- a/engine/src/renderer/ShaderLibrary.cpp +++ b/engine/src/renderer/ShaderLibrary.cpp @@ -21,7 +21,7 @@ namespace nexo::renderer { ShaderLibrary::ShaderLibrary() { // Helper lambda to safely load a shader with proper error handling - auto safeLoadShader = [this](const std::string& name, const std::string& relativePath) { + auto safeLoadShader = [this](const std::string &name, const std::string &relativePath) { try { // Resolve the absolute path const std::filesystem::path absPath = Path::resolvePathRelativeToExe(relativePath); @@ -36,7 +36,7 @@ namespace nexo::renderer { load(name, absPath.string()); LOG(NEXO_INFO, "Shader '{}' loaded successfully", name); return true; - } catch (const std::exception& e) { + } catch (const std::exception &e) { LOG(NEXO_ERROR, "Failed to load shader '{}': {}", name, e.what()); return false; } catch (...) { @@ -52,12 +52,13 @@ namespace nexo::renderer { safeLoadShader("Albedo unshaded transparent", "../resources/shaders/albedo_unshaded_transparent.glsl"); safeLoadShader("Grid shader", "../resources/shaders/grid_shader.glsl"); safeLoadShader("Flat color", "../resources/shaders/flat_color.glsl"); + safeLoadShader("AABB Debug", "../resources/shaders/box_shader.glsl"); } void ShaderLibrary::add(const std::shared_ptr &shader) { const std::string &name = shader->getName(); - m_shaders[name] = shader; + m_shaders[name] = shader; } void ShaderLibrary::add(const std::string &name, const std::shared_ptr &shader) @@ -79,7 +80,8 @@ namespace nexo::renderer { return shader; } - std::shared_ptr ShaderLibrary::load(const std::string &name, const std::string &vertexSource, const std::string &fragmentSource) + std::shared_ptr ShaderLibrary::load(const std::string &name, const std::string &vertexSource, + const std::string &fragmentSource) { auto shader = NxShader::create(name, vertexSource, fragmentSource); add(shader); @@ -88,11 +90,10 @@ namespace nexo::renderer { std::shared_ptr ShaderLibrary::get(const std::string &name) const { - if (!m_shaders.contains(name)) - { + if (!m_shaders.contains(name)) { LOG(NEXO_WARN, "ShaderLibrary::get: shader {} not found", name); return nullptr; } return m_shaders.at(name); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/ShaderLibrary.hpp b/engine/src/renderer/ShaderLibrary.hpp index 36f8109f6..f808cf3ec 100644 --- a/engine/src/renderer/ShaderLibrary.hpp +++ b/engine/src/renderer/ShaderLibrary.hpp @@ -19,43 +19,86 @@ namespace nexo::renderer { struct TransparentStringHasher { - using is_transparent = void; // enable heterogeneous lookup + using is_transparent = void; // enable heterogeneous lookup - size_t operator()(std::string_view sv) const noexcept { + size_t operator()(const std::string_view sv) const noexcept + { return std::hash{}(sv); } - size_t operator()(const std::string &s) const noexcept { + size_t operator()(const std::string &s) const noexcept + { return operator()(std::string_view(s)); } }; class ShaderLibrary { - private: - // Singleton: private constructor and destructor - ShaderLibrary(); - ~ShaderLibrary() = default; - public: - void add(const std::shared_ptr &shader); - void add(const std::string &name, const std::shared_ptr &shader); - std::shared_ptr load(const std::string &path); - std::shared_ptr load(const std::string &name, const std::string &path); - std::shared_ptr load(const std::string &name, const std::string &vertexSource, const std::string &fragmentSource); - std::shared_ptr get(const std::string &name) const; - - static ShaderLibrary& getInstance() - { - static ShaderLibrary instance; - return instance; - } - ShaderLibrary(ShaderLibrary const&) = delete; - void operator=(ShaderLibrary const&) = delete; - private: - - std::unordered_map< - std::string, - std::shared_ptr, - TransparentStringHasher, - std::equal_to<> - > m_shaders; + private: + // Singleton: private constructor and destructor + ShaderLibrary(); + ~ShaderLibrary() = default; + + public: + /** + * @brief Adds a shader to the library. + * @param shader Shared pointer to the shader to add. + */ + void add(const std::shared_ptr &shader); + + /** @brief Adds a shader with a specific name to the library. + * @param name Name to associate with the shader. + * @param shader Shared pointer to the shader to add. + */ + void add(const std::string &name, const std::shared_ptr &shader); + + /** + * @brief Loads a shader from a file path and adds it to the library. + * The shader name is derived from the file name. + * @param path File path to load the shader from. + * @return Shared pointer to the loaded shader. + */ + std::shared_ptr load(const std::string &path); + + /** + * @brief Loads a shader from a file path with a specific name. + * @param name Custom name to associate with the shader. + * @param path File path to load the shader from. + * @return Shared pointer to the loaded shader. + */ + std::shared_ptr load(const std::string &name, const std::string &path); + + /** + * @brief Creates a shader from source code strings. + * @param name Name to associate with the shader. + * @param vertexSource Source code for the vertex shader. + * @param fragmentSource Source code for the fragment shader. + * @return Shared pointer to the created shader. + */ + std::shared_ptr load(const std::string &name, const std::string &vertexSource, + const std::string &fragmentSource); + + /** + * @brief Retrieves a shader from the library by name. + * @param name Name of the shader to retrieve. + * @return Shared pointer to the requested shader. + * @throw std::runtime_error if shader not found. + */ + [[nodiscard]] std::shared_ptr get(const std::string &name) const; + + /** + * @brief Gets the singleton instance of the shader library. + * @return Reference to the shader library instance. + */ + static ShaderLibrary &getInstance() + { + static ShaderLibrary instance; + return instance; + } + + // Delete copy constructor and assignment operator to ensure singleton pattern + ShaderLibrary(ShaderLibrary const &) = delete; + void operator=(ShaderLibrary const &) = delete; + + private: + std::unordered_map, TransparentStringHasher, std::equal_to<> > m_shaders; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/ShaderStorageBuffer.cpp b/engine/src/renderer/ShaderStorageBuffer.cpp index 6ebbac635..33e045895 100644 --- a/engine/src/renderer/ShaderStorageBuffer.cpp +++ b/engine/src/renderer/ShaderStorageBuffer.cpp @@ -13,21 +13,21 @@ /////////////////////////////////////////////////////////////////////////////// #include "ShaderStorageBuffer.hpp" -#include "renderer/RendererExceptions.hpp" #include +#include "renderer/RendererExceptions.hpp" #ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlShaderStorageBuffer.hpp" #endif namespace nexo::renderer { - std::shared_ptr NxShaderStorageBuffer::create(unsigned int size) - { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(size); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif - } + std::shared_ptr NxShaderStorageBuffer::create(unsigned int size) + { +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(size); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif + } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/ShaderStorageBuffer.hpp b/engine/src/renderer/ShaderStorageBuffer.hpp index b00fe9cd6..f0723cae6 100644 --- a/engine/src/renderer/ShaderStorageBuffer.hpp +++ b/engine/src/renderer/ShaderStorageBuffer.hpp @@ -17,17 +17,76 @@ #include namespace nexo::renderer { - class NxShaderStorageBuffer { - public: - virtual ~NxShaderStorageBuffer() = default; + class NxShaderStorageBuffer { + public: + virtual ~NxShaderStorageBuffer() = default; - static std::shared_ptr create(unsigned int size); + /** + * @brief Creates a shader storage buffer of the specified size. + * @param size Size of the buffer in bytes. + * @return A shared pointer to the created NxShaderStorageBuffer instance. + * + * Throws: + * - Implementation-specific exceptions if buffer creation fails. + * + * Notes: + * - This function is typically implemented in a platform-specific or API-specific source file. + */ + static std::shared_ptr create(unsigned int size); - virtual void bind() const = 0; - virtual void bindBase(unsigned int bindingLocation) const = 0; - virtual void unbind() const = 0; + /** + * @brief Binds the shader storage buffer to the current context. + * + * This method binds the shader storage buffer, making it active for subsequent operations. + * Binding a buffer allows it to be used in shader programs for reading and writing data. + * + * Notes: + * - The actual binding point may depend on the graphics API and implementation. + */ + virtual void bind() const = 0; - virtual void setData(void *data, size_t size) = 0; - [[nodiscard]] virtual unsigned int getId() const = 0; - }; -} + /** + * @brief Binds the shader storage buffer to a specific binding point. + * @param bindingLocation The binding point index to bind the buffer to. + * + * This method binds the shader storage buffer to a specified binding point, + * allowing shaders to access the buffer data at that location. + * + * Notes: + * - The binding location must match the one specified in the shader code. + */ + virtual void bindBase(unsigned int bindingLocation) const = 0; + + /** + * @brief Unbinds the shader storage buffer from the current context. + * + * This method unbinds the shader storage buffer, making it inactive for subsequent operations. + * Unbinding a buffer ensures that it is no longer used in shader programs until it is bound again. + */ + virtual void unbind() const = 0; + + /** + * @brief Updates the data stored in the shader storage buffer. + * @param data Pointer to the data to upload to the buffer. + * @param size Size of the data in bytes. + * + * This method updates the contents of the shader storage buffer with new data. + * The provided data pointer should point to a memory block of at least 'size' bytes. + * + * Notes: + * - The size must not exceed the size of the buffer allocated during creation. + * - The data format must match what the shader expects for correct interpretation. + */ + virtual void setData(void *data, size_t size) = 0; + + /** + * @brief Retrieves the unique identifier of the shader storage buffer. + * @return The unique ID of the buffer. + * + * This method returns an unsigned integer that uniquely identifies the shader storage buffer + * within the graphics context. This ID can be used for debugging or advanced operations + * that require direct access to the underlying buffer resource. + */ + [[nodiscard]] virtual unsigned int getId() const = 0; + }; +} // namespace nexo::renderer diff --git a/engine/src/renderer/SubTexture2D.cpp b/engine/src/renderer/SubTexture2D.cpp index 5ec3e1046..1587bafd4 100644 --- a/engine/src/renderer/SubTexture2D.cpp +++ b/engine/src/renderer/SubTexture2D.cpp @@ -14,7 +14,8 @@ #include "SubTexture2D.hpp" namespace nexo::renderer { - NxSubTexture2D::NxSubTexture2D(const std::shared_ptr &texture, const glm::vec2 &min, const glm::vec2 &max) + NxSubTexture2D::NxSubTexture2D(const std::shared_ptr &texture, const glm::vec2 &min, + const glm::vec2 &max) : m_texture(texture) { m_texCoords[0] = {min.x, min.y}; @@ -23,11 +24,15 @@ namespace nexo::renderer { m_texCoords[3] = {min.x, max.y}; } - std::shared_ptr NxSubTexture2D::createFromCoords(const std::shared_ptr &texture, const glm::vec2 &coords, const glm::vec2 &cellSize, const glm::vec2 &spriteSize) + std::shared_ptr NxSubTexture2D::createFromCoords(const std::shared_ptr &texture, + const glm::vec2 &coords, const glm::vec2 &cellSize, + const glm::vec2 &spriteSize) { - glm::vec2 min = {(coords.x * cellSize.x) / static_cast(texture->getWidth()) , (coords.y * cellSize.y) / static_cast(texture->getHeight())}; - glm::vec2 max = {((coords.x + spriteSize.x) * cellSize.x) / static_cast(texture->getWidth()), ((coords.y + spriteSize.y) * cellSize.y) / static_cast(texture->getHeight())}; + glm::vec2 min = {(coords.x * cellSize.x) / static_cast(texture->getWidth()), + (coords.y * cellSize.y) / static_cast(texture->getHeight())}; + glm::vec2 max = {((coords.x + spriteSize.x) * cellSize.x) / static_cast(texture->getWidth()), + ((coords.y + spriteSize.y) * cellSize.y) / static_cast(texture->getHeight())}; return std::make_shared(texture, min, max); } -} \ No newline at end of file +} // namespace nexo::renderer diff --git a/engine/src/renderer/SubTexture2D.hpp b/engine/src/renderer/SubTexture2D.hpp index 1062e5722..f279227fe 100644 --- a/engine/src/renderer/SubTexture2D.hpp +++ b/engine/src/renderer/SubTexture2D.hpp @@ -19,80 +19,98 @@ namespace nexo::renderer { /** - * @class NxSubTexture2D - * @brief Represents a portion of a 2D texture, useful for sprite rendering. - * - * The `NxSubTexture2D` class allows defining a sub-region within a larger texture. - * This is commonly used in sprite sheets where a single texture contains multiple - * sprites. The class provides texture coordinates to render only the specified region. - * - * Responsibilities: - * - Define a rectangular sub-region of a texture using minimum and maximum coordinates. - * - Provide texture coordinates for rendering the sub-region. - * - Create subtextures dynamically from grid coordinates, such as in sprite sheets. - * - * Example Usage: - * ```cpp - * auto texture = NxTexture2D::create("path/to/texture.png"); - * auto subTexture = NxSubTexture2D::createFromCoords(texture, {1, 1}, {64, 64}); - * ``` - */ + * @class NxSubTexture2D + * @brief Represents a portion of a 2D texture, useful for sprite rendering. + * + * The `NxSubTexture2D` class allows defining a sub-region within a larger texture. + * This is commonly used in sprite sheets where a single texture contains multiple + * sprites. The class provides texture coordinates to render only the specified region. + * + * Responsibilities: + * - Define a rectangular sub-region of a texture using minimum and maximum coordinates. + * - Provide texture coordinates for rendering the sub-region. + * - Create subtextures dynamically from grid coordinates, such as in sprite sheets. + * + * Example Usage: + * ```cpp + * auto texture = NxTexture2D::create("path/to/texture.png"); + * auto subTexture = NxSubTexture2D::createFromCoords(texture, {1, 1}, {64, 64}); + * ``` + */ class NxSubTexture2D { - public: - /** - * @brief Constructs a `NxSubTexture2D` from specified texture coordinates. - * - * Initializes the subtexture by defining its bounds using normalized minimum - * and maximum coordinates. The coordinates should be normalized to the texture's size - * (values between 0 and 1). - * - * @param texture A shared pointer to the base `NxTexture2D`. - * @param min The normalized minimum coordinates (bottom-left corner) of the subtexture. - * @param max The normalized maximum coordinates (top-right corner) of the subtexture. - */ - NxSubTexture2D(const std::shared_ptr &texture, const glm::vec2 &min, const glm::vec2 &max); + public: + /** + * @brief Constructs a `NxSubTexture2D` from specified texture coordinates. + * + * Initializes the subtexture by defining its bounds using normalized minimum + * and maximum coordinates. The coordinates should be normalized to the texture's size + * (values between 0 and 1). + * + * @param texture A shared pointer to the base `NxTexture2D`. + * @param min The normalized minimum coordinates (bottom-left corner) of the subtexture. + * @param max The normalized maximum coordinates (top-right corner) of the subtexture. + */ + NxSubTexture2D(const std::shared_ptr &texture, const glm::vec2 &min, const glm::vec2 &max); - [[nodiscard]] const std::shared_ptr &getTexture() const { return m_texture; }; - /** - * @brief Retrieves the texture coordinates for the subtexture. - * - * Returns a pointer to an array of four `glm::vec2` objects representing the - * texture coordinates of the subtexture's corners. The coordinates are ordered as: - * - Bottom-left - * - Bottom-right - * - Top-right - * - Top-left - * - * @return A constant pointer to the array of texture coordinates. - */ - [[nodiscard]] const glm::vec2 *getTextureCoords() const { return m_texCoords; }; + /** + * @brief Retrieves the base texture associated with the subtexture. + * + * Returns a shared pointer to the `NxTexture2D` that this subtexture is derived from. + * This allows access to the original texture for rendering or other operations. + * + * @return A constant reference to the shared pointer of the base texture. + */ + [[nodiscard]] const std::shared_ptr &getTexture() const + { + return m_texture; + } - /** - * @brief Creates a `NxSubTexture2D` from grid-based coordinates within a texture. - * - * Dynamically calculates the normalized minimum and maximum texture coordinates - * for a subtexture based on its position and size in a sprite sheet. - * - * @param texture A shared pointer to the base `NxTexture2D`. - * @param coords The grid-based coordinates (e.g., sprite index in a sprite sheet). - * @param cellSize The size of each cell (sprite) in the sprite sheet, in pixels. - * @param spriteSize The size of the sprite in grid units, defaulting to {1, 1}. - * @return A shared pointer to the created `NxSubTexture2D` instance. - * - * Example: - * ```cpp - * auto texture = NxTexture2D::create("path/to/spritesheet.png"); - * auto subTexture = NxSubTexture2D::createFromCoords(texture, {1, 1}, {64, 64}); - * ``` - * - * Example Explanation: - * - The sprite is located at grid position (1, 1) in the sprite sheet. - * - Each cell in the grid is 64x64 pixels. - * - The sprite occupies one grid cell by default. - */ - static std::shared_ptr createFromCoords(const std::shared_ptr &texture, const glm::vec2 &coords, const glm::vec2 &cellSize, const glm::vec2 &spriteSize = {1, 1}); - private: - std::shared_ptr m_texture; - glm::vec2 m_texCoords[4]{}; + /** + * @brief Retrieves the texture coordinates for the subtexture. + * + * Returns a pointer to an array of four `glm::vec2` objects representing the + * texture coordinates of the subtexture's corners. The coordinates are ordered as: + * - Bottom-left + * - Bottom-right + * - Top-right + * - Top-left + * + * @return A constant pointer to the array of texture coordinates. + */ + [[nodiscard]] const glm::vec2 *getTextureCoords() const + { + return m_texCoords; + } + + /** + * @brief Creates a `NxSubTexture2D` from grid-based coordinates within a texture. + * + * Dynamically calculates the normalized minimum and maximum texture coordinates + * for a subtexture based on its position and size in a sprite sheet. + * + * @param texture A shared pointer to the base `NxTexture2D`. + * @param coords The grid-based coordinates (e.g., sprite index in a sprite sheet). + * @param cellSize The size of each cell (sprite) in the sprite sheet, in pixels. + * @param spriteSize The size of the sprite in grid units, defaulting to {1, 1}. + * @return A shared pointer to the created `NxSubTexture2D` instance. + * + * Example: + * ```cpp + * auto texture = NxTexture2D::create("path/to/spritesheet.png"); + * auto subTexture = NxSubTexture2D::createFromCoords(texture, {1, 1}, {64, 64}); + * ``` + * + * Example Explanation: + * - The sprite is located at grid position (1, 1) in the sprite sheet. + * - Each cell in the grid is 64x64 pixels. + * - The sprite occupies one grid cell by default. + */ + static std::shared_ptr createFromCoords(const std::shared_ptr &texture, + const glm::vec2 &coords, const glm::vec2 &cellSize, + const glm::vec2 &spriteSize = {1, 1}); + + private: + std::shared_ptr m_texture; + glm::vec2 m_texCoords[4]{}; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Texture.cpp b/engine/src/renderer/Texture.cpp index 67f9f35e8..a065fd8bc 100644 --- a/engine/src/renderer/Texture.cpp +++ b/engine/src/renderer/Texture.cpp @@ -13,9 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #include "Texture.hpp" -#include "Renderer.hpp" -#include "renderer/RendererExceptions.hpp" +// #include "Renderer.hpp" #include "String.hpp" +#include "renderer/RendererExceptions.hpp" #ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlTexture2D.hpp" @@ -23,18 +23,38 @@ namespace nexo::renderer { - NxTextureFormat NxTextureFormatFromString(const std::string_view& format) + /** + * Converts a string representation of a texture format to the corresponding NxTextureFormat enum value. + * + * This function takes a string input representing a texture format (e.g., "R8", "RG8", "RGB8", "RGBA8") + * and returns the corresponding NxTextureFormat enum value. If the input string does not match any + * known formats, the function returns NxTextureFormat::INVALID. + * + * @param format A string_view representing the texture format. + * @return The corresponding NxTextureFormat enum value, or NxTextureFormat::INVALID if the format is unrecognized. + */ + NxTextureFormat NxTextureFormatFromString(const std::string_view &format) { - if (iequals(format, "R8")) return NxTextureFormat::R8; - if (iequals(format, "RG8")) return NxTextureFormat::RG8; - if (iequals(format, "RGB8")) return NxTextureFormat::RGB8; + if (iequals(format, "R8")) return NxTextureFormat::R8; + if (iequals(format, "RG8")) return NxTextureFormat::RG8; + if (iequals(format, "RGB8")) return NxTextureFormat::RGB8; if (iequals(format, "RGBA8")) return NxTextureFormat::RGBA8; return NxTextureFormat::INVALID; } + /** + * Converts an array of pixels from ARGB8 format to RGBA8 format in place. + * + * This function takes a pointer to an array of bytes representing pixel data in ARGB8 format + * and converts each pixel to RGBA8 format. The conversion is done in place, meaning the original + * array is modified directly. + * + * @param bytes Pointer to the array of bytes containing pixel data in ARGB8 format. + * @param size The size of the byte array. It should be a multiple of 4, as each pixel consists of 4 bytes (A, R, G, B). + */ void NxTextureFormatConvertArgb8ToRgba8(uint8_t *bytes, const size_t size) { - auto *pixels = reinterpret_cast(bytes); + auto *pixels = reinterpret_cast(bytes); const size_t width = size / 4; for (size_t i = 0; i < width; ++i) { @@ -44,39 +64,39 @@ namespace nexo::renderer { std::shared_ptr NxTexture2D::create(unsigned int width, unsigned int height) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(width, height); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(width, height); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } std::shared_ptr NxTexture2D::create(const uint8_t *buffer, unsigned int width, unsigned int height, - NxTextureFormat format) + NxTextureFormat format) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(buffer, width, height, format); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(buffer, width, height, format); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } - std::shared_ptr NxTexture2D::create(const uint8_t* buffer, unsigned int len) + std::shared_ptr NxTexture2D::create(const uint8_t *buffer, unsigned int len) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(buffer, len); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(buffer, len); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } std::shared_ptr NxTexture2D::create(const std::string &path) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(path); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(path); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Texture.hpp b/engine/src/renderer/Texture.hpp index e3bef4936..22383fe1a 100644 --- a/engine/src/renderer/Texture.hpp +++ b/engine/src/renderer/Texture.hpp @@ -20,37 +20,109 @@ namespace nexo::renderer { /** - * @class NxTexture - * @brief Abstract base class for representing textures in a rendering system. - * - * The `Texture` class provides a common interface for managing texture resources - * in a rendering API. It defines the basic operations for texture creation, data management, - * and binding/unbinding to the graphics pipeline. - * - * Responsibilities: - * - Retrieve texture properties (e.g., width, height, ID). - * - Manage texture data. - * - Bind and unbind textures to specific texture slots. - * - * Derived classes (e.g., `NxOpenGlTexture2D`) implement platform-specific behavior for - * managing textures in different rendering backends. - */ + * @class NxTexture + * @brief Abstract base class for representing textures in a rendering system. + * + * The `Texture` class provides a common interface for managing texture resources + * in a rendering API. It defines the basic operations for texture creation, data management, + * and binding/unbinding to the graphics pipeline. + * + * Responsibilities: + * - Retrieve texture properties (e.g., width, height, ID). + * - Manage texture data. + * - Bind and unbind textures to specific texture slots. + * + * Derived classes (e.g., `NxOpenGlTexture2D`) implement platform-specific behavior for + * managing textures in different rendering backends. + */ class NxTexture { - public: - virtual ~NxTexture() = default; + public: + virtual ~NxTexture() = default; + + /** + * @brief Gets the width of the texture in pixels. + * @return The width of the texture. + */ + [[nodiscard]] virtual unsigned int getWidth() const = 0; - [[nodiscard]] virtual unsigned int getWidth() const = 0; - [[nodiscard]] virtual unsigned int getHeight() const = 0; - [[nodiscard]] virtual unsigned int getMaxTextureSize() const = 0; + /** + * @brief Gets the height of the texture in pixels. + * @return The height of the texture. + */ + [[nodiscard]] virtual unsigned int getHeight() const = 0; - [[nodiscard]] virtual unsigned int getId() const = 0; + /** + * @brief Gets the maximum supported texture size. + * @return The maximum size (width or height) in pixels that a texture can have. + * @note This value is typically hardware-dependent and may vary across different graphics cards. + */ + [[nodiscard]] virtual unsigned int getMaxTextureSize() const = 0; + /** + * @brief Retrieves the unique identifier of the texture. + * + * This ID is typically assigned by the graphics API (e.g., OpenGL, DirectX) + * when the texture is created and is used for binding and managing the texture + * within the rendering pipeline. + * + * @return The unique identifier of the texture as an unsigned integer. + */ + [[nodiscard]] virtual unsigned int getId() const = 0; - virtual void bind(unsigned int slot = 0) const = 0; - virtual void unbind(unsigned int slot = 0) const = 0; + /** + * @brief Binds the texture to a specified texture slot. + * + * This method binds the texture to the specified texture slot, making it active for + * subsequent rendering operations. If no slot is specified, it defaults to slot 0. + * + * @param slot The texture slot to bind the texture to (default is 0). + * + * Throws: + * - Implementation-specific exceptions if binding fails. + * + * Notes: + * - The actual implementation of this method is platform-specific and should be + * provided by derived classes. + */ + virtual void bind(unsigned int slot = 0) const = 0; - virtual void setData(void *data, size_t size) = 0; + /** + * @brief Unbinds the texture from a specified texture slot. + * + * This method unbinds the texture from the specified texture slot, effectively + * detaching it from the graphics pipeline. If no slot is specified, it defaults + * to slot 0. + * + * @param slot The texture slot to unbind the texture from (default is 0). + * + * Notes: + * - The actual implementation of this method is platform-specific and should be + * provided by derived classes. + */ + virtual void unbind(unsigned int slot = 0) const = 0; - bool operator==(const NxTexture &other) const { return this->getId() == other.getId(); }; + /** + * @brief Sets the texture data from a raw data buffer. + * + * This method updates the texture's pixel data using the provided raw data buffer. + * The size parameter specifies the size of the data in bytes. The data format and + * layout must match the texture's internal format. + * + * @param data Pointer to the raw pixel data buffer. + * @param size Size of the data buffer in bytes. + * + * Throws: + * - Implementation-specific exceptions if setting the data fails. + * + * Notes: + * - The actual implementation of this method is platform-specific and should be + * provided by derived classes. + */ + virtual void setData(void *data, size_t size) = 0; + + bool operator==(const NxTexture &other) const + { + return this->getId() == other.getId(); + }; }; /** @@ -71,10 +143,10 @@ namespace nexo::renderer { enum class NxTextureFormat { INVALID = 0, // Invalid texture format, used for error reporting - R8 = 1, // 1 channel RED, 8 bits per channel - RG8, // 2 channels RED GREEN, 8 bits per channel - RGB8, // 3 channels RED GREEN BLUE, 8 bits per channel - RGBA8, // 4 channels RED GREEN BLUE ALPHA, 8 bits per channel + R8 = 1, // 1 channel RED, 8 bits per channel + RG8, // 2 channels RED GREEN, 8 bits per channel + RGB8, // 3 channels RED GREEN BLUE, 8 bits per channel + RGBA8, // 4 channels RED GREEN BLUE ALPHA, 8 bits per channel _NB_FORMATS_ // Number of texture formats, used for array sizing }; @@ -91,11 +163,16 @@ namespace nexo::renderer { [[nodiscard]] constexpr std::string_view NxTextureFormatToString(const NxTextureFormat format) { switch (format) { - case NxTextureFormat::R8: return "R8"; - case NxTextureFormat::RG8: return "RG8"; - case NxTextureFormat::RGB8: return "RGB8"; - case NxTextureFormat::RGBA8: return "RGBA8"; - default: return "INVALID"; + case NxTextureFormat::R8: + return "R8"; + case NxTextureFormat::RG8: + return "RG8"; + case NxTextureFormat::RGB8: + return "RGB8"; + case NxTextureFormat::RGBA8: + return "RGBA8"; + default: + return "INVALID"; } } @@ -123,90 +200,91 @@ namespace nexo::renderer { */ void NxTextureFormatConvertArgb8ToRgba8(uint8_t *bytes, size_t size); - class NxTexture2D : public NxTexture { - public: - /** - * @brief Creates a blank 2D texture with the specified dimensions. - * - * Allocates a texture resource with the given width and height. The texture - * will have no initial data and can be updated later with `setData`. - * - * @param width The width of the texture in pixels. - * @param height The height of the texture in pixels. - * @return A shared pointer to the created `NxTexture2D` instance. - * - * Example: - * ```cpp - * auto blankTexture = NxTexture2D::create(512, 512); - * ``` - */ - static std::shared_ptr create(unsigned int width, unsigned int height); - - - /** - * @brief Creates a 2D texture from raw pixel data in memory. - * - * Creates a texture from a raw pixel buffer with the specified dimensions and format. - * This is useful when you have direct access to pixel data that wasn't loaded through - * compressed images files or when you want to create textures from procedurally generated data. - * - * @param buffer Pointer to the raw pixel data. The buffer should contain pixel data - * in a format that matches the specified NxTextureFormat. The data consists - * of height scanlines of width pixels, with each pixel consisting of N components - * (where N depends on the format). The first pixel pointed to is bottom-left-most - * in the image. There is no padding between image scanlines or between pixels. - * Each component is an 8-bit unsigned value (uint8_t). - * @param width The width of the texture in pixels. - * @param height The height of the texture in pixels. - * @param format The format of the pixel data, which determines the number of components - * per pixel. - * @return A shared pointer to the created NxTexture2D instance. - * - * Example: - * ```cpp - * // Create a 128x128 RGBA texture with custom data - * std::vector pixelData(128 * 128 * 4); // 4 components (RGBA) - * // Fill pixelData with your custom values... - * auto texture = NxTexture2D::create(pixelData.data(), 128, 128, NxTextureFormat::RGBA8); - * ``` - */ - static std::shared_ptr create(const uint8_t *buffer, unsigned int width, unsigned int height, NxTextureFormat format); - - /** - * @brief Creates a 2D texture from file in memory. - * - * Loads the texture data from the specified memory buffer. The buffer must contain - * image data in a supported format (e.g., PNG, JPG). The texture will be ready - * for rendering after creation. - * - * @param buffer The memory buffer containing the texture image data. - * @param len The length of the memory buffer in bytes. - * @return A shared pointer to the created `NxTexture2D` instance. - * - * Example: - * ```cpp - * std::vector imageData = ...; // Load image data into a buffer - * auto texture = NxTexture2D::create(imageData.data(), imageData.size()); - * ``` - */ - static std::shared_ptr create(const uint8_t* buffer, unsigned int len); - - /** - * @brief Creates a 2D texture from an image file. - * - * Loads the texture data from the specified file path. The file must contain - * image data in a supported format (e.g., PNG, JPG). The texture will be ready - * for rendering after creation. - * - * @param path The file path to the texture image. - * @return A shared pointer to the created `NxTexture2D` instance. - * - * Example: - * ```cpp - * auto texture = NxTexture2D::create("assets/textures/brick_wall.png"); - * ``` - */ - static std::shared_ptr create(const std::string &path); + class NxTexture2D : public NxTexture { + public: + /** + * @brief Creates a blank 2D texture with the specified dimensions. + * + * Allocates a texture resource with the given width and height. The texture + * will have no initial data and can be updated later with `setData`. + * + * @param width The width of the texture in pixels. + * @param height The height of the texture in pixels. + * @return A shared pointer to the created `NxTexture2D` instance. + * + * Example: + * ```cpp + * auto blankTexture = NxTexture2D::create(512, 512); + * ``` + */ + static std::shared_ptr create(unsigned int width, unsigned int height); + + /** + * @brief Creates a 2D texture from raw pixel data in memory. + * + * Creates a texture from a raw pixel buffer with the specified dimensions and format. + * This is useful when you have direct access to pixel data that wasn't loaded through + * compressed images files or when you want to create textures from procedurally generated data. + * + * @param buffer Pointer to the raw pixel data. The buffer should contain pixel data + * in a format that matches the specified NxTextureFormat. The data consists + * of height scanlines of width pixels, with each pixel consisting of N components + * (where N depends on the format). The first pixel pointed to is bottom-left-most + * in the image. There is no padding between image scanlines or between pixels. + * Each component is an 8-bit unsigned value (uint8_t). + * @param width The width of the texture in pixels. + * @param height The height of the texture in pixels. + * @param format The format of the pixel data, which determines the number of components + * per pixel. + * @return A shared pointer to the created NxTexture2D instance. + * + * Example: + * ```cpp + * // Create a 128x128 RGBA texture with custom data (4 components (RGBA)) + * std::vector pixelData(128 * 128 * 4); + * // Fill pixelData with your custom values... + * auto texture = NxTexture2D::create(pixelData.data(), 128, 128, NxTextureFormat::RGBA8); + * ``` + */ + static std::shared_ptr create(const uint8_t *buffer, unsigned int width, unsigned int height, + NxTextureFormat format); + + /** + * @brief Creates a 2D texture from file in memory. + * + * Loads the texture data from the specified memory buffer. The buffer must contain + * image data in a supported format (e.g., PNG, JPG). The texture will be ready + * for rendering after creation. + * + * @param buffer The memory buffer containing the texture image data. + * @param len The length of the memory buffer in bytes. + * @return A shared pointer to the created `NxTexture2D` instance. + * + * Example: + * ```cpp + * // Load image data into a buffer + * std::vector imageData = ...; + * auto texture = NxTexture2D::create(imageData.data(), imageData.size()); + * ``` + */ + static std::shared_ptr create(const uint8_t *buffer, unsigned int len); + + /** + * @brief Creates a 2D texture from an image file. + * + * Loads the texture data from the specified file path. The file must contain + * image data in a supported format (e.g., PNG, JPG). The texture will be ready + * for rendering after creation. + * + * @param path The file path to the texture image. + * @return A shared pointer to the created `NxTexture2D` instance. + * + * Example: + * ```cpp + * auto texture = NxTexture2D::create("assets/textures/brick_wall.png"); + * ``` + */ + static std::shared_ptr create(const std::string &path); }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/UniformCache.cpp b/engine/src/renderer/UniformCache.cpp index c472fcd63..b9065947b 100644 --- a/engine/src/renderer/UniformCache.cpp +++ b/engine/src/renderer/UniformCache.cpp @@ -14,13 +14,16 @@ #include "UniformCache.hpp" +#include + namespace nexo::renderer { void UniformCache::setFloat(const std::string& name, float value) { const auto it = m_values.find(name); - if (it == m_values.end() || !std::holds_alternative(it->second) || std::get(it->second) != value) { - m_values[name] = value; + if (it == m_values.end() || !std::holds_alternative(it->second) || + std::get(it->second) != value) { + m_values[name] = value; m_dirtyFlags[name] = true; } } @@ -28,8 +31,9 @@ namespace nexo::renderer { void UniformCache::setFloat2(const std::string& name, const glm::vec2& value) { const auto it = m_values.find(name); - if (it == m_values.end() || !std::holds_alternative(it->second) || std::get(it->second) != value) { - m_values[name] = value; + if (it == m_values.end() || !std::holds_alternative(it->second) || + std::get(it->second) != value) { + m_values[name] = value; m_dirtyFlags[name] = true; } } @@ -37,8 +41,9 @@ namespace nexo::renderer { void UniformCache::setFloat3(const std::string& name, const glm::vec3& value) { const auto it = m_values.find(name); - if (it == m_values.end() || !std::holds_alternative(it->second) || std::get(it->second) != value) { - m_values[name] = value; + if (it == m_values.end() || !std::holds_alternative(it->second) || + std::get(it->second) != value) { + m_values[name] = value; m_dirtyFlags[name] = true; } } @@ -46,8 +51,9 @@ namespace nexo::renderer { void UniformCache::setFloat4(const std::string& name, const glm::vec4& value) { const auto it = m_values.find(name); - if (it == m_values.end() || !std::holds_alternative(it->second) || std::get(it->second) != value) { - m_values[name] = value; + if (it == m_values.end() || !std::holds_alternative(it->second) || + std::get(it->second) != value) { + m_values[name] = value; m_dirtyFlags[name] = true; } } @@ -56,7 +62,7 @@ namespace nexo::renderer { { const auto it = m_values.find(name); if (it == m_values.end() || !std::holds_alternative(it->second) || std::get(it->second) != value) { - m_values[name] = value; + m_values[name] = value; m_dirtyFlags[name] = true; } } @@ -65,7 +71,7 @@ namespace nexo::renderer { { const auto it = m_values.find(name); if (it == m_values.end() || !std::holds_alternative(it->second) || std::get(it->second) != value) { - m_values[name] = value; + m_values[name] = value; m_dirtyFlags[name] = true; } } @@ -73,8 +79,9 @@ namespace nexo::renderer { void UniformCache::setMatrix(const std::string& name, const glm::mat4& value) { const auto it = m_values.find(name); - if (it == m_values.end() || !std::holds_alternative(it->second) || std::get(it->second) != value) { - m_values[name] = value; + if (it == m_values.end() || !std::holds_alternative(it->second) || + std::get(it->second) != value) { + m_values[name] = value; m_dirtyFlags[name] = true; } } @@ -103,8 +110,8 @@ namespace nexo::renderer { void UniformCache::clearAllDirtyFlags() { - for (auto& [name, dirty] : m_dirtyFlags) { + for (auto& dirty : m_dirtyFlags | std::views::values) { dirty = false; } } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/UniformCache.hpp b/engine/src/renderer/UniformCache.hpp index 2a4d6fcbf..3b275f640 100644 --- a/engine/src/renderer/UniformCache.hpp +++ b/engine/src/renderer/UniformCache.hpp @@ -13,43 +13,127 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include +#include #include #include #include -#include -#include namespace nexo::renderer { - using UniformValue = std::variant< - float, - glm::vec2, - glm::vec3, - glm::vec4, - int, - bool, - glm::mat4 - >; + using UniformValue = std::variant; class UniformCache { - public: + public: + /** + * @brief Sets a float uniform value. + * + * @param name The name of the uniform variable. + * @param value The float value to set. + * + * If the value is different from the current cached value, it updates the cache and marks it as dirty. + */ void setFloat(const std::string& name, float value); + + /** + * @brief Sets a 2D vector uniform value. + * + * @param name The name of the uniform variable. + * @param value The vec2 value to set. + * + * If the value is different from the current cached value, it updates the cache and marks it as dirty. + */ void setFloat2(const std::string& name, const glm::vec2& value); + + /** + * @brief Sets a 3D vector uniform value. + * + * @param name The name of the uniform variable. + * @param value The vec3 value to set. + * + * If the value is different from the current cached value, it updates the cache and marks it as dirty. + */ void setFloat3(const std::string& name, const glm::vec3& value); + + /** + * @brief Sets a 4D vector uniform value. + * + * @param name The name of the uniform variable. + * @param value The vec4 value to set. + * + * If the value is different from the current cached value, it updates the cache and marks it as dirty. + */ void setFloat4(const std::string& name, const glm::vec4& value); + + /** + * @brief Sets an integer uniform value. + * + * @param name The name of the uniform variable. + * @param value The integer value to set. + * + * If the value is different from the current cached value, it updates the cache and marks it as dirty. + */ void setInt(const std::string& name, int value); + + /** + * @brief Sets a boolean uniform value. + * + * @param name The name of the uniform variable. + * @param value The boolean value to set. + * + * If the value is different from the current cached value, it updates the cache and marks it as dirty. + */ void setBool(const std::string& name, bool value); + + /** + * @brief Sets a 4x4 matrix uniform value. + * + * @param name The name of the uniform variable. + * @param value The mat4 value to set. + * + * If the value is different from the current cached value, it updates the cache and marks it as dirty. + */ void setMatrix(const std::string& name, const glm::mat4& value); - bool isDirty(const std::string& name) const; - bool hasValue(const std::string& name) const; - std::optional getValue(const std::string& name) const; + /** + * @brief Checks if a uniform value is marked as dirty. + * + * @param name The name of the uniform variable. + * @return true if the uniform is dirty, false otherwise. + */ + [[nodiscard]] bool isDirty(const std::string& name) const; + + /** + * @brief Checks if a uniform value exists in the cache. + * + * @param name The name of the uniform variable. + * @return true if the uniform exists in the cache, false otherwise. + */ + [[nodiscard]] bool hasValue(const std::string& name) const; + + /** + * @brief Retrieves a uniform value from the cache. + * + * @param name The name of the uniform variable. + * @return The uniform value if it exists, std::nullopt otherwise. + */ + [[nodiscard]] std::optional getValue(const std::string& name) const; + + /** + * @brief Clears the dirty flag for a specific uniform. + * + * @param name The name of the uniform variable. + */ void clearDirtyFlag(const std::string& name); + + /** + * @brief Clears all dirty flags in the cache. + */ void clearAllDirtyFlags(); - private: + private: std::unordered_map m_values; std::unordered_map m_dirtyFlags; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/VertexArray.cpp b/engine/src/renderer/VertexArray.cpp index 3573ee48b..1a85f1350 100644 --- a/engine/src/renderer/VertexArray.cpp +++ b/engine/src/renderer/VertexArray.cpp @@ -21,11 +21,11 @@ namespace nexo::renderer { std::shared_ptr createVertexArray() { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/VertexArray.hpp b/engine/src/renderer/VertexArray.hpp index a0efef9bc..b2164c481 100644 --- a/engine/src/renderer/VertexArray.hpp +++ b/engine/src/renderer/VertexArray.hpp @@ -18,44 +18,131 @@ namespace nexo::renderer { /** - * @class NxVertexArray - * @brief Abstract class representing a vertex array in the rendering system. - * - * The `NxVertexArray` class manages the collection of vertex buffers and an optional - * index buffer. It provides the interface for binding, unbinding, and configuring - * vertex attributes in the rendering pipeline. - * - * Responsibilities: - * - Manage vertex buffers and index buffers. - * - Bind/unbind the vertex array for rendering. - * - Provide access to underlying buffers. - * - * Derived classes (e.g., `NxOpenGlVertexArray`) implement platform-specific behavior - * for managing vertex arrays. - */ + * @class NxVertexArray + * @brief Abstract class representing a vertex array in the rendering system. + * + * The `NxVertexArray` class manages the collection of vertex buffers and an optional + * index buffer. It provides the interface for binding, unbinding, and configuring + * vertex attributes in the rendering pipeline. + * + * Responsibilities: + * - Manage vertex buffers and index buffers. + * - Bind/unbind the vertex array for rendering. + * - Provide access to underlying buffers. + * + * Derived classes (e.g., `NxOpenGlVertexArray`) implement platform-specific behavior + * for managing vertex arrays. + */ class NxVertexArray { - public: - virtual ~NxVertexArray() = default; + public: + virtual ~NxVertexArray() = default; - virtual void bind() const = 0; - virtual void unbind() const = 0; + /** + * @brief Binds the vertex array, making it active for rendering. + * + * This method binds the vertex array object (VAO) to the current graphics context, + * allowing subsequent draw calls to use the vertex and index buffers associated + * with this vertex array. + * + * Throws: + * - Implementation-specific exceptions if binding fails. + * + * Notes: + * - The actual implementation of this method is platform-specific and should be + * provided by derived classes. + */ + virtual void bind() const = 0; - virtual void addVertexBuffer(const std::shared_ptr &vertexBuffer) = 0; - virtual void setIndexBuffer(const std::shared_ptr &indexBuffer) = 0; + /** + * @brief Unbinds the vertex array, detaching it from the current context. + * + * This method unbinds the vertex array object (VAO) from the current graphics context, + * effectively disabling its use for subsequent draw calls. This is useful for ensuring + * that no unintended modifications are made to the vertex array state. + * + * Notes: + * - The actual implementation of this method is platform-specific and should be + * provided by derived classes. + */ + virtual void unbind() const = 0; - [[nodiscard]] virtual const std::vector> &getVertexBuffers() const = 0; - [[nodiscard]] virtual const std::shared_ptr &getIndexBuffer() const = 0; + /** + * @brief Adds a vertex buffer to the vertex array. + * + * This method associates a vertex buffer with the vertex array, allowing it to be + * used for rendering. The vertex buffer contains vertex attribute data such as + * positions, normals, texture coordinates, etc. + * + * @param vertexBuffer A shared pointer to the `NxVertexBuffer` to be added. + * + * Throws: + * - Implementation-specific exceptions if adding the vertex buffer fails. + * + * Notes: + * - The actual implementation of this method is platform-specific and should be + * provided by derived classes. + */ + virtual void addVertexBuffer(const std::shared_ptr &vertexBuffer) = 0; - [[nodiscard]] virtual unsigned int getId() const = 0; + /** + * @brief Sets the index buffer for the vertex array. + * + * This method associates an index buffer with the vertex array, allowing indexed + * drawing operations. The index buffer contains indices that reference vertices + * in the vertex buffers, enabling efficient reuse of vertex data. + * + * @param indexBuffer A shared pointer to the `NxIndexBuffer` to be set. + * + * Throws: + * - Implementation-specific exceptions if setting the index buffer fails. + * + * Notes: + * - The actual implementation of this method is platform-specific and should be + * provided by derived classes. + */ + virtual void setIndexBuffer(const std::shared_ptr &indexBuffer) = 0; + + /** + * @brief Retrieves the list of vertex buffers associated with the vertex array. + * + * This method returns a constant reference to a vector containing shared pointers + * to all vertex buffers that have been added to the vertex array. This allows + * inspection of the vertex buffers for debugging or configuration purposes. + * + * @return A constant reference to a vector of shared pointers to `NxVertexBuffer` instances. + */ + [[nodiscard]] virtual const std::vector> &getVertexBuffers() const = 0; + + /** + * @brief Retrieves the index buffer associated with the vertex array. + * + * This method returns a constant reference to the shared pointer of the index buffer + * that has been set for the vertex array. If no index buffer has been set, it may + * return a null pointer. + * + * @return A constant reference to the shared pointer of the `NxIndexBuffer` instance. + */ + [[nodiscard]] virtual const std::shared_ptr &getIndexBuffer() const = 0; + + /** + * @brief Retrieves the unique identifier of the vertex array. + * + * This method returns an unsigned integer that uniquely identifies the vertex array + * within the graphics context. This ID is typically used for debugging or low-level + * graphics operations. + * + * @return The unique ID of the vertex array. + */ + [[nodiscard]] virtual unsigned int getId() const = 0; }; /** - * @brief Factory function to create a platform-specific vertex array object. - * - * Depending on the graphics API (e.g., OpenGL), this function creates an instance - * of the corresponding `NxVertexArray` implementation. - * - * @return A shared pointer to the created `NxVertexArray` instance. - */ + * @brief Factory function to create a platform-specific vertex array object. + * + * Depending on the graphics API (e.g., OpenGL), this function creates an instance + * of the corresponding `NxVertexArray` implementation. + * + * @return A shared pointer to the created `NxVertexArray` instance. + */ std::shared_ptr createVertexArray(); -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Window.cpp b/engine/src/renderer/Window.cpp index 2a77946ab..de5632e68 100644 --- a/engine/src/renderer/Window.cpp +++ b/engine/src/renderer/Window.cpp @@ -22,11 +22,11 @@ namespace nexo::renderer { std::shared_ptr NxWindow::create(int width, int height, const std::string &title) { - #ifdef NX_GRAPHICS_API_OPENGL - return std::make_shared(width, height, title); - #else - THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - #endif +#ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(width, height, title); +#else + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); +#endif } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/Window.hpp b/engine/src/renderer/Window.hpp index 1f1de5f8e..5d0762c78 100644 --- a/engine/src/renderer/Window.hpp +++ b/engine/src/renderer/Window.hpp @@ -15,8 +15,8 @@ #pragma once #include -#include #include +#include #include #define GLFW_INCLUDE_NONE #include @@ -24,20 +24,19 @@ namespace nexo::renderer { - using ResizeCallback = std::function; - using CloseCallback = std::function; - using KeyCallback = std::function; - using MouseClickCallback = std::function; + using ResizeCallback = std::function; + using CloseCallback = std::function; + using KeyCallback = std::function; + using MouseClickCallback = std::function; using MouseScrollCallback = std::function; - using MouseMoveCallback = std::function; - using FileDropCallback = std::function; + using MouseMoveCallback = std::function; + using FileDropCallback = std::function; - struct NxWindowProperty - { + struct NxWindowProperty { unsigned int width; unsigned int height; std::string title; - bool vsync = true; + bool vsync = true; bool isDarkMode = false; ResizeCallback resizeCallback; @@ -48,83 +47,203 @@ namespace nexo::renderer { MouseMoveCallback mouseMoveCallback; FileDropCallback fileDropCallback; - NxWindowProperty(const unsigned int w, const unsigned h, const std::string &t) : width(w), height(h), title(t) {} + NxWindowProperty(const unsigned int w, const unsigned h, std::string t) + : width(w), height(h), title(std::move(t)) + {} }; /** - * @class NxWindow - * @brief Abstract class for managing window operations in the rendering system. - * - * The `NxWindow` class provides an interface for creating, configuring, and - * managing a window. It includes support for events like resizing, closing, - * keyboard input, and mouse interactions. - * - * Responsibilities: - * - Initialize and manage the window lifecycle. - * - Handle window properties such as size, title, and VSync. - * - Provide event handling through callbacks. - * - * Derived classes (e.g., `NxOpenGlWindow`) implement platform-specific behavior - * for managing windows. - */ + * @class NxWindow + * @brief Abstract class for managing window operations in the rendering system. + * + * The `NxWindow` class provides an interface for creating, configuring, and + * managing a window. It includes support for events like resizing, closing, + * keyboard input, and mouse interactions. + * + * Responsibilities: + * - Initialize and manage the window lifecycle. + * - Handle window properties such as size, title, and VSync. + * - Provide event handling through callbacks. + * + * Derived classes (e.g., `NxOpenGlWindow`) implement platform-specific behavior + * for managing windows. + */ class NxWindow { - public: - NxWindow() = default; - - virtual ~NxWindow() = default; - - virtual void init() = 0; - virtual void shutdown() = 0; - virtual void onUpdate() = 0; - - [[nodiscard]] virtual unsigned int getWidth() const = 0; - [[nodiscard]] virtual unsigned int getHeight() const = 0; - - virtual void getDpiScale(float *x, float *y) const = 0; - - virtual void setWindowIcon(const std::filesystem::path& iconPath) = 0; - - virtual void setTitle(const std::string& title) = 0; - [[nodiscard]] virtual const std::string& getTitle() const = 0; - - virtual void setDarkMode(bool enabled) = 0; - [[nodiscard]] virtual bool isDarkMode() const = 0; - - virtual void setVsync(bool enabled) = 0; - [[nodiscard]] virtual bool isVsync() const = 0; - - - [[nodiscard]] virtual bool isOpen() const = 0; - virtual void close() = 0; - - [[nodiscard]] virtual void *window() const = 0; - - /** - * @brief Factory function to create a platform-specific window. - * - * Depending on the graphics API (e.g., OpenGL), this function creates an - * instance of the corresponding `NxWindow` implementation. - * - * @param width Initial width of the window. - * @param height Initial height of the window. - * @param title Title of the window. - * @return A shared pointer to the created `NxWindow` instance. - */ - static std::shared_ptr create(int width = 1920, int height = 1080, const std::string &title = "Nexo window"); - - virtual void setErrorCallback(void *fctPtr) = 0; - virtual void setResizeCallback(ResizeCallback callback) = 0; - virtual void setCloseCallback(CloseCallback callback) = 0; - virtual void setKeyCallback(KeyCallback callback) = 0; - virtual void setMouseClickCallback(MouseClickCallback callback) = 0; - virtual void setMouseScrollCallback(MouseScrollCallback callback) = 0; - virtual void setMouseMoveCallback(MouseMoveCallback callback) = 0; - virtual void setFileDropCallback(FileDropCallback callback) = 0; - - // Linux specific methods + public: + NxWindow() = default; + + virtual ~NxWindow() = default; + + /** + * @brief Initializes the window with specified properties. + * + * This method sets up the window with the desired width, height, title, + * and other properties. It must be called before using the window for + * rendering or event handling. + * + * Throws: + * - Implementation-specific exceptions if initialization fails. + * + * Notes: + * - The actual implementation of this method is platform-specific and should be + * provided by derived classes. + */ + virtual void init() = 0; + + /** + * @brief Cleans up resources and shuts down the window. + */ + virtual void shutdown() = 0; + + /** + * @brief Updates the window state. Should be called each frame. + */ + virtual void onUpdate() = 0; + + /** + * @brief Gets the current width of the window. + * @return The window width in pixels. + */ + [[nodiscard]] virtual unsigned int getWidth() const = 0; + + /** + * @brief Gets the current height of the window. + * @return The window height in pixels. + */ + [[nodiscard]] virtual unsigned int getHeight() const = 0; + + /** + * @brief Gets the DPI scaling factors for the window. + * @param x Pointer to store the horizontal DPI scale. + * @param y Pointer to store the vertical DPI scale. + */ + virtual void getDpiScale(float *x, float *y) const = 0; + + /** + * @brief Sets the window icon from an image file. + * @param iconPath Path to the icon image file. + */ + virtual void setWindowIcon(const std::filesystem::path &iconPath) = 0; + + /** + * @brief Sets the window title. + * @param title The new window title. + */ + virtual void setTitle(const std::string &title) = 0; + + /** + * @brief Gets the current window title. + * @return The window title string. + */ + [[nodiscard]] virtual const std::string &getTitle() const = 0; + + /** + * @brief Enables or disables dark mode for the window. + * @param enabled True to enable dark mode, false to disable. + */ + virtual void setDarkMode(bool enabled) = 0; + + /** + * @brief Checks if dark mode is enabled. + * @return True if dark mode is enabled, false otherwise. + */ + [[nodiscard]] virtual bool isDarkMode() const = 0; + + /** + * @brief Enables or disables vertical synchronization. + * @param enabled True to enable VSync, false to disable. + */ + virtual void setVsync(bool enabled) = 0; + + /** + * @brief Checks if VSync is enabled. + * @return True if VSync is enabled, false otherwise. + */ + [[nodiscard]] virtual bool isVsync() const = 0; + + /** + * @brief Checks if the window is currently open. + * @return True if the window is open, false otherwise. + */ + [[nodiscard]] virtual bool isOpen() const = 0; + + /** + * @brief Closes the window. + */ + virtual void close() = 0; + + /** + * @brief Gets the native window handle. + * @return A pointer to the native window implementation. + */ + [[nodiscard]] virtual void *window() const = 0; + + /** + /** + * @brief Factory function to create a platform-specific window. + * + * Depending on the graphics API (e.g., OpenGL), this function creates an + * instance of the corresponding `NxWindow` implementation. + * + * @param width Initial width of the window. + * @param height Initial height of the window. + * @param title Title of the window. + * @return A shared pointer to the created `NxWindow` instance. + */ + static std::shared_ptr create(int width = 1920, int height = 1080, + const std::string &title = "Nexo window"); + + /** + * @brief Sets the callback for error handling. + * @param fctPtr Pointer to the error callback function. + */ + virtual void setErrorCallback(void *fctPtr) = 0; + + /** + * @brief Sets the callback for window resize events. + * @param callback Function called when the window is resized (width, height). + */ + virtual void setResizeCallback(ResizeCallback callback) = 0; + + /** + * @brief Sets the callback for window close events. + * @param callback Function called when the window is closed. + */ + virtual void setCloseCallback(CloseCallback callback) = 0; + + /** + * @brief Sets the callback for keyboard events. + * @param callback Function called when a key is pressed/released (key, scancode, action). + */ + virtual void setKeyCallback(KeyCallback callback) = 0; + + /** + * @brief Sets the callback for mouse button events. + * @param callback Function called when a mouse button is pressed/released (button, action, mods). + */ + virtual void setMouseClickCallback(MouseClickCallback callback) = 0; + + /** + * @brief Sets the callback for mouse scroll events. + * @param callback Function called when mouse wheel is scrolled (xoffset, yoffset). + */ + virtual void setMouseScrollCallback(MouseScrollCallback callback) = 0; + + /** + * @brief Sets the callback for mouse movement events. + * @param callback Function called when mouse position changes (xpos, ypos). + */ + virtual void setMouseMoveCallback(MouseMoveCallback callback) = 0; + + /** + * @brief Sets the callback for file drop events. + * @param callback Function called when files are dropped onto the window (count, paths). + */ + virtual void setFileDropCallback(FileDropCallback callback) = 0; + // Linux specific methods #ifdef __linux__ - virtual void setWaylandAppId(const char *appId) = 0; - virtual void setWmClass(const char *className, const char *instanceName) = 0; + virtual void setWaylandAppId(const char *appId) = 0; + virtual void setWmClass(const char *className, const char *instanceName) = 0; #endif }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlBuffer.cpp b/engine/src/renderer/opengl/OpenGlBuffer.cpp index 0fad5c762..afd13b8a2 100644 --- a/engine/src/renderer/opengl/OpenGlBuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlBuffer.cpp @@ -48,13 +48,12 @@ namespace nexo::renderer { glBindBuffer(GL_ARRAY_BUFFER, 0); } - void NxOpenGlVertexBuffer::setData(void *data, size_t size) + void NxOpenGlVertexBuffer::setData(void *data, const size_t size) { glBindBuffer(GL_ARRAY_BUFFER, _id); - glBufferSubData(GL_ARRAY_BUFFER, 0, size, data); + glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(size), data); } - // INDEX BUFFER NxOpenGlIndexBuffer::NxOpenGlIndexBuffer() @@ -81,11 +80,12 @@ namespace nexo::renderer { void NxOpenGlIndexBuffer::setData(unsigned int *indices, const size_t count) { _count = count; - glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(unsigned int), indices, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast(count * sizeof(unsigned int)), indices, + GL_STATIC_DRAW); } size_t NxOpenGlIndexBuffer::getCount() const { return _count; } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlBuffer.hpp b/engine/src/renderer/opengl/OpenGlBuffer.hpp index b87ab4e65..fb58d739b 100644 --- a/engine/src/renderer/opengl/OpenGlBuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlBuffer.hpp @@ -19,184 +19,242 @@ namespace nexo::renderer { class NxOpenGlVertexBuffer final : public NxVertexBuffer { - public: - /** - * @brief Constructs a new vertex buffer and initializes it with vertex data. - * - * This constructor creates a new OpenGL vertex buffer, allocates GPU memory, and - * uploads the provided vertex data to the buffer. - * - * @param vertices Pointer to the array of vertex data to initialize the buffer with. - * @param size The size (in bytes) of the vertex data to upload. - * - * OpenGL Calls: - * - `glGenBuffers`: Generates a new buffer object. - * - `glBindBuffer`: Binds the buffer as the current vertex buffer (GL_ARRAY_BUFFER). - * - `glBufferData`: Allocates GPU memory and uploads the vertex data. - */ - NxOpenGlVertexBuffer(const float *vertices, unsigned int size); - - /** - * @brief Constructs an empty vertex buffer with a specified size. - * - * This constructor creates a new OpenGL vertex buffer and allocates GPU memory - * without uploading any data. The buffer is intended for dynamic updates. - * - * @param size The size (in bytes) of the buffer to allocate. - * - * OpenGL Calls: - * - `glGenBuffers`: Generates a new buffer object. - * - `glBindBuffer`: Binds the buffer as the current vertex buffer (GL_ARRAY_BUFFER). - * - `glBufferData`: Allocates GPU memory with a `nullptr` for data. - * - * Usage: - * - Call `setData` later to populate the buffer with vertex data dynamically. - */ - explicit NxOpenGlVertexBuffer(unsigned int size); - - /** - * @brief Destroys the vertex buffer and releases GPU resources. - * - * This destructor ensures that the OpenGL buffer is deleted, freeing GPU memory. - * - * OpenGL Calls: - * - `glDeleteBuffers`: Deletes the buffer object associated with the buffer ID. - */ - ~NxOpenGlVertexBuffer() override; - - /** - * @brief Binds the vertex buffer as the active buffer in the OpenGL context. - * - * OpenGL Calls: - * - `glBindBuffer`: Binds the buffer as the current vertex buffer (GL_ARRAY_BUFFER). - * - * Usage: - * - Binding the vertex buffer ensures that subsequent OpenGL calls will operate - * on this buffer. - */ - void bind() const override; - - /** - * @brief Unbinds the current vertex buffer from the OpenGL context. - * - * OpenGL Calls: - * - `glBindBuffer`: Unbinds any currently bound vertex buffer by setting the - * active buffer to 0. - * - * Usage: - * - This is optional but ensures no unintended operations are performed on the buffer. - */ - void unbind() const override; - - void setLayout(const NxBufferLayout &layout) override { _layout = layout; }; - [[nodiscard]] NxBufferLayout getLayout() const override { return _layout; }; - - /** - * @brief Updates the data in the vertex buffer. - * - * This method replaces the content of the vertex buffer with new data. The data - * must fit within the allocated buffer size. - * - * @param data Pointer to the new vertex data to upload. - * @param size The size (in bytes) of the data to upload. - * - * OpenGL Calls: - * - `glBindBuffer`: Binds the buffer as the current vertex buffer (GL_ARRAY_BUFFER). - * - `glBufferSubData`: Updates a subset of the buffer's data. - * - * Usage: - * - Use this method for dynamically updating buffer content. - */ - void setData(void *data, size_t size) override; - - [[nodiscard]] unsigned int getId() const override { return _id; }; - - private: - unsigned int _id{}; - NxBufferLayout _layout; + public: + /** + * @brief Constructs a new vertex buffer and initializes it with vertex data. + * + * This constructor creates a new OpenGL vertex buffer, allocates GPU memory, and + * uploads the provided vertex data to the buffer. + * + * @param vertices Pointer to the array of vertex data to initialize the buffer with. + * @param size The size (in bytes) of the vertex data to upload. + * + * OpenGL Calls: + * - `glGenBuffers`: Generates a new buffer object. + * - `glBindBuffer`: Binds the buffer as the current vertex buffer (GL_ARRAY_BUFFER). + * - `glBufferData`: Allocates GPU memory and uploads the vertex data. + */ + NxOpenGlVertexBuffer(const float *vertices, unsigned int size); + + /** + * @brief Constructs an empty vertex buffer with a specified size. + * + * This constructor creates a new OpenGL vertex buffer and allocates GPU memory + * without uploading any data. The buffer is intended for dynamic updates. + * + * @param size The size (in bytes) of the buffer to allocate. + * + * OpenGL Calls: + * - `glGenBuffers`: Generates a new buffer object. + * - `glBindBuffer`: Binds the buffer as the current vertex buffer (GL_ARRAY_BUFFER). + * - `glBufferData`: Allocates GPU memory with a `nullptr` for data. + * + * Usage: + * - Call `setData` later to populate the buffer with vertex data dynamically. + */ + explicit NxOpenGlVertexBuffer(unsigned int size); + + /** + * @brief Destroys the vertex buffer and releases GPU resources. + * + * This destructor ensures that the OpenGL buffer is deleted, freeing GPU memory. + * + * OpenGL Calls: + * - `glDeleteBuffers`: Deletes the buffer object associated with the buffer ID. + */ + ~NxOpenGlVertexBuffer() override; + + /** + * @brief Binds the vertex buffer as the active buffer in the OpenGL context. + * + * OpenGL Calls: + * - `glBindBuffer`: Binds the buffer as the current vertex buffer (GL_ARRAY_BUFFER). + * + * Usage: + * - Binding the vertex buffer ensures that subsequent OpenGL calls will operate + * on this buffer. + */ + void bind() const override; + + /** + * @brief Unbinds the current vertex buffer from the OpenGL context. + * + * OpenGL Calls: + * - `glBindBuffer`: Unbinds any currently bound vertex buffer by setting the + * active buffer to 0. + * + * Usage: + * - This is optional but ensures no unintended operations are performed on the buffer. + */ + void unbind() const override; + + /** + * @brief Sets the layout of the vertex buffer. + * + * This method defines the layout of the vertex data stored in the buffer. + * The layout specifies how vertex attributes are organized, including their + * types, sizes, and offsets. + * + * @param layout The `NxBufferLayout` object that describes the vertex data layout. + * + * Usage: + * - Call this method after creating the buffer and before rendering to ensure + * correct interpretation of vertex data. + */ + void setLayout(const NxBufferLayout &layout) override + { + _layout = layout; + }; + + /** + * @brief Retrieves the layout of the vertex buffer. + * + * This method returns the layout information that describes the structure of + * the vertex data stored in the buffer. + * + * @return The `NxBufferLayout` object representing the vertex data layout. + * + * Usage: + * - Use this information to configure vertex attribute pointers before rendering. + */ + [[nodiscard]] NxBufferLayout getLayout() const override + { + return _layout; + }; + + /** + * @brief Updates the data in the vertex buffer. + * + * This method replaces the content of the vertex buffer with new data. The data + * must fit within the allocated buffer size. + * + * @param data Pointer to the new vertex data to upload. + * @param size The size (in bytes) of the data to upload. + * + * OpenGL Calls: + * - `glBindBuffer`: Binds the buffer as the current vertex buffer (GL_ARRAY_BUFFER). + * - `glBufferSubData`: Updates a subset of the buffer's data. + * + * Usage: + * - Use this method for dynamically updating buffer content. + */ + void setData(void *data, size_t size) override; + + /** + * @brief Retrieves the OpenGL buffer ID. + * + * This method returns the unique identifier assigned by OpenGL to this vertex buffer. + * + * @return The OpenGL buffer ID as an unsigned integer. + * + * Usage: + * - Use this ID for advanced OpenGL operations that require direct buffer access. + */ + [[nodiscard]] unsigned int getId() const override + { + return _id; + }; + + private: + unsigned int _id{}; + NxBufferLayout _layout; }; class NxOpenGlIndexBuffer final : public NxIndexBuffer { - public: - /** - * @brief Constructs a new OpenGL index buffer. - * - * This constructor creates a new index buffer and allocates GPU memory. The - * buffer is initially empty and must be populated with index data using `setData`. - * - * OpenGL Calls: - * - `glGenBuffers`: Generates a new buffer object. - * - `glBindBuffer`: Binds the buffer as the current index buffer (GL_ELEMENT_ARRAY_BUFFER). - */ - NxOpenGlIndexBuffer(); - - /** - * @brief Destroys the index buffer and releases GPU resources. - * - * This destructor ensures that the OpenGL buffer is deleted, freeing GPU memory. - * - * OpenGL Calls: - * - `glDeleteBuffers`: Deletes the buffer object associated with the buffer ID. - */ - ~NxOpenGlIndexBuffer() override; - - /** - * @brief Binds the index buffer as the active buffer in the OpenGL context. - * - * OpenGL Calls: - * - `glBindBuffer`: Binds the buffer as the current index buffer (GL_ELEMENT_ARRAY_BUFFER). - * - * Usage: - * - Binding the index buffer ensures that subsequent OpenGL draw calls will use - * this buffer for rendering indexed primitives. - */ - void bind() const override; - /** - * @brief Unbinds the current index buffer from the OpenGL context. - * - * OpenGL Calls: - * - `glBindBuffer`: Unbinds any currently bound index buffer by setting the - * active buffer to 0. - * - * Usage: - * - This is optional but ensures no unintended operations are performed on the buffer. - */ - void unbind() const override; - - /** - * @brief Uploads index data to the index buffer. - * - * This method populates the buffer with an array of indices, which are used - * for rendering indexed primitives. The buffer must have sufficient memory - * allocated to store the indices. - * - * @param indices Pointer to the array of indices to upload. - * @param count The number of indices to upload. - * - * OpenGL Calls: - * - `glBufferData`: Allocates GPU memory and uploads the index data. - * - * Notes: - * - Sets the `_count` member to track the number of indices in the buffer. - */ - void setData(unsigned int *indices, size_t count) override; - - /** - * @brief Retrieves the number of indices in the buffer. - * - * This method returns the number of indices currently stored in the index buffer. - * - * @return The count of indices in the buffer. - * - * Usage: - * - Use this value for rendering indexed primitives. - */ - [[nodiscard]] size_t getCount() const override; - - [[nodiscard]] unsigned int getId() const override { return _id; }; - private: - unsigned int _id{}; - size_t _count = 0; + public: + /** + * @brief Constructs a new OpenGL index buffer. + * + * This constructor creates a new index buffer and allocates GPU memory. The + * buffer is initially empty and must be populated with index data using `setData`. + * + * OpenGL Calls: + * - `glGenBuffers`: Generates a new buffer object. + * - `glBindBuffer`: Binds the buffer as the current index buffer (GL_ELEMENT_ARRAY_BUFFER). + */ + NxOpenGlIndexBuffer(); + + /** + * @brief Destroys the index buffer and releases GPU resources. + * + * This destructor ensures that the OpenGL buffer is deleted, freeing GPU memory. + * + * OpenGL Calls: + * - `glDeleteBuffers`: Deletes the buffer object associated with the buffer ID. + */ + ~NxOpenGlIndexBuffer() override; + + /** + * @brief Binds the index buffer as the active buffer in the OpenGL context. + * + * OpenGL Calls: + * - `glBindBuffer`: Binds the buffer as the current index buffer (GL_ELEMENT_ARRAY_BUFFER). + * + * Usage: + * - Binding the index buffer ensures that subsequent OpenGL draw calls will use + * this buffer for rendering indexed primitives. + */ + void bind() const override; + /** + * @brief Unbinds the current index buffer from the OpenGL context. + * + * OpenGL Calls: + * - `glBindBuffer`: Unbinds any currently bound index buffer by setting the + * active buffer to 0. + * + * Usage: + * - This is optional but ensures no unintended operations are performed on the buffer. + */ + void unbind() const override; + + /** + * @brief Uploads index data to the index buffer. + * + * This method populates the buffer with an array of indices, which are used + * for rendering indexed primitives. The buffer must have sufficient memory + * allocated to store the indices. + * + * @param indices Pointer to the array of indices to upload. + * @param count The number of indices to upload. + * + * OpenGL Calls: + * - `glBufferData`: Allocates GPU memory and uploads the index data. + * + * Notes: + * - Sets the `_count` member to track the number of indices in the buffer. + */ + void setData(unsigned int *indices, size_t count) override; + + /** + * @brief Retrieves the number of indices in the buffer. + * + * This method returns the number of indices currently stored in the index buffer. + * + * @return The count of indices in the buffer. + * + * Usage: + * - Use this value for rendering indexed primitives. + */ + [[nodiscard]] size_t getCount() const override; + + /** + * @brief Retrieves the OpenGL buffer ID. + * + * This method returns the unique identifier assigned by OpenGL to this index buffer. + * + * @return The OpenGL buffer ID as an unsigned integer. + * + * Usage: + * - Use this ID for advanced OpenGL operations that require direct buffer access. + */ + [[nodiscard]] unsigned int getId() const override + { + return _id; + }; + + private: + unsigned int _id{}; + size_t _count = 0; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp index faec1c0e2..a53da2b8e 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp @@ -15,8 +15,8 @@ #include "OpenGlFramebuffer.hpp" #include "Logger.hpp" -#include #include +#include namespace nexo::renderer { @@ -34,7 +34,8 @@ namespace nexo::renderer { */ static int framebufferTextureFormatToOpenGlInternalFormat(NxFrameBufferTextureFormats format) { - constexpr GLenum internalFormats[] = {GL_NONE, GL_RGBA8, GL_RGBA16, GL_R32I, GL_DEPTH24_STENCIL8, GL_DEPTH24_STENCIL8}; + constexpr GLenum internalFormats[] = { + GL_NONE, GL_RGBA8, GL_RGBA16, GL_R32I, GL_DEPTH24_STENCIL8, GL_DEPTH24_STENCIL8}; if (static_cast(format) == 0 || format >= NxFrameBufferTextureFormats::NB_TEXTURE_FORMATS) return -1; return static_cast(internalFormats[static_cast(format)]); @@ -106,17 +107,18 @@ namespace nexo::renderer { * - Sets texture parameters for filtering and wrapping. * - Attaches the texture to the framebuffer using `glFramebufferTexture2D`. */ - static void attachColorTexture(const unsigned int id, const unsigned int samples, const GLenum internalFormat, const GLenum format, - const unsigned int width, const unsigned int height, const unsigned int index) + static void attachColorTexture(const unsigned int id, const unsigned int samples, const GLenum internalFormat, + const GLenum format, const unsigned int width, const unsigned int height, + const unsigned int index) { const bool multisample = samples > 1; if (multisample) - glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, static_cast(samples), static_cast(internalFormat), - static_cast(width), static_cast(height), GL_TRUE); - else - { - glTexImage2D(GL_TEXTURE_2D, 0, static_cast(internalFormat), static_cast(width), static_cast(height), - 0, static_cast(format), GL_UNSIGNED_BYTE, nullptr); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, static_cast(samples), + static_cast(internalFormat), static_cast(width), static_cast(height), + GL_TRUE); + else { + glTexImage2D(GL_TEXTURE_2D, 0, static_cast(internalFormat), static_cast(width), + static_cast(height), 0, static_cast(format), GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -153,8 +155,7 @@ namespace nexo::renderer { if (multisample) glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, static_cast(samples), format, static_cast(width), static_cast(height), GL_TRUE); - else - { + else { glTexStorage2D(GL_TEXTURE_2D, 1, format, static_cast(width), static_cast(height)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -178,10 +179,11 @@ namespace nexo::renderer { */ static bool isDepthFormat(const NxFrameBufferTextureFormats format) { - switch (format) - { - case NxFrameBufferTextureFormats::DEPTH24STENCIL8: return true; - default: return false; + switch (format) { + case NxFrameBufferTextureFormats::DEPTH24STENCIL8: + return true; + default: + return false; } } @@ -191,8 +193,7 @@ namespace nexo::renderer { THROW_EXCEPTION(NxFramebufferResizingFailed, "OPENGL", false, m_specs.width, m_specs.height); if (m_specs.width > sMaxFramebufferSize || m_specs.height > sMaxFramebufferSize) THROW_EXCEPTION(NxFramebufferResizingFailed, "OPENGL", true, m_specs.width, m_specs.height); - for (auto format: m_specs.attachments.attachments) - { + for (auto format : m_specs.attachments.attachments) { if (!isDepthFormat(format.textureFormat)) m_colorAttachmentsSpecs.emplace_back(format); else @@ -210,8 +211,7 @@ namespace nexo::renderer { void NxOpenGlFramebuffer::invalidate() { - if (m_id) - { + if (m_id) { glDeleteFramebuffers(1, &m_id); glDeleteTextures(static_cast(m_colorAttachments.size()), m_colorAttachments.data()); glDeleteTextures(1, &m_depthAttachment); @@ -225,45 +225,38 @@ namespace nexo::renderer { const bool multisample = m_specs.samples > 1; - if (!m_colorAttachmentsSpecs.empty()) - { + if (!m_colorAttachmentsSpecs.empty()) { m_colorAttachments.resize(m_colorAttachmentsSpecs.size()); // Fill the color attachments vector with open gl texture ids - createTextures(multisample, m_colorAttachments.data(), static_cast(m_colorAttachmentsSpecs.size())); + createTextures(multisample, m_colorAttachments.data(), + static_cast(m_colorAttachmentsSpecs.size())); - for (unsigned int i = 0; i < m_colorAttachments.size(); ++i) - { + for (unsigned int i = 0; i < m_colorAttachments.size(); ++i) { bindTexture(multisample, m_colorAttachments[i]); - const int glTextureInternalFormat = framebufferTextureFormatToOpenGlInternalFormat( - m_colorAttachmentsSpecs[i].textureFormat); - if (glTextureInternalFormat == -1) - THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); - const int glTextureFormat = framebufferTextureFormatToOpenGlFormat(m_colorAttachmentsSpecs[i].textureFormat); - if (glTextureFormat == -1) - THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); - attachColorTexture(m_colorAttachments[i], m_specs.samples, glTextureInternalFormat, glTextureFormat, m_specs.width, - m_specs.height, i); + const int glTextureInternalFormat = + framebufferTextureFormatToOpenGlInternalFormat(m_colorAttachmentsSpecs[i].textureFormat); + if (glTextureInternalFormat == -1) THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); + const int glTextureFormat = + framebufferTextureFormatToOpenGlFormat(m_colorAttachmentsSpecs[i].textureFormat); + if (glTextureFormat == -1) THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); + attachColorTexture(m_colorAttachments[i], m_specs.samples, glTextureInternalFormat, glTextureFormat, + m_specs.width, m_specs.height, i); } } - if (m_depthAttachmentSpec.textureFormat != NxFrameBufferTextureFormats::NONE) - { + if (m_depthAttachmentSpec.textureFormat != NxFrameBufferTextureFormats::NONE) { createTextures(multisample, &m_depthAttachment, 1); bindTexture(multisample, m_depthAttachment); int glDepthFormat = framebufferTextureFormatToOpenGlInternalFormat(m_depthAttachmentSpec.textureFormat); - if (glDepthFormat == -1) - THROW_EXCEPTION(NxFramebufferUnsupportedDepthFormat, "OPENGL"); + if (glDepthFormat == -1) THROW_EXCEPTION(NxFramebufferUnsupportedDepthFormat, "OPENGL"); attachDepthTexture(m_depthAttachment, m_specs.samples, glDepthFormat, GL_DEPTH_STENCIL_ATTACHMENT, m_specs.width, m_specs.height); } - if (m_colorAttachments.size() > 1) - { - if (m_colorAttachments.size() >= 4) - THROW_EXCEPTION(NxFramebufferCreationFailed, "OPENGL"); - constexpr GLenum buffers[4] = { - GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 - }; + if (m_colorAttachments.size() > 1) { + if (m_colorAttachments.size() >= 4) THROW_EXCEPTION(NxFramebufferCreationFailed, "OPENGL"); + constexpr GLenum buffers[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3}; glDrawBuffers(static_cast(m_colorAttachments.size()), buffers); } else if (m_colorAttachments.empty()) glDrawBuffer(GL_NONE); @@ -276,10 +269,9 @@ namespace nexo::renderer { void NxOpenGlFramebuffer::bind() { - if (toResize) - { - invalidate(); - toResize = false; + if (toResize) { + invalidate(); + toResize = false; } glBindFramebuffer(GL_FRAMEBUFFER, m_id); @@ -329,12 +321,9 @@ namespace nexo::renderer { glDrawBuffer(attachment); // Blit this attachment - glBlitFramebuffer( - 0, 0, static_cast(source->getSpecs().width), static_cast(source->getSpecs().height), - 0, 0, static_cast(m_specs.width), static_cast(m_specs.height), - GL_COLOR_BUFFER_BIT, - GL_NEAREST - ); + glBlitFramebuffer(0, 0, static_cast(source->getSpecs().width), + static_cast(source->getSpecs().height), 0, 0, static_cast(m_specs.width), + static_cast(m_specs.height), GL_COLOR_BUFFER_BIT, GL_NEAREST); } // Reset state: read buffer back to first attachment @@ -350,34 +339,26 @@ namespace nexo::renderer { // If depth and stencil are combined, copy them together if (source->hasDepthStencilAttachment() && this->hasDepthStencilAttachment()) { - glBlitFramebuffer( - 0, 0, static_cast(source->getSpecs().width), static_cast(source->getSpecs().height), - 0, 0, static_cast(m_specs.width), static_cast(m_specs.height), - GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, - GL_NEAREST - ); + glBlitFramebuffer(0, 0, static_cast(source->getSpecs().width), + static_cast(source->getSpecs().height), 0, 0, static_cast(m_specs.width), + static_cast(m_specs.height), GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, + GL_NEAREST); glBindFramebuffer(GL_FRAMEBUFFER, 0); return; } // Copy depth buffer if both source and destination have it if (source->hasDepthAttachment() && this->hasDepthAttachment()) { - glBlitFramebuffer( - 0, 0, static_cast(source->getSpecs().width), static_cast(source->getSpecs().height), - 0, 0, static_cast(m_specs.width), static_cast(m_specs.height), - GL_DEPTH_BUFFER_BIT, - GL_NEAREST - ); + glBlitFramebuffer(0, 0, static_cast(source->getSpecs().width), + static_cast(source->getSpecs().height), 0, 0, static_cast(m_specs.width), + static_cast(m_specs.height), GL_DEPTH_BUFFER_BIT, GL_NEAREST); } // Copy stencil buffer if both source and destination have it if (source->hasStencilAttachment() && this->hasStencilAttachment()) { - glBlitFramebuffer( - 0, 0, static_cast(source->getSpecs().width), static_cast(source->getSpecs().height), - 0, 0, static_cast(m_specs.width), static_cast(m_specs.height), - GL_STENCIL_BUFFER_BIT, - GL_NEAREST - ); + glBlitFramebuffer(0, 0, static_cast(source->getSpecs().width), + static_cast(source->getSpecs().height), 0, 0, static_cast(m_specs.width), + static_cast(m_specs.height), GL_STENCIL_BUFFER_BIT, GL_NEAREST); } glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -390,14 +371,13 @@ namespace nexo::renderer { void NxOpenGlFramebuffer::resize(const unsigned int width, const unsigned int height) { - if (!width || !height) - THROW_EXCEPTION(NxFramebufferResizingFailed, "OPENGL", false, width, height); + if (!width || !height) THROW_EXCEPTION(NxFramebufferResizingFailed, "OPENGL", false, width, height); if (width > sMaxFramebufferSize || height > sMaxFramebufferSize) THROW_EXCEPTION(NxFramebufferResizingFailed, "OPENGL", true, width, height); - m_specs.width = width; + m_specs.width = width; m_specs.height = height; - toResize = true; + toResize = true; } glm::vec2 NxOpenGlFramebuffer::getSize() const @@ -405,16 +385,18 @@ namespace nexo::renderer { return {m_specs.width, m_specs.height}; } - void NxOpenGlFramebuffer::getPixelWrapper(const unsigned int attachementIndex, const int x, const int y, void *result, const std::type_info &ti) const + void NxOpenGlFramebuffer::getPixelWrapper(const unsigned int attachementIndex, const int x, const int y, + void *result, const std::type_info &ti) const { // Add more types here when necessary if (ti == typeid(int)) - *static_cast(result) = getPixelImpl(attachementIndex, x, y); + *static_cast(result) = getPixelImpl(attachementIndex, x, y); else THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); } - void NxOpenGlFramebuffer::clearAttachmentWrapper(const unsigned int attachmentIndex, const void *value, const std::type_info &ti) const + void NxOpenGlFramebuffer::clearAttachmentWrapper(const unsigned int attachmentIndex, const void *value, + const std::type_info &ti) const { // Add more types here when necessary if (ti == typeid(int)) @@ -425,4 +407,4 @@ namespace nexo::renderer { THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp index 102c1325f..8fa15cf70 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp @@ -22,7 +22,15 @@ #include namespace nexo::renderer { - + /** + * @brief Retrieves the OpenGL type corresponding to the given template type. + * + * This template function maps a C++ type to its corresponding OpenGL type. + * For example, `float` maps to `GL_FLOAT`, `int` maps to `GL_INT`, etc. + * + * @tparam T The C++ type to map to an OpenGL type. + * @return GLenum The OpenGL type corresponding to the template type. + */ template constexpr GLenum getGLTypeFromTemplate() { @@ -33,194 +41,364 @@ namespace nexo::renderer { else if constexpr (std::is_same_v) return GL_UNSIGNED_INT; else if constexpr (std::is_same_v) - return GL_RGBA; + return GL_RGBA; else return 0; } + /** + * @brief Converts a framebuffer texture format to its corresponding OpenGL format. + * + * This function maps a `NxFrameBufferTextureFormats` enum value to its + * corresponding OpenGL format. If the format is invalid or unsupported, + * the function returns -1. + * + * @param format The framebuffer texture format to convert. + * @return int The OpenGL format corresponding to the given texture format, + * or -1 if the format is invalid or unsupported. + */ static int framebufferTextureFormatToOpenGlFormat(NxFrameBufferTextureFormats format) { constexpr GLenum formats[] = {GL_NONE, GL_RGBA, GL_RGBA, GL_RED_INTEGER}; - if (static_cast(format) == 0 || format >= NxFrameBufferTextureFormats::DEPTH24STENCIL8) // Maybe change that later + if (static_cast(format) == 0 || + format >= NxFrameBufferTextureFormats::DEPTH24STENCIL8) // Maybe change that later return -1; return static_cast(formats[static_cast(format)]); } class NxOpenGlFramebuffer final : public NxFramebuffer { - public: - /** - * @brief Constructs an OpenGL framebuffer with the specified specifications. - * - * Initializes an OpenGL framebuffer object, sets up texture attachments based - * on the provided specifications, and validates the framebuffer. - * - * @param specs The specifications for the framebuffer, including dimensions, - * attachments, and sampling options. - * - * Throws: - * - NxFramebufferResizingFailed if the dimensions are invalid (e.g., zero or exceeding limits). - * - NxFramebufferUnsupportedColorFormat if the color attachment format is unsupported. - * - NxFramebufferUnsupportedDepthFormat if the depth attachment format is unsupported. - * - NxFramebufferCreationFailed if the framebuffer status is not complete. - */ - explicit NxOpenGlFramebuffer(NxFramebufferSpecs specs); - - /** - * @brief Destroys the OpenGL framebuffer and releases associated resources. - * - * Deletes the framebuffer object and all associated texture attachments - * (color and depth) from GPU memory. - * - * OpenGL Calls: - * - `glDeleteFramebuffers`: Deletes the framebuffer object. - * - `glDeleteTextures`: Deletes the textures associated with color and depth attachments. - */ - ~NxOpenGlFramebuffer() override; - - /** - * @brief Recreates the OpenGL framebuffer and its attachments. - * - * Invalidates the current framebuffer, releasing all associated resources, - * and recreates the framebuffer based on the current specifications. - * This method is useful when resizing the framebuffer or changing its attachments. - * - * OpenGL Operations: - * - Deletes existing framebuffer and textures. - * - Generates a new framebuffer object. - * - Allocates and binds color and depth textures as specified. - * - Validates the framebuffer status. - * - * Throws: - * - NxFramebufferUnsupportedColorFormat if a specified color format is unsupported. - * - NxFramebufferUnsupportedDepthFormat if a specified depth format is unsupported. - * - NxFramebufferCreationFailed if the framebuffer is not complete. - */ - void invalidate(); - - /** - * @brief Binds the framebuffer as the current rendering target. - * - * Activates the framebuffer for rendering, ensuring that subsequent draw calls - * output to the framebuffer's attachments instead of the default framebuffer. - * If the framebuffer is flagged for resizing, it is invalidated first. - * - * OpenGL Operations: - * - `glBindFramebuffer`: Binds the framebuffer object. - * - `glViewport`: Sets the viewport dimensions to match the framebuffer. - * - `glClear`: Clears the framebuffer with a default black color. - */ - void bind() override; - - void bindAsTexture(unsigned int slot = 0, unsigned int attachment = 0) override; - - void bindDepthAsTexture(unsigned int slot = 0) override; - /** - * @brief Unbinds the framebuffer and restores the default framebuffer. - * - * Deactivates the current framebuffer, ensuring that subsequent draw calls - * output to the default framebuffer (e.g., the screen or swap chain). - * - * OpenGL Operation: - * - `glBindFramebuffer`: Binds the default framebuffer (ID = 0). - */ - void unbind() override; - - void setClearColor(const glm::vec4 &color) override {m_clearColor = color;} - - void copy(std::shared_ptr source) override; - - [[nodiscard]] unsigned int getFramebufferId() const override; - - /** - * @brief Resizes the framebuffer to new dimensions. - * - * Updates the width and height of the framebuffer and flags it for resizing. - * The framebuffer will be invalidated and recreated during the next `bind()` call. - * - * @param width The new width of the framebuffer in pixels. - * @param height The new height of the framebuffer in pixels. - * - * Throws: - * - NxFramebufferResizingFailed if the new dimensions are zero or exceed the maximum supported size. - */ - void resize(unsigned int width, unsigned int height) override; - - [[nodiscard]] glm::vec2 getSize() const override; - - - /** - * @brief Reads a pixel value from a specified attachment. - * - * Template helper that retrieves the pixel value from the given attachment at (x,y). - * - * @tparam T The expected pixel data type. - * @param attachmentIndex The index of the attachment. - * @param x X-coordinate. - * @param y Y-coordinate. - * @return T The pixel data. - */ - template - T getPixelImpl(const unsigned int attachmentIndex, const int x, const int y) const - { - if (attachmentIndex >= m_colorAttachments.size()) - THROW_EXCEPTION(NxFramebufferInvalidIndex, "OPENGL", attachmentIndex); - - glReadBuffer(GL_COLOR_ATTACHMENT0 + attachmentIndex); - - auto &textureFormat = m_colorAttachmentsSpecs[attachmentIndex].textureFormat; - const GLenum format = framebufferTextureFormatToOpenGlFormat(textureFormat); - const GLenum type = getGLTypeFromTemplate(); - - T pixelData; - glReadPixels(x, y, 1, 1, format, type, &pixelData); - - return pixelData; - } - void getPixelWrapper(unsigned int attachementIndex, int x, int y, void *result, const std::type_info &ti) const override; - - - /** - * @brief Clears the specified attachment with a given value. - * - * Template helper that clears the texture attached at the given index. - * - * @tparam T The type of the clear value. - * @param attachmentIndex The index of the attachment. - * @param value The value to clear the attachment to. - */ - template - void clearAttachmentImpl(const unsigned int attachmentIndex, const void *value) const - { - if (attachmentIndex >= m_colorAttachments.size()) - THROW_EXCEPTION(NxFramebufferInvalidIndex, "OPENGL", attachmentIndex); - auto &spec = m_colorAttachmentsSpecs[attachmentIndex]; - constexpr GLenum type = getGLTypeFromTemplate(); - - glClearTexImage(m_colorAttachments[attachmentIndex], 0, framebufferTextureFormatToOpenGlFormat(spec.textureFormat), type, value); - } - void clearAttachmentWrapper(unsigned int attachmentIndex, const void *value, const std::type_info &ti) const override; - - NxFramebufferSpecs &getSpecs() override {return m_specs;}; - [[nodiscard]] const NxFramebufferSpecs &getSpecs() const override {return m_specs;}; - - [[nodiscard]] unsigned int getNbColorAttachments() const override { return static_cast(m_colorAttachments.size()); }; - [[nodiscard]] unsigned int getColorAttachmentId(const unsigned int index = 0) const override {return m_colorAttachments[index];}; - [[nodiscard]] unsigned int getDepthAttachmentId() const override { return m_depthAttachment; } - - [[nodiscard]] bool hasDepthAttachment() const override {return m_depthAttachmentSpec.textureFormat != NxFrameBufferTextureFormats::NONE;}; - [[nodiscard]] bool hasStencilAttachment() const override {return m_depthAttachmentSpec.textureFormat != NxFrameBufferTextureFormats::NONE;}; - [[nodiscard]] bool hasDepthStencilAttachment() const override {return m_depthAttachmentSpec.textureFormat != NxFrameBufferTextureFormats::NONE;}; - private: - unsigned int m_id = 0; - bool toResize = false; - NxFramebufferSpecs m_specs; - - glm::vec4 m_clearColor = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); - - std::vector m_colorAttachmentsSpecs; - NxFrameBufferTextureSpecifications m_depthAttachmentSpec; - - std::vector m_colorAttachments; - unsigned int m_depthAttachment = 0; + public: + /** + * @brief Constructs an OpenGL framebuffer with the specified specifications. + * + * Initializes an OpenGL framebuffer object, sets up texture attachments based + * on the provided specifications, and validates the framebuffer. + * + * @param specs The specifications for the framebuffer, including dimensions, + * attachments, and sampling options. + * + * Throws: + * - NxFramebufferResizingFailed if the dimensions are invalid (e.g., zero or exceeding limits). + * - NxFramebufferUnsupportedColorFormat if the color attachment format is unsupported. + * - NxFramebufferUnsupportedDepthFormat if the depth attachment format is unsupported. + * - NxFramebufferCreationFailed if the framebuffer status is not complete. + */ + explicit NxOpenGlFramebuffer(NxFramebufferSpecs specs); + + /** + * @brief Destroys the OpenGL framebuffer and releases associated resources. + * + * Deletes the framebuffer object and all associated texture attachments + * (color and depth) from GPU memory. + * + * OpenGL Calls: + * - `glDeleteFramebuffers`: Deletes the framebuffer object. + * - `glDeleteTextures`: Deletes the textures associated with color and depth attachments. + */ + ~NxOpenGlFramebuffer() override; + + /** + * @brief Recreates the OpenGL framebuffer and its attachments. + * + * Invalidates the current framebuffer, releasing all associated resources, + * and recreates the framebuffer based on the current specifications. + * This method is useful when resizing the framebuffer or changing its attachments. + * + * OpenGL Operations: + * - Deletes existing framebuffer and textures. + * - Generates a new framebuffer object. + * - Allocates and binds color and depth textures as specified. + * - Validates the framebuffer status. + * + * Throws: + * - NxFramebufferUnsupportedColorFormat if a specified color format is unsupported. + * - NxFramebufferUnsupportedDepthFormat if a specified depth format is unsupported. + * - NxFramebufferCreationFailed if the framebuffer is not complete. + */ + void invalidate(); + + /** + * @brief Binds the framebuffer as the current rendering target. + * + * Activates the framebuffer for rendering, ensuring that subsequent draw calls + * output to the framebuffer's attachments instead of the default framebuffer. + * If the framebuffer is flagged for resizing, it is invalidated first. + * + * OpenGL Operations: + * - `glBindFramebuffer`: Binds the framebuffer object. + * - `glViewport`: Sets the viewport dimensions to match the framebuffer. + * - `glClear`: Clears the framebuffer with a default black color. + */ + void bind() override; + + /** + * @brief Binds a specified color attachment as a texture to a texture slot. + * + * Activates the specified color attachment texture for use in shaders, + * allowing it to be sampled during rendering. The texture is bound to the + * specified texture unit. + * + * @param slot The texture unit slot to bind the texture to (default is 0). + * @param attachment The index of the color attachment to bind (default is 0). + * + * Throws: + * - NxFramebufferInvalidIndex if the attachment index is out of range. + * + * OpenGL Operations: + * - `glActiveTexture`: Activates the specified texture unit. + * - `glBindTexture`: Binds the color attachment texture to the active texture unit. + */ + void bindAsTexture(unsigned int slot = 0, unsigned int attachment = 0) override; + + /** + * @brief Binds the depth attachment as a texture to a specified texture slot. + * + * Activates the depth texture for use in shaders, allowing it to be sampled + * during rendering. The texture is bound to the specified texture unit. + * + * @param slot The texture unit slot to bind the depth texture to (default is 0). + * + * OpenGL Operations: + * - `glActiveTexture`: Activates the specified texture unit. + * - `glBindTexture`: Binds the depth texture to the active texture unit. + */ + void bindDepthAsTexture(unsigned int slot = 0) override; + + /** + * @brief Unbinds the framebuffer and restores the default framebuffer. + * + * Deactivates the current framebuffer, ensuring that subsequent draw calls + * output to the default framebuffer (e.g., the screen or swap chain). + * + * OpenGL Operation: + * - `glBindFramebuffer`: Binds the default framebuffer (ID = 0). + */ + void unbind() override; + + /** + * @brief Sets the clear color for the framebuffer. + * + * Updates the color used when clearing the framebuffer. This color is applied + * during the `bind()` operation to clear the framebuffer's color attachments. + * + * @param color A `glm::vec4` representing the RGBA clear color. + */ + void setClearColor(const glm::vec4 &color) override + { + m_clearColor = color; + } + + /** + * @brief Copies the contents from another framebuffer into this framebuffer. + * + * Performs a blit operation to copy color, depth, and stencil data from the + * source framebuffer to this framebuffer. Both framebuffers must have compatible + * attachments for the copy to succeed. + * + * @param source The source framebuffer to copy from. + * + * Throws: + * - NxFramebufferInvalidIndex if the source framebuffer has more color attachments + * than this framebuffer. + * + * OpenGL Operations: + * - `glBindFramebuffer`: Binds the source and destination framebuffers for reading and drawing. + * - `glBlitFramebuffer`: Copies the specified buffers from the source to the destination. + * - `glReadBuffer` and `glDrawBuffers`: Configures which attachments to read from and draw to. + */ + void copy(std::shared_ptr source) override; + + /** + * @brief Retrieves the OpenGL ID of the framebuffer. + * + * @return The OpenGL ID of the framebuffer object. + */ + [[nodiscard]] unsigned int getFramebufferId() const override; + + /** + * @brief Resizes the framebuffer to new dimensions. + * + * Updates the width and height of the framebuffer and flags it for resizing. + * The framebuffer will be invalidated and recreated during the next `bind()` call. + * + * @param width The new width of the framebuffer in pixels. + * @param height The new height of the framebuffer in pixels. + * + * Throws: + * - NxFramebufferResizingFailed if the new dimensions are zero or exceed the maximum supported size. + */ + void resize(unsigned int width, unsigned int height) override; + + /** + * @brief Retrieves the current size of the framebuffer. + * + * @return A `glm::vec2` containing the width and height of the framebuffer. + */ + [[nodiscard]] glm::vec2 getSize() const override; + + /** + * @brief Reads a pixel value from a specified attachment. + * + * Template helper that retrieves the pixel value from the given attachment at (x,y). + * + * @tparam T The expected pixel data type. + * @param attachmentIndex The index of the attachment. + * @param x X-coordinate. + * @param y Y-coordinate. + * @return T The pixel data. + */ + template + T getPixelImpl(const unsigned int attachmentIndex, const int x, const int y) const + { + if (attachmentIndex >= m_colorAttachments.size()) + THROW_EXCEPTION(NxFramebufferInvalidIndex, "OPENGL", attachmentIndex); + + glReadBuffer(GL_COLOR_ATTACHMENT0 + attachmentIndex); + + auto &textureFormat = m_colorAttachmentsSpecs[attachmentIndex].textureFormat; + const GLenum format = framebufferTextureFormatToOpenGlFormat(textureFormat); + const GLenum type = getGLTypeFromTemplate(); + + T pixelData; + glReadPixels(x, y, 1, 1, format, type, &pixelData); + + return pixelData; + } + + /** + * @brief Wrapper to call the templated getPixelImpl based on type_info. + * + * @param attachementIndex The index of the attachment. + * @param x X-coordinate. + * @param y Y-coordinate. + * @param result Pointer to store the result. + * @param ti Type information of the expected pixel data type. + */ + void getPixelWrapper(unsigned int attachementIndex, int x, int y, void *result, + const std::type_info &ti) const override; + + /** + * @brief Clears the specified attachment with a given value. + * + * Template helper that clears the texture attached at the given index. + * + * @tparam T The type of the clear value. + * @param attachmentIndex The index of the attachment. + * @param value The value to clear the attachment to. + */ + template + void clearAttachmentImpl(const unsigned int attachmentIndex, const void *value) const + { + if (attachmentIndex >= m_colorAttachments.size()) + THROW_EXCEPTION(NxFramebufferInvalidIndex, "OPENGL", attachmentIndex); + auto &spec = m_colorAttachmentsSpecs[attachmentIndex]; + constexpr GLenum type = getGLTypeFromTemplate(); + + glClearTexImage(m_colorAttachments[attachmentIndex], 0, + framebufferTextureFormatToOpenGlFormat(spec.textureFormat), type, value); + } + + /** + * @brief Wrapper to call the templated clearAttachmentImpl based on type_info. + * + * @param attachmentIndex The index of the attachment. + * @param value Pointer to the value to clear the attachment to. + * @param ti Type information of the clear value type. + */ + void clearAttachmentWrapper(unsigned int attachmentIndex, const void *value, + const std::type_info &ti) const override; + + /** + * @brief Retrieves the specifications of the framebuffer. + * + * @return The `NxFramebufferSpecs` structure containing the framebuffer's specifications. + */ + NxFramebufferSpecs &getSpecs() override + { + return m_specs; + } + + /** @brief Retrieves the specifications of the framebuffer (const version). + * + * @return The `NxFramebufferSpecs` structure containing the framebuffer's specifications. + */ + [[nodiscard]] const NxFramebufferSpecs &getSpecs() const override + { + return m_specs; + } + + /** + * @brief Retrieves the number of color attachments in the framebuffer. + * + * @return The number of color attachments. + */ + [[nodiscard]] unsigned int getNbColorAttachments() const override + { + return static_cast(m_colorAttachments.size()); + } + + /** + * @brief Retrieves the OpenGL ID of a specific color attachment. + * + * @param index The index of the color attachment (default is 0). + * @return The OpenGL ID of the specified color attachment. + */ + [[nodiscard]] unsigned int getColorAttachmentId(const unsigned int index = 0) const override + { + return m_colorAttachments[index]; + } + + /** + * @brief Retrieves the OpenGL ID of the depth attachment. + * + * @return The OpenGL ID of the depth attachment. + */ + [[nodiscard]] unsigned int getDepthAttachmentId() const override + { + return m_depthAttachment; + } + + /** + * @brief Checks if the framebuffer has a depth attachment. + * + * @return True if a depth attachment exists, false otherwise. + */ + [[nodiscard]] bool hasDepthAttachment() const override + { + return m_depthAttachmentSpec.textureFormat != NxFrameBufferTextureFormats::NONE; + } + + /** + * @brief Checks if the framebuffer has a stencil attachment. + * + * @return True if a stencil attachment exists, false otherwise. + */ + [[nodiscard]] bool hasStencilAttachment() const override + { + return m_depthAttachmentSpec.textureFormat != NxFrameBufferTextureFormats::NONE; + } + + /** + * @brief Checks if the framebuffer has a combined depth-stencil attachment. + * + * @return True if a combined depth-stencil attachment exists, false otherwise. + */ + [[nodiscard]] bool hasDepthStencilAttachment() const override + { + return m_depthAttachmentSpec.textureFormat != NxFrameBufferTextureFormats::NONE; + } + + private: + unsigned int m_id = 0; + bool toResize = false; + NxFramebufferSpecs m_specs; + + glm::vec4 m_clearColor = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + + std::vector m_colorAttachmentsSpecs; + NxFrameBufferTextureSpecifications m_depthAttachmentSpec; + + std::vector m_colorAttachments; + unsigned int m_depthAttachment = 0; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlRendererAPI.hpp b/engine/src/renderer/opengl/OpenGlRendererAPI.hpp index a63ad7feb..8e511c4b9 100644 --- a/engine/src/renderer/opengl/OpenGlRendererAPI.hpp +++ b/engine/src/renderer/opengl/OpenGlRendererAPI.hpp @@ -18,125 +18,220 @@ namespace nexo::renderer { /** - * @class NxOpenGlRendererApi - * @brief Implementation of the RendererApi interface using OpenGL. - * - * The `NxOpenGlRendererApi` class provides OpenGL-specific implementations for the - * methods defined in `NxRendererApi`. It interacts directly with OpenGL functions - * to configure and manage rendering operations. - * - * Responsibilities: - * - Manage OpenGL state for viewport, buffer clearing, and rendering. - * - Provide access to OpenGL-specific features like blending and depth testing. - * - Issue draw calls for indexed geometry. - */ + * @class NxOpenGlRendererApi + * @brief Implementation of the RendererApi interface using OpenGL. + * + * The `NxOpenGlRendererApi` class provides OpenGL-specific implementations for the + * methods defined in `NxRendererApi`. It interacts directly with OpenGL functions + * to configure and manage rendering operations. + * + * Responsibilities: + * - Manage OpenGL state for viewport, buffer clearing, and rendering. + * - Provide access to OpenGL-specific features like blending and depth testing. + * - Issue draw calls for indexed geometry. + */ class NxOpenGlRendererApi final : public NxRendererApi { - public: - /** - * @brief Initializes the OpenGL renderer API. - * - * Sets up OpenGL state, including: - * - Enabling blending for transparent objects. - * - Enabling depth testing for correct object ordering. - * - Configuring maximum viewport dimensions. - * - * Throws: - * - NxGraphicsApiNotInitialized if OpenGL fails to initialize. - */ - void init() override; - - /** - * @brief Configures the OpenGL viewport. - * - * Sets the OpenGL viewport dimensions and position, ensuring that rendering - * occurs within the specified bounds. - * - * @param x The x-coordinate of the viewport's bottom-left corner. - * @param y The y-coordinate of the viewport's bottom-left corner. - * @param width The width of the viewport in pixels. - * @param height The height of the viewport in pixels. - * - * Throws: - * - NxGraphicsApiViewportResizingFailure if the dimensions exceed the maximum allowed size. - */ - void setViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height) override; - - /** - * @brief Retrieves the maximum viewport dimensions supported by OpenGL. - * - * Queries OpenGL for the maximum allowed dimensions of the viewport and - * stores the results in the provided pointers. - * - * @param[out] width Pointer to store the maximum viewport width. - * @param[out] height Pointer to store the maximum viewport height. - */ - void getMaxViewportSize(unsigned int *width, unsigned int *height) override; - - /** - * @brief Clears the OpenGL frame buffer. - * - * Resets the color and depth buffers using the current clear color and depth values. - * - * Throws: - * - NxGraphicsApiNotInitialized if OpenGL is not initialized. - */ - void clear() override; - - /** - * @brief Sets the OpenGL clear color. - * - * Configures the RGBA color used to clear the frame buffer during the next call to `clear()`. - * - * @param color A `glm::vec4` containing the red, green, blue, and alpha components of the clear color. - * - * Throws: - * - NxGraphicsApiNotInitialized if OpenGL is not initialized. - */ - void setClearColor(const glm::vec4 &color) override; - - /** - * @brief Sets the OpenGL clear depth value. - * - * Configures the depth value used to clear the depth buffer during the next call to `clear()`. - * - * @param depth A float value representing the clear depth. - * - * Throws: - * - NxGraphicsApiNotInitialized if OpenGL is not initialized. - */ - void setClearDepth(float depth) override; - - void setDepthTest(bool enable) override; - void setDepthFunc(unsigned int func) override; - void setDepthMask(bool enable) override; - - /** - * @brief Renders indexed geometry using OpenGL. - * - * Issues a draw call for indexed primitives using data from the specified `NxVertexArray`. - * - * @param vertexArray A shared pointer to the `NxVertexArray` containing vertex and index data. - * @param indexCount The number of indices to draw. If zero, all indices in the buffer are used. - * - * Throws: - * - NxGraphicsApiNotInitialized if OpenGL is not initialized. - * - NxInvalidValue if the `vertexArray` is null. - */ - void drawIndexed(const std::shared_ptr &vertexArray, size_t indexCount = 0) override; - - void drawUnIndexed(size_t verticesCount) override; - - void setStencilTest(bool enable) override; - void setStencilMask(unsigned int mask) override; - void setStencilFunc(unsigned int func, int ref, unsigned int mask) override; - void setStencilOp(unsigned int sfail, unsigned int dpfail, unsigned int dppass) override; - - void setCulling(bool enable) override; - void setCulledFace(CulledFace face) override; - void setWindingOrder(WindingOrder order) override; - private: - bool m_initialized = false; - unsigned int m_maxWidth = 0; - unsigned int m_maxHeight = 0; + public: + /** + * @brief Initializes the OpenGL renderer API. + * + * Sets up OpenGL state, including: + * - Enabling blending for transparent objects. + * - Enabling depth testing for correct object ordering. + * - Configuring maximum viewport dimensions. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL fails to initialize. + */ + void init() override; + + /** + * @brief Configures the OpenGL viewport. + * + * Sets the OpenGL viewport dimensions and position, ensuring that rendering + * occurs within the specified bounds. + * + * @param x The x-coordinate of the viewport's bottom-left corner. + * @param y The y-coordinate of the viewport's bottom-left corner. + * @param width The width of the viewport in pixels. + * @param height The height of the viewport in pixels. + * + * Throws: + * - NxGraphicsApiViewportResizingFailure if the dimensions exceed the maximum allowed size. + */ + void setViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height) override; + + /** + * @brief Retrieves the maximum viewport dimensions supported by OpenGL. + * + * Queries OpenGL for the maximum allowed dimensions of the viewport and + * stores the results in the provided pointers. + * + * @param[out] width Pointer to store the maximum viewport width. + * @param[out] height Pointer to store the maximum viewport height. + */ + void getMaxViewportSize(unsigned int *width, unsigned int *height) override; + + /** + * @brief Clears the OpenGL frame buffer. + * + * Resets the color and depth buffers using the current clear color and depth values. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void clear() override; + + /** + * @brief Sets the OpenGL clear color. + * + * Configures the RGBA color used to clear the frame buffer during the next call to `clear()`. + * + * @param color A `glm::vec4` containing the red, green, blue, and alpha components of the clear color. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void setClearColor(const glm::vec4 &color) override; + + /** + * @brief Sets the OpenGL clear depth value. + * + * Configures the depth value used to clear the depth buffer during the next call to `clear()`. + * + * @param depth A float value representing the clear depth. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void setClearDepth(float depth) override; + + void setDepthTest(bool enable) override; + void setDepthFunc(unsigned int func) override; + void setDepthMask(bool enable) override; + + void setLineWidth(float lineWidth) override; + + /** + * @brief Renders indexed geometry using OpenGL. + * + * Issues a draw call for indexed primitives using data from the specified `NxVertexArray`. + * + * @param vertexArray A shared pointer to the `NxVertexArray` containing vertex and index data. + * @param indexCount The number of indices to draw. If zero, all indices in the buffer are used. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + * - NxInvalidValue if the `vertexArray` is null. + */ + void drawIndexed(const std::shared_ptr &vertexArray, size_t indexCount = 0, CommandType primitiveType = CommandType::MESH) override; + + /** + * @brief Renders non-indexed geometry using OpenGL. + * + * Issues a draw call for non-indexed primitives using the specified number of vertices. + * + * @param verticesCount The number of vertices to draw. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void drawUnIndexed(size_t verticesCount) override; + + /** + * @brief Enables or disables stencil testing in OpenGL. + * + * Configures whether stencil testing is active during rendering operations. + * + * @param enable A boolean indicating whether to enable (true) or disable (false) stencil testing. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void setStencilTest(bool enable) override; + + /** + * @brief Sets the stencil mask for OpenGL operations. + * + * Configures the stencil mask, which determines which bits of the stencil buffer + * can be written during stencil operations. + * + * @param mask The bitmask to apply to stencil buffer writes. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void setStencilMask(unsigned int mask) override; + + /** + * @brief Configures the stencil test function in OpenGL. + * + * Sets the stencil test function, reference value, and mask used to compare + * stencil buffer values during rendering. + * + * @param func The stencil test function (e.g., GL_ALWAYS, GL_NEVER, GL_EQUAL). + * @param ref The reference value for the stencil test. + * @param mask The bitmask applied to the reference and stencil buffer values. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void setStencilFunc(unsigned int func, int ref, unsigned int mask) override; + + /** + * @brief Configures the stencil operations for OpenGL. + * + * Sets the actions to take when stencil tests fail, depth tests fail, or both pass. + * + * @param sfail Action to take when the stencil test fails. + * @param dpfail Action to take when the stencil test passes but the depth test fails. + * @param dppass Action to take when both the stencil and depth tests pass. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void setStencilOp(unsigned int sfail, unsigned int dpfail, unsigned int dppass) override; + + /** + * @brief Enables or disables face culling in OpenGL. + * + * Configures whether face culling is active during rendering operations. + * + * @param enable A boolean indicating whether to enable (true) or disable (false) face culling. + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void setCulling(bool enable) override; + + /** + * @brief Sets the face to be culled in OpenGL. + * + * Configures whether front, back, or both faces are culled during rendering. + * + * @param face The face to cull (e.g., CulledFace::FRONT, CulledFace::BACK). + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void setCulledFace(CulledFace face) override; + + /** + * @brief Sets the winding order for face culling in OpenGL. + * + * Configures the vertex winding order (clockwise or counterclockwise) used + * to determine front-facing polygons. + * + * @param order The winding order (e.g., WindingOrder::CW, WindingOrder::CCW). + * + * Throws: + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + */ + void setWindingOrder(WindingOrder order) override; + + private: + bool m_initialized = false; + unsigned int m_maxWidth = 0; + unsigned int m_maxHeight = 0; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlRendererApi.cpp b/engine/src/renderer/opengl/OpenGlRendererApi.cpp index cae680411..09c3fc916 100644 --- a/engine/src/renderer/opengl/OpenGlRendererApi.cpp +++ b/engine/src/renderer/opengl/OpenGlRendererApi.cpp @@ -15,13 +15,28 @@ #include #include -#include "OpenGlRendererAPI.hpp" +#include "DrawCommand.hpp" #include "Logger.hpp" +#include "OpenGlRendererAPI.hpp" #include namespace nexo::renderer { + static GLenum nexoPrimitiveTypeToGLType(CommandType type) + { + switch (type) + { + case CommandType::MESH: + return GL_TRIANGLES; + case CommandType::LINE: + return GL_LINES; + default: + THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Unsupported primitive type"); + } + return 0; + } + void NxOpenGlRendererApi::init() { glEnable(GL_BLEND); @@ -38,18 +53,17 @@ namespace nexo::renderer { glCullFace(GL_BACK); int maxViewportSize[] = {0, 0}; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportSize); - m_maxWidth = static_cast(maxViewportSize[0]); - m_maxHeight = static_cast(maxViewportSize[1]); + m_maxWidth = static_cast(maxViewportSize[0]); + m_maxHeight = static_cast(maxViewportSize[1]); m_initialized = true; LOG(NEXO_DEV, "Opengl renderer api initialized"); } - void NxOpenGlRendererApi::setViewport(const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height) + void NxOpenGlRendererApi::setViewport(const unsigned int x, const unsigned int y, const unsigned int width, + const unsigned int height) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); - if (!width || !height) - THROW_EXCEPTION(NxGraphicsApiViewportResizingFailure, "OPENGL", false, width, height); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!width || !height) THROW_EXCEPTION(NxGraphicsApiViewportResizingFailure, "OPENGL", false, width, height); if (width > m_maxWidth || height > m_maxHeight) THROW_EXCEPTION(NxGraphicsApiViewportResizingFailure, "OPENGL", true, width, height); glViewport(static_cast(x), static_cast(y), static_cast(width), static_cast(height)); @@ -57,35 +71,31 @@ namespace nexo::renderer { void NxOpenGlRendererApi::getMaxViewportSize(unsigned int *width, unsigned int *height) { - *width = m_maxWidth; + *width = m_maxWidth; *height = m_maxHeight; } void NxOpenGlRendererApi::clear() { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } void NxOpenGlRendererApi::setClearColor(const glm::vec4 &color) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glClearColor(color.r, color.g, color.b, color.a); } void NxOpenGlRendererApi::setClearDepth(const float depth) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glClearDepth(depth); } void NxOpenGlRendererApi::setDepthTest(const bool enable) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (enable) glEnable(GL_DEPTH_TEST); else @@ -94,42 +104,43 @@ namespace nexo::renderer { void NxOpenGlRendererApi::setDepthFunc(const unsigned int func) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glDepthFunc(func); } void NxOpenGlRendererApi::setDepthMask(const bool enable) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (enable) glDepthMask(GL_TRUE); else glDepthMask(GL_FALSE); } - void NxOpenGlRendererApi::drawIndexed(const std::shared_ptr &vertexArray, size_t indexCount) + void NxOpenGlRendererApi::setLineWidth(float lineWidth) + { + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + glLineWidth(lineWidth); + } + + void NxOpenGlRendererApi::drawIndexed(const std::shared_ptr &vertexArray, size_t indexCount, CommandType primitiveType) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); - if (!vertexArray) - THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Vertex array cannot be null"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!vertexArray) THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Vertex array cannot be null"); const size_t count = indexCount ? indexCount : vertexArray->getIndexBuffer()->getCount(); - glDrawElements(GL_TRIANGLES, static_cast(count), GL_UNSIGNED_INT, nullptr); + const GLenum glType = nexoPrimitiveTypeToGLType(primitiveType); + glDrawElements(glType, static_cast(count), GL_UNSIGNED_INT, nullptr); } void NxOpenGlRendererApi::drawUnIndexed(size_t verticesCount) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glDrawArrays(GL_TRIANGLES, 0, static_cast(verticesCount)); } void NxOpenGlRendererApi::setStencilTest(const bool enable) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (enable) glEnable(GL_STENCIL_TEST); else @@ -138,29 +149,26 @@ namespace nexo::renderer { void NxOpenGlRendererApi::setStencilMask(const unsigned int mask) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glStencilMask(mask); } void NxOpenGlRendererApi::setStencilFunc(const unsigned int func, const int ref, const unsigned int mask) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glStencilFunc(func, ref, mask); } - void NxOpenGlRendererApi::setStencilOp(const unsigned int sfail, const unsigned int dpfail, const unsigned int dppass) + void NxOpenGlRendererApi::setStencilOp(const unsigned int sfail, const unsigned int dpfail, + const unsigned int dppass) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glStencilOp(sfail, dpfail, dppass); } void NxOpenGlRendererApi::setCulling(const bool enable) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (enable) glEnable(GL_CULL_FACE); else @@ -169,8 +177,7 @@ namespace nexo::renderer { void NxOpenGlRendererApi::setCulledFace(const CulledFace face) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (face == CulledFace::BACK) glCullFace(GL_BACK); else if (face == CulledFace::FRONT) @@ -181,11 +188,10 @@ namespace nexo::renderer { void NxOpenGlRendererApi::setWindingOrder(const WindingOrder order) { - if (!m_initialized) - THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); + if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (order == WindingOrder::CCW) glFrontFace(GL_CCW); else if (order == WindingOrder::CW) glFrontFace(GL_CW); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlShader.cpp b/engine/src/renderer/opengl/OpenGlShader.cpp index 74d3ef3c2..d425944f8 100644 --- a/engine/src/renderer/opengl/OpenGlShader.cpp +++ b/engine/src/renderer/opengl/OpenGlShader.cpp @@ -15,45 +15,54 @@ #include "OpenGlShader.hpp" #include "Exception.hpp" #include "Logger.hpp" +#include "OpenGlShaderReflection.hpp" #include "Shader.hpp" #include "renderer/RendererExceptions.hpp" -#include "OpenGlShaderReflection.hpp" #include -#include #include +#include namespace nexo::renderer { + /** + * @brief Converts a shader type string to the corresponding OpenGL constant. + * + * This function takes a string representing the shader type (`vertex` or `fragment`) + * and returns the corresponding OpenGL constant (`GL_VERTEX_SHADER` or `GL_FRAGMENT_SHADER`). + * If the type is not recognized, the function returns 0. + * + * @param type String representing the shader type. + * @return The GLenum constant corresponding to the shader type, or 0 if unknown. + */ static GLenum shaderTypeFromString(const std::string_view &type) { - if (type == "vertex") - return GL_VERTEX_SHADER; - if (type == "fragment") - return GL_FRAGMENT_SHADER; + if (type == "vertex") return GL_VERTEX_SHADER; + if (type == "fragment") return GL_FRAGMENT_SHADER; return 0; } NxOpenGlShader::NxOpenGlShader(const std::string &path) { - const std::string src = readFile(path); + const std::string src = readFile(path); const auto shaderSources = preProcess(src, path); compile(shaderSources); - auto lastSlash = path.find_last_of("/\\"); - lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1; + auto lastSlash = path.find_last_of("/\\"); + lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1; const auto lastDot = path.rfind('.'); - const auto count = lastDot == std::string::npos ? path.size() - lastSlash : lastDot - lastSlash; - m_name = path.substr(lastSlash, count); + const auto count = lastDot == std::string::npos ? path.size() - lastSlash : lastDot - lastSlash; + m_name = path.substr(lastSlash, count); setupUniformLocations(); } NxOpenGlShader::NxOpenGlShader(std::string name, const std::string_view &vertexSource, - const std::string_view &fragmentSource) : m_name(std::move(name)) + const std::string_view &fragmentSource) + : m_name(std::move(name)) { std::unordered_map preProcessedSource; - preProcessedSource[GL_VERTEX_SHADER] = vertexSource; + preProcessedSource[GL_VERTEX_SHADER] = vertexSource; preProcessedSource[GL_FRAGMENT_SHADER] = fragmentSource; compile(preProcessedSource); setupUniformLocations(); @@ -65,39 +74,39 @@ namespace nexo::renderer { } std::unordered_map NxOpenGlShader::preProcess(const std::string_view &src, - const std::string &filePath) + const std::string &filePath) { std::unordered_map shaderSources; const char *typeToken = "#type"; - size_t pos = src.find(typeToken, 0); - size_t currentLine = 1; - while (pos != std::string::npos) - { + size_t pos = src.find(typeToken, 0); + size_t currentLine = 1; + while (pos != std::string::npos) { constexpr size_t typeTokenLength = 5; - const size_t eol = src.find_first_of("\r\n", pos); + const size_t eol = src.find_first_of("\r\n", pos); if (eol == std::string::npos) THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", - "Syntax error at line: " + std::to_string(currentLine), filePath); + "Syntax error at line: " + std::to_string(currentLine), filePath); - const size_t begin = pos + typeTokenLength + 1; + const size_t begin = pos + typeTokenLength + 1; std::string_view type = src.substr(begin, eol - begin); if (!shaderTypeFromString(type)) THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", - "Invalid shader type encountered at line: " + std::to_string(currentLine), filePath); + "Invalid shader type encountered at line: " + std::to_string(currentLine), filePath); const size_t nextLinePos = src.find_first_not_of("\r\n", eol); if (nextLinePos == std::string::npos) THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", - "Syntax error at line: " + std::to_string(currentLine), filePath); + "Syntax error at line: " + std::to_string(currentLine), filePath); pos = src.find(typeToken, nextLinePos); - shaderSources[shaderTypeFromString(type)] = (pos == std::string::npos) - ? src.substr(nextLinePos) - : src.substr(nextLinePos, pos - nextLinePos); - currentLine += std::count(src.begin() + nextLinePos, - src.begin() + (pos == std::string::npos ? src.size() : pos), '\n'); + shaderSources[shaderTypeFromString(type)] = + (pos == std::string::npos) ? src.substr(nextLinePos) : src.substr(nextLinePos, pos - nextLinePos); + currentLine += std::count(src.begin() + static_cast(nextLinePos), + src.begin() + static_cast( + pos == std::string::npos ? src.size() : pos), + '\n'); } return shaderSources; @@ -110,13 +119,12 @@ namespace nexo::renderer { // Get a program object. if (shaderSources.size() > 2) THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", - "Only two shader type (vertex/fragment) are supported for now", ""); + "Only two shader type (vertex/fragment) are supported for now", ""); const GLuint program = glCreateProgram(); std::array glShaderIds{}; unsigned int shaderIndex = 0; - for (const auto &[fst, snd]: shaderSources) - { - const GLenum type = fst; + for (const auto &[fst, snd] : shaderSources) { + const GLenum type = fst; const std::string src = snd; // Create an empty vertex shader handle const GLuint shader = glCreateShader(type); @@ -131,8 +139,7 @@ namespace nexo::renderer { GLint isCompiled = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled); - if (isCompiled == GL_FALSE) - { + if (isCompiled == GL_FALSE) { GLint maxLength = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); @@ -156,8 +163,7 @@ namespace nexo::renderer { // Note the different functions here: glGetProgram* instead of glGetShader*. GLint isLinked = 0; glGetProgramiv(m_id, GL_LINK_STATUS, &isLinked); - if (isLinked == GL_FALSE) - { + if (isLinked == GL_FALSE) { GLint maxLength = 0; glGetProgramiv(m_id, GL_INFO_LOG_LENGTH, &maxLength); @@ -168,32 +174,29 @@ namespace nexo::renderer { // We don't need the program anymore. glDeleteProgram(m_id); // Don't leak shaders either. - for (const auto id: glShaderIds) - glDeleteShader(id); + for (const auto id : glShaderIds) glDeleteShader(id); THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", - "Opengl failed to compile the shader: " + std::string(infoLog.data()), ""); + "Opengl failed to compile the shader: " + std::string(infoLog.data()), ""); } // Always detach shaders after a successful link. - for (const auto id: glShaderIds) - glDetachShader(program, id); + for (const auto id : glShaderIds) glDetachShader(program, id); } void NxOpenGlShader::setupUniformLocations() { - m_uniformInfos = ShaderReflection::reflectUniforms(m_id); + m_uniformInfos = ShaderReflection::reflectUniforms(m_id); m_attributeInfos = ShaderReflection::reflectAttributes(m_id); - static const std::unordered_map> attributeMappers = { - {"aPos", [](RequiredAttributes& attrs) { attrs.bitsUnion.flags.position = true; }}, - {"aNormal", [](RequiredAttributes& attrs) { attrs.bitsUnion.flags.normal = true; }}, - {"aTangent", [](RequiredAttributes& attrs) { attrs.bitsUnion.flags.tangent = true; }}, - {"aBiTangent", [](RequiredAttributes& attrs) { attrs.bitsUnion.flags.bitangent = true; }}, - {"aTexCoord", [](RequiredAttributes& attrs) { attrs.bitsUnion.flags.uv0 = true; }} - }; + static const std::unordered_map> attributeMappers = { + {"aPos", [](RequiredAttributes &attrs) { attrs.bitsUnion.flags.position = true; }}, + {"aNormal", [](RequiredAttributes &attrs) { attrs.bitsUnion.flags.normal = true; }}, + {"aTangent", [](RequiredAttributes &attrs) { attrs.bitsUnion.flags.tangent = true; }}, + {"aBiTangent", [](RequiredAttributes &attrs) { attrs.bitsUnion.flags.bitangent = true; }}, + {"aTexCoord", [](RequiredAttributes &attrs) { attrs.bitsUnion.flags.uv0 = true; }}}; - for (const auto& [location, info] : m_attributeInfos) { + for (const auto &[location, info] : m_attributeInfos) { const auto it = attributeMappers.find(info.name); if (it != attributeMappers.end()) { it->second(m_requiredAttributes); @@ -211,7 +214,8 @@ namespace nexo::renderer { glUseProgram(0); } - int NxOpenGlShader::getUniformLocation(const std::string& name) const { + int NxOpenGlShader::getUniformLocation(const std::string &name) const + { const auto it = m_uniformInfos.find(name); if (it != m_uniformInfos.end()) { return it->second.location; @@ -219,12 +223,10 @@ namespace nexo::renderer { return glGetUniformLocation(m_id, name.c_str()); } - bool NxOpenGlShader::setUniformFloat(const std::string& name, const float value) const + bool NxOpenGlShader::setUniformFloat(const std::string &name, const float value) const { - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformFloat(name, value)) - return true; // Value was cached, no need to update + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformFloat(name, value)) return true; // Value was cached, no need to update const int location = getUniformLocation(name); if (location == -1) { @@ -239,10 +241,8 @@ namespace nexo::renderer { bool NxOpenGlShader::setUniformFloat(const NxShaderUniforms uniform, const float value) const { const std::string &name = ShaderUniformsName.at(uniform); - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformFloat(name, value)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformFloat(name, value)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -254,12 +254,10 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformFloat2(const std::string& name, const glm::vec2& values) const + bool NxOpenGlShader::setUniformFloat2(const std::string &name, const glm::vec2 &values) const { - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformFloat2(name, values)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformFloat2(name, values)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -271,12 +269,10 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformFloat3(const std::string& name, const glm::vec3& values) const + bool NxOpenGlShader::setUniformFloat3(const std::string &name, const glm::vec3 &values) const { - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformFloat3(name, values)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformFloat3(name, values)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -291,10 +287,8 @@ namespace nexo::renderer { bool NxOpenGlShader::setUniformFloat3(const NxShaderUniforms uniform, const glm::vec3 &values) const { const std::string &name = ShaderUniformsName.at(uniform); - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformFloat3(name, values)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformFloat3(name, values)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -306,12 +300,10 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformFloat4(const std::string& name, const glm::vec4& values) const + bool NxOpenGlShader::setUniformFloat4(const std::string &name, const glm::vec4 &values) const { - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformFloat4(name, values)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformFloat4(name, values)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -326,10 +318,8 @@ namespace nexo::renderer { bool NxOpenGlShader::setUniformFloat4(const NxShaderUniforms uniform, const glm::vec4 &values) const { const std::string &name = ShaderUniformsName.at(uniform); - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformFloat4(name, values)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformFloat4(name, values)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -341,12 +331,10 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformMatrix(const std::string& name, const glm::mat4& matrix) const + bool NxOpenGlShader::setUniformMatrix(const std::string &name, const glm::mat4 &matrix) const { - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformMatrix(name, matrix)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformMatrix(name, matrix)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -361,10 +349,8 @@ namespace nexo::renderer { bool NxOpenGlShader::setUniformMatrix(const NxShaderUniforms uniform, const glm::mat4 &matrix) const { const std::string &name = ShaderUniformsName.at(uniform); - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformMatrix(name, matrix)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformMatrix(name, matrix)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -376,12 +362,10 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformInt(const std::string& name, int value) const + bool NxOpenGlShader::setUniformInt(const std::string &name, int value) const { - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformInt(name, value)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformInt(name, value)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -393,12 +377,10 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformBool(const std::string& name, bool value) const + bool NxOpenGlShader::setUniformBool(const std::string &name, bool value) const { - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformBool(name, value)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformBool(name, value)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -413,10 +395,8 @@ namespace nexo::renderer { bool NxOpenGlShader::setUniformInt(const NxShaderUniforms uniform, const int value) const { const std::string &name = ShaderUniformsName.at(uniform); - if (!NxShader::hasUniform(name)) - return false; - if (NxShader::setUniformInt(name, value)) - return true; + if (!NxShader::hasUniform(name)) return false; + if (NxShader::setUniformInt(name, value)) return true; const int location = getUniformLocation(name); if (location == -1) { @@ -430,8 +410,7 @@ namespace nexo::renderer { bool NxOpenGlShader::setUniformIntArray(const std::string &name, const int *values, const unsigned int count) const { - if (!NxShader::hasUniform(name)) - return false; + if (!NxShader::hasUniform(name)) return false; const int location = getUniformLocation(name); if (location == -1) { @@ -443,11 +422,11 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformIntArray(const NxShaderUniforms uniform, const int *values, const unsigned int count) const + bool NxOpenGlShader::setUniformIntArray(const NxShaderUniforms uniform, const int *values, + const unsigned int count) const { const std::string &name = ShaderUniformsName.at(uniform); - if (!NxShader::hasUniform(name)) - return false; + if (!NxShader::hasUniform(name)) return false; const int location = getUniformLocation(name); if (location == -1) { @@ -461,22 +440,19 @@ namespace nexo::renderer { void NxOpenGlShader::bindStorageBuffer(const unsigned int index) const { - if (index > m_storageBuffers.size()) - THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); - m_storageBuffers[index]->bind(); + if (index > m_storageBuffers.size()) THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); + m_storageBuffers[index]->bind(); } void NxOpenGlShader::unbindStorageBuffer(const unsigned int index) const { - if (index > m_storageBuffers.size()) - THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); - m_storageBuffers[index]->unbind(); + if (index > m_storageBuffers.size()) THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); + m_storageBuffers[index]->unbind(); } void NxOpenGlShader::bindStorageBufferBase(const unsigned int index, const unsigned int bindingLocation) const { - if (index > m_storageBuffers.size()) - THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); - m_storageBuffers[index]->bindBase(bindingLocation); + if (index > m_storageBuffers.size()) THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); + m_storageBuffers[index]->bindBase(bindingLocation); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlShader.hpp b/engine/src/renderer/opengl/OpenGlShader.hpp index 7255199d7..14667e17e 100644 --- a/engine/src/renderer/opengl/OpenGlShader.hpp +++ b/engine/src/renderer/opengl/OpenGlShader.hpp @@ -13,81 +13,263 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "renderer/Shader.hpp" #include +#include "renderer/Shader.hpp" namespace nexo::renderer { /** - * @class NxOpenGlShader - * @brief Implementation of the Shader interface using OpenGL. - * - * The `NxOpenGlShader` class provides OpenGL-specific functionality for creating - * and managing shader programs. It supports setting uniform variables and compiling - * shaders from source code or files. - * - * Responsibilities: - * - Compile and link shader programs using OpenGL. - * - Set uniform variables for rendering operations. - * - Manage the lifecycle of OpenGL shader programs. - */ + * @class NxOpenGlShader + * @brief Implementation of the Shader interface using OpenGL. + * + * The `NxOpenGlShader` class provides OpenGL-specific functionality for creating + * and managing shader programs. It supports setting uniform variables and compiling + * shaders from source code or files. + * + * Responsibilities: + * - Compile and link shader programs using OpenGL. + * - Set uniform variables for rendering operations. + * - Manage the lifecycle of OpenGL shader programs. + */ class NxOpenGlShader final : public NxShader { - public: - /** - * @brief Constructs a shader program from a source file. - * - * Reads and compiles a shader program from the specified file path. The file should - * contain `#type` directives to separate shader stages. - * - * @param path The file path to the shader source code. - * - * Throws: - * - `NxFileNotFoundException` if the file cannot be found. - * - `NxShaderCreationFailed` if shader compilation fails. - */ - explicit NxOpenGlShader(const std::string &path); - NxOpenGlShader(std::string name, const std::string_view &vertexSource, const std::string_view &fragmentSource); - ~NxOpenGlShader() override; - - /** - * @brief Activates the shader program in OpenGL. - * - * Binds the shader program for use in subsequent rendering operations. - */ - void bind() const override; - void unbind() const override; - - bool setUniformFloat(const std::string &name, float value) const override; - bool setUniformFloat2(const std::string &name, const glm::vec2 &values) const override; - bool setUniformFloat3(const std::string &name, const glm::vec3 &values) const override; - bool setUniformFloat4(const std::string &name, const glm::vec4 &values) const override; - bool setUniformMatrix(const std::string &name, const glm::mat4 &matrix) const override; - bool setUniformBool(const std::string &name, bool value) const override; - bool setUniformInt(const std::string &name, int value) const override; - bool setUniformIntArray(const std::string &name, const int *values, unsigned int count) const override; - - bool setUniformFloat(NxShaderUniforms uniform, float value) const override; - bool setUniformFloat3(NxShaderUniforms uniform, const glm::vec3 &values) const override; - bool setUniformFloat4(NxShaderUniforms uniform, const glm::vec4 &values) const override; - bool setUniformMatrix(NxShaderUniforms uniform, const glm::mat4 &matrix) const override; - bool setUniformInt(NxShaderUniforms uniform, int value) const override; - bool setUniformIntArray(NxShaderUniforms uniform, const int *values, unsigned int count) const override; - - void bindStorageBuffer(unsigned int index) const override; - void bindStorageBufferBase(unsigned int index, unsigned int bindingLocation) const override; - void unbindStorageBuffer(unsigned int index) const override; - - [[nodiscard]] const std::string &getName() const override { return m_name; }; - unsigned int getProgramId() const override { return m_id; }; - - private: - std::string m_name; - unsigned int m_id = 0; - - static std::unordered_map preProcess(const std::string_view &src, const std::string &filePath); - void compile(const std::unordered_map &shaderSources); - void setupUniformLocations(); - int getUniformLocation(const std::string& name) const; + public: + /** + * @brief Constructs a shader program from a source file. + * + * Reads and compiles a shader program from the specified file path. The file should + * contain `#type` directives to separate shader stages. + * + * @param path The file path to the shader source code. + * + * Throws: + * - `NxFileNotFoundException` if the file cannot be found. + * - `NxShaderCreationFailed` if shader compilation fails. + */ + explicit NxOpenGlShader(const std::string &path); + + /** + * @brief Constructs a shader program from vertex and fragment source code. + * + * Compiles and links a shader program using the provided vertex and fragment shader + * source code strings. + * + * @param name A name identifier for the shader program. + * @param vertexSource The source code for the vertex shader. + * @param fragmentSource The source code for the fragment shader. + * + * Throws: + * - `NxShaderCreationFailed` if shader compilation fails. + */ + NxOpenGlShader(std::string name, const std::string_view &vertexSource, const std::string_view &fragmentSource); + + ~NxOpenGlShader() override; + + /** + * @brief Activates the shader program in OpenGL. + * + * Binds the shader program for use in subsequent rendering operations. + */ + void bind() const override; + /** + * @brief Deactivates the shader program in OpenGL. + * + * Unbinds the shader program, reverting to fixed-function pipeline or no shader. + */ + void unbind() const override; + + /** + * @brief Sets a float uniform variable in the shader program. + * @param name The name of the uniform variable. + * @param value The float value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformFloat(const std::string &name, float value) const override; + + /** + * @brief Sets a vec2 (2-component float vector) uniform variable in the shader program. + * @param name The name of the uniform variable. + * @param values The glm::vec2 value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformFloat2(const std::string &name, const glm::vec2 &values) const override; + + /** + * @brief Sets a vec3 (3-component float vector) uniform variable in the shader program. + * @param name The name of the uniform variable. + * @param values The glm::vec3 value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformFloat3(const std::string &name, const glm::vec3 &values) const override; + + /** + * @brief Sets a vec4 (4-component float vector) uniform variable in the shader program. + * @param name The name of the uniform variable. + * @param values The glm::vec4 value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformFloat4(const std::string &name, const glm::vec4 &values) const override; + + /** + * @brief Sets a mat4 (4x4 float matrix) uniform variable in the shader program. + * @param name The name of the uniform variable. + * @param matrix The glm::mat4 value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformMatrix(const std::string &name, const glm::mat4 &matrix) const override; + + /** + * @brief Sets a boolean uniform variable in the shader program. + * @param name The name of the uniform variable. + * @param value The boolean value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformBool(const std::string &name, bool value) const override; + + /** + * @brief Sets an integer uniform variable in the shader program. + * @param name The name of the uniform variable. + * @param value The integer value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformInt(const std::string &name, int value) const override; + + /** + * @brief Sets an array of integer uniform variables in the shader program. + * @param name The name of the uniform variable. + * @param values Pointer to the array of integer values. + * @param count The number of elements in the array. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformIntArray(const std::string &name, const int *values, unsigned int count) const override; + + /** + * @brief Sets a float uniform variable using a predefined uniform enum. + * @param uniform The predefined uniform identifier. + * @param value The float value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformFloat(NxShaderUniforms uniform, float value) const override; + + /** + * @brief Sets a vec3 (3-component float vector) uniform variable using a predefined uniform enum. + * @param uniform The predefined uniform identifier. + * @param values The glm::vec3 value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformFloat3(NxShaderUniforms uniform, const glm::vec3 &values) const override; + + /** + * @brief Sets a vec4 (4-component float vector) uniform variable using a predefined uniform enum. + * @param uniform The predefined uniform identifier. + * @param values The glm::vec4 value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformFloat4(NxShaderUniforms uniform, const glm::vec4 &values) const override; + + /** + * @brief Sets a mat4 (4x4 float matrix) uniform variable using a predefined uniform enum. + * @param uniform The predefined uniform identifier. + * @param matrix The glm::mat4 value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformMatrix(NxShaderUniforms uniform, const glm::mat4 &matrix) const override; + + /** + * @brief Sets an integer uniform variable using a predefined uniform enum. + * @param uniform The predefined uniform identifier. + * @param value The integer value to set. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformInt(NxShaderUniforms uniform, int value) const override; + + /** + * @brief Sets an array of integer uniform variables using a predefined uniform enum. + * @param uniform The predefined uniform identifier. + * @param values Pointer to the array of integer values. + * @param count The number of elements in the array. + * @return True if the uniform was set successfully, false otherwise. + */ + bool setUniformIntArray(NxShaderUniforms uniform, const int *values, unsigned int count) const override; + + /** + * @brief Binds a storage buffer to the shader program at the specified index. + * @param index The index of the storage buffer to bind. + */ + void bindStorageBuffer(unsigned int index) const override; + + /** + * @brief Binds a storage buffer to a specific binding location in the shader program. + * @param index The index of the storage buffer. + * @param bindingLocation The binding location in the shader. + */ + void bindStorageBufferBase(unsigned int index, unsigned int bindingLocation) const override; + + /** + * @brief Unbinds a storage buffer from the shader program at the specified index. + * @param index The index of the storage buffer to unbind. + */ + void unbindStorageBuffer(unsigned int index) const override; + + /** + * @brief Retrieves the name of the shader program. + * @return The name of the shader program as a constant reference to a string. + */ + [[nodiscard]] const std::string &getName() const override + { + return m_name; + } + + /** + * @brief Retrieves the OpenGL program ID of the shader. + * @return The OpenGL program ID as an unsigned integer. + */ + unsigned int getProgramId() const override + { + return m_id; + } + + private: + std::string m_name; + unsigned int m_id = 0; + + /** + * @brief Preprocesses shader source code to separate different shader stages. + * + * Parses the provided shader source code string to identify and extract + * individual shader stages (e.g., vertex, fragment) based on `#type` directives. + * + * @param src The complete shader source code as a string view. + * @param filePath The file path of the shader source (for error reporting). + * @return A map associating OpenGL shader types (GLenum) with their corresponding source code strings. + * + * Throws: + * - `NxShaderCreationFailed` if there are syntax errors or invalid shader types. + */ + static std::unordered_map preProcess(const std::string_view &src, + const std::string &filePath); + + /** + * @brief Compiles and links OpenGL shaders from the provided sources. + * + * @param shaderSources Map associating OpenGL shader types (GLenum) with their source code. + * @throws NxShaderCreationFailed if compilation or linking fails. + */ + void compile(const std::unordered_map &shaderSources); + + /** + * @brief Prepares and caches the locations of shader uniforms. + * + * This method should be called after the shader program has been compiled. + */ + void setupUniformLocations(); + + /** + * @brief Retrieves the location of a uniform variable in the shader program. + * + * @param name The name of the uniform variable. + * @return The location of the uniform, or -1 if not found. + */ + int getUniformLocation(const std::string &name) const; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlShaderReflection.cpp b/engine/src/renderer/opengl/OpenGlShaderReflection.cpp index d4f4d6b06..d8fe8d14a 100644 --- a/engine/src/renderer/opengl/OpenGlShaderReflection.cpp +++ b/engine/src/renderer/opengl/OpenGlShaderReflection.cpp @@ -13,8 +13,8 @@ /////////////////////////////////////////////////////////////////////////////// #include "OpenGlShaderReflection.hpp" -#include "Logger.hpp" #include +#include "Logger.hpp" namespace nexo::renderer { @@ -42,14 +42,14 @@ namespace nexo::renderer { int nameLength; glGetActiveUniform(programId, i, maxNameLength, &nameLength, &info.size, &info.type, nameBuffer.data()); - info.name = std::string(nameBuffer.data(), nameLength); + info.name = std::string(nameBuffer.data(), nameLength); info.location = glGetUniformLocation(programId, info.name.c_str()); // Store the uniform with original name uniforms[info.name] = info; // For array uniforms, also store the base name (without [0]) - std::string baseName = info.name; + std::string baseName = info.name; const size_t bracketPos = baseName.find('['); if (bracketPos != std::string::npos) { baseName = baseName.substr(0, bracketPos); @@ -58,8 +58,8 @@ namespace nexo::renderer { if (!uniforms.contains(baseName)) { // For arrays, the base name location is the same as the first element UniformInfo baseInfo = info; - baseInfo.name = baseName; - uniforms[baseName] = baseInfo; + baseInfo.name = baseName; + uniforms[baseName] = baseInfo; } } } @@ -84,7 +84,7 @@ namespace nexo::renderer { int nameLength; glGetActiveAttrib(programId, i, maxNameLength, &nameLength, &info.size, &info.type, nameBuffer.data()); - info.name = std::string(nameBuffer.data(), nameLength); + info.name = std::string(nameBuffer.data(), nameLength); info.location = glGetAttribLocation(programId, info.name.c_str()); attributes[info.location] = info; @@ -105,4 +105,4 @@ namespace nexo::renderer { return required; } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlShaderReflection.hpp b/engine/src/renderer/opengl/OpenGlShaderReflection.hpp index 3dbd1b626..b8cf02c55 100644 --- a/engine/src/renderer/opengl/OpenGlShaderReflection.hpp +++ b/engine/src/renderer/opengl/OpenGlShaderReflection.hpp @@ -14,17 +14,34 @@ #pragma once #include -#include #include +#include #include "Shader.hpp" namespace nexo::renderer { class ShaderReflection { - public: + public: + /** + * \brief Reflects all active uniforms in the given OpenGL shader program. + * \param programId The OpenGL shader program ID. + * \return A map from uniform name to UniformInfo structure. + */ static std::unordered_map reflectUniforms(unsigned int programId); + + /** + * \brief Reflects all active attributes in the given OpenGL shader program. + * \param programId The OpenGL shader program ID. + * \return A map from attribute location to AttributeInfo structure. + */ static std::unordered_map reflectAttributes(unsigned int programId); + + /** + * \brief Gets the list of required attribute names for the given OpenGL shader program. + * \param programId The OpenGL shader program ID. + * \return A vector of required attribute names. + */ static std::vector getRequiredAttributes(unsigned int programId); }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp index 230365330..e23a371d3 100644 --- a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp @@ -12,37 +12,37 @@ // /////////////////////////////////////////////////////////////////////////////// -#include #include "OpenGlShaderStorageBuffer.hpp" +#include namespace nexo::renderer { - NxOpenGlShaderStorageBuffer::NxOpenGlShaderStorageBuffer(const unsigned int size) - { - glCreateBuffers(1, &m_id); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); - glBufferData(GL_SHADER_STORAGE_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); - } + NxOpenGlShaderStorageBuffer::NxOpenGlShaderStorageBuffer(const unsigned int size) + { + glCreateBuffers(1, &m_id); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); + glBufferData(GL_SHADER_STORAGE_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + } - void NxOpenGlShaderStorageBuffer::bind() const - { - glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); - } + void NxOpenGlShaderStorageBuffer::bind() const + { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); + } - void NxOpenGlShaderStorageBuffer::bindBase(const unsigned int bindingLocation) const - { - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingLocation, m_id); - } + void NxOpenGlShaderStorageBuffer::bindBase(const unsigned int bindingLocation) const + { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingLocation, m_id); + } - void NxOpenGlShaderStorageBuffer::unbind() const - { - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); - } + void NxOpenGlShaderStorageBuffer::unbind() const + { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + } - void NxOpenGlShaderStorageBuffer::setData(void* data, size_t size) - { - glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); - glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, size, data); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); - } -} + void NxOpenGlShaderStorageBuffer::setData(void* data, size_t size) + { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, size, data); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + } +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp index 14aaac4b7..df081ae83 100644 --- a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp @@ -16,20 +16,52 @@ #include "renderer/ShaderStorageBuffer.hpp" namespace nexo::renderer { - class NxOpenGlShaderStorageBuffer final : public NxShaderStorageBuffer { - public: - explicit NxOpenGlShaderStorageBuffer(unsigned int size); - ~NxOpenGlShaderStorageBuffer() override = default; + class NxOpenGlShaderStorageBuffer final : public NxShaderStorageBuffer { + public: + /** + * @brief Constructs an OpenGL Shader Storage Buffer Object (SSBO) with the specified size. + * @param size The size in bytes of the buffer to allocate. + */ + explicit NxOpenGlShaderStorageBuffer(unsigned int size); - void bind() const override; - void bindBase(unsigned int bindingLocation) const override; - void unbind() const override; + /** + * @brief Destructor for the OpenGL SSBO. + */ + ~NxOpenGlShaderStorageBuffer() override = default; - void setData(void* data, size_t size) override; + /** + * @brief Binds the SSBO to the current OpenGL context. + */ + void bind() const override; - [[nodiscard]] unsigned int getId() const override { return m_id; }; + /** + * @brief Binds the SSBO to a specific binding location. + * @param bindingLocation The binding point index. + */ + void bindBase(unsigned int bindingLocation) const override; - private: - unsigned int m_id{}; - }; -} + /** + * @brief Unbinds the SSBO from the current OpenGL context. + */ + void unbind() const override; + + /** + * @brief Sets the data of the SSBO. + * @param data Pointer to the data to upload. + * @param size Size in bytes of the data. + */ + void setData(void* data, size_t size) override; + + /** + * @brief Returns the OpenGL buffer object ID. + * @return The OpenGL buffer ID. + */ + [[nodiscard]] unsigned int getId() const override + { + return m_id; + }; + + private: + unsigned int m_id{}; + }; +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index 1a0a171ab..d50775cfe 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -21,35 +21,36 @@ namespace nexo::renderer { - NxOpenGlTexture2D::NxOpenGlTexture2D(const unsigned int width, const unsigned int height) : m_width(width), m_height(height) + NxOpenGlTexture2D::NxOpenGlTexture2D(const unsigned int width, const unsigned int height) + : m_width(width), m_height(height) { createOpenGLTexture(nullptr, width, height, GL_RGBA8, GL_RGBA); } NxOpenGlTexture2D::NxOpenGlTexture2D(const uint8_t *buffer, const unsigned int width, const unsigned int height, - const NxTextureFormat format) : m_width(width), m_height(height) + const NxTextureFormat format) + : m_width(width), m_height(height) { - if (!buffer) - THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Buffer is null"); + if (!buffer) THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Buffer is null"); GLint internalFormat = 0; - GLenum dataFormat = 0; + GLenum dataFormat = 0; switch (format) { [[likely]] case NxTextureFormat::RGBA8: internalFormat = GL_RGBA8; - dataFormat = GL_RGBA; + dataFormat = GL_RGBA; break; [[likely]] case NxTextureFormat::RGB8: internalFormat = GL_RGB8; - dataFormat = GL_RGB; + dataFormat = GL_RGB; break; case NxTextureFormat::RG8: internalFormat = GL_RG8; - dataFormat = GL_RG; + dataFormat = GL_RG; break; case NxTextureFormat::R8: internalFormat = GL_R8; - dataFormat = GL_RED; + dataFormat = GL_RED; break; default: @@ -59,21 +60,19 @@ namespace nexo::renderer { createOpenGLTexture(buffer, width, height, internalFormat, dataFormat); } - NxOpenGlTexture2D::NxOpenGlTexture2D(const std::string &path) - : m_path(path) + NxOpenGlTexture2D::NxOpenGlTexture2D(const std::string &path) : m_path(path) { - int width = 0; - int height = 0; + int width = 0; + int height = 0; int channels = 0; - //TODO: Set this conditionnaly based on the type of texture + // TODO: Set this conditionnaly based on the type of texture stbi_set_flip_vertically_on_load(1); stbi_uc *data = stbi_load(path.c_str(), &width, &height, &channels, 0); - if (!data) - THROW_EXCEPTION(NxFileNotFoundException, path); + if (!data) THROW_EXCEPTION(NxFileNotFoundException, path); try { ingestDataFromStb(data, width, height, channels, path); - } catch (const Exception&) { + } catch (const Exception &) { stbi_image_free(data); throw; } @@ -85,20 +84,19 @@ namespace nexo::renderer { glDeleteTextures(1, &m_id); } - NxOpenGlTexture2D::NxOpenGlTexture2D(const uint8_t* buffer, const unsigned int len) + NxOpenGlTexture2D::NxOpenGlTexture2D(const uint8_t *buffer, const unsigned int len) { - int width = 0; - int height = 0; + int width = 0; + int height = 0; int channels = 0; - //TODO: Set this conditionnaly based on the type of texture - //stbi_set_flip_vertically_on_load(1); + // TODO: Set this conditionnaly based on the type of texture + // stbi_set_flip_vertically_on_load(1); stbi_uc *data = stbi_load_from_memory(buffer, static_cast(len), &width, &height, &channels, 0); - if (!data) - THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", channels, "(buffer)"); + if (!data) THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", channels, "(buffer)"); try { ingestDataFromStb(data, width, height, channels, "(buffer)"); - } catch (const Exception&) { + } catch (const Exception &) { stbi_image_free(data); throw; } @@ -114,57 +112,61 @@ namespace nexo::renderer { void NxOpenGlTexture2D::setData(void *data, const size_t size) { - if (const size_t expectedSize = static_cast(m_width) * m_height * (m_dataFormat == GL_RGBA ? 4 : 3); size != expectedSize) + if (const size_t expectedSize = static_cast(m_width) * m_height * (m_dataFormat == GL_RGBA ? 4 : 3); + size != expectedSize) THROW_EXCEPTION(NxTextureSizeMismatch, "OPENGL", size, expectedSize); glBindTexture(GL_TEXTURE_2D, m_id); // Update the entire texture with new data - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast(m_width), static_cast(m_height), m_dataFormat, GL_UNSIGNED_BYTE, data); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast(m_width), static_cast(m_height), m_dataFormat, + GL_UNSIGNED_BYTE, data); glBindTexture(GL_TEXTURE_2D, 0); } - void NxOpenGlTexture2D::ingestDataFromStb(const uint8_t* data, const int width, const int height, const int channels, - const std::string& debugPath) + void NxOpenGlTexture2D::ingestDataFromStb(const uint8_t *data, const int width, const int height, + const int channels, const std::string &debugPath) { GLint internalFormat = 0; - GLenum dataFormat = 0; + GLenum dataFormat = 0; switch (channels) { [[likely]] case 4: // red, green, blue, alpha internalFormat = GL_RGBA8; - dataFormat = GL_RGBA; + dataFormat = GL_RGBA; break; [[likely]] case 3: // red, green, blue internalFormat = GL_RGB8; - dataFormat = GL_RGB; + dataFormat = GL_RGB; break; case 2: // grey, alpha internalFormat = GL_RG16; - dataFormat = GL_RG16; + dataFormat = GL_RG16; break; case 1: // grey internalFormat = GL_R8; - dataFormat = GL_RED; + dataFormat = GL_RED; break; default: THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", channels, debugPath); } - createOpenGLTexture(data, static_cast(width), static_cast(height), internalFormat, dataFormat); + createOpenGLTexture(data, static_cast(width), static_cast(height), internalFormat, + dataFormat); } - void NxOpenGlTexture2D::createOpenGLTexture(const uint8_t* buffer, const unsigned int width, const unsigned int height, - const GLint internalFormat, const GLenum dataFormat) + void NxOpenGlTexture2D::createOpenGLTexture(const uint8_t *buffer, const unsigned int width, + const unsigned int height, const GLint internalFormat, + const GLenum dataFormat) { const unsigned int maxTextureSize = getMaxTextureSize(); if (width > maxTextureSize || height > maxTextureSize) THROW_EXCEPTION(NxTextureInvalidSize, "OPENGL", width, height, maxTextureSize); - const auto glWidth = static_cast(width); + const auto glWidth = static_cast(width); const auto glHeight = static_cast(height); m_internalFormat = internalFormat; - m_dataFormat = dataFormat; - m_width = width; - m_height = height; + m_dataFormat = dataFormat; + m_width = width; + m_height = height; glGenTextures(1, &m_id); glBindTexture(GL_TEXTURE_2D, m_id); @@ -188,4 +190,4 @@ namespace nexo::renderer { glBindTexture(GL_TEXTURE_2D, 0); } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.hpp b/engine/src/renderer/opengl/OpenGlTexture2D.hpp index cc6d67e93..241b60e63 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.hpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.hpp @@ -14,219 +14,240 @@ #pragma once -#include "renderer/Texture.hpp" #include +#include "renderer/Texture.hpp" namespace nexo::renderer { /** - * @class NxOpenGlTexture2D - * @brief OpenGL-specific implementation of the `NxTexture2D` class. - * - * The `NxOpenGlTexture2D` class manages 2D textures in an OpenGL rendering context. - * It supports texture creation, data uploading, and binding/unbinding operations. - * - * Responsibilities: - * - Create textures using OpenGL APIs. - * - Load texture data from files or raw memory. - * - Provide texture binding and unbinding functionality. - */ + * @class NxOpenGlTexture2D + * @brief OpenGL-specific implementation of the `NxTexture2D` class. + * + * The `NxOpenGlTexture2D` class manages 2D textures in an OpenGL rendering context. + * It supports texture creation, data uploading, and binding/unbinding operations. + * + * Responsibilities: + * - Create textures using OpenGL APIs. + * - Load texture data from files or raw memory. + * - Provide texture binding and unbinding functionality. + */ class NxOpenGlTexture2D final : public NxTexture2D { - public: - ~NxOpenGlTexture2D() override; - - /** - * @brief Loads an OpenGL 2D texture from an image file. - * - * Reads texture data from the specified file path and uploads it to the GPU. - * Supports common image formats (e.g., PNG, JPG). Automatically detects the - * internal and data formats based on the number of channels in the image. - * - * @param path The file path to the texture image. - * @throw NxFileNotFoundException If the file cannot be found. - * @throw NxTextureUnsupportedFormat If the image format is unsupported. - * - * Example: - * ```cpp - * auto texture = std::make_shared("textures/wood.jpg"); - * ``` - */ - explicit NxOpenGlTexture2D(const std::string &path); - - /** - * @brief Creates a blank OpenGL 2D texture with the specified dimensions. - * - * Initializes a texture with the given width and height, using default OpenGL - * parameters for filtering and wrapping. - * - * @param width The width of the texture in pixels. - * @param height The height of the texture in pixels. - * - * Example: - * ```cpp - * auto texture = std::make_shared(256, 256); - * ``` - */ - NxOpenGlTexture2D(unsigned int width, unsigned int height); - - /** - * @brief Creates an OpenGL 2D texture from raw pixel data. - * - * Creates a texture from a raw pixel buffer with the specified dimensions and format. - * This is useful for creating textures from procedurally generated data or when working - * with raw pixel data from other sources. - * - * @param buffer Pointer to the raw pixel data. The buffer should contain pixel data - * in a format that matches the specified NxTextureFormat. The data consists - * of height scanlines of width pixels, with each pixel consisting of N components - * (where N depends on the format). The first pixel pointed to is bottom-left-most - * in the image. There is no padding between image scanlines or between pixels. - * Each component is an 8-bit unsigned value (uint8_t). - * @param width The width of the texture in pixels. Must not exceed the maximum texture - * size supported by the GPU. - * @param height The height of the texture in pixels. Must not exceed the maximum texture - * size supported by the GPU. - * @param format The format of the pixel data, which determines the number of components - * per pixel and the internal OpenGL representation: - * - NxTextureFormat::R8: Single channel (GL_R8/GL_RED) - * - NxTextureFormat::RG8: Two channels (GL_RG8/GL_RG) - * - NxTextureFormat::RGB8: Three channels (GL_RGB8/GL_RGB) - * - NxTextureFormat::RGBA8: Four channels (GL_RGBA8/GL_RGBA) - * @return A shared pointer to the created NxTexture2D instance. - * - * @throws NxInvalidValue If the buffer is null. - * @throws NxTextureUnsupportedFormat If the specified format is not supported. - * @throws NxTextureInvalidSize If the dimensions exceed the maximum texture size. - * - * Example: - * ```cpp - * // Create a 256x256 RGBA texture with custom data - * std::vector pixelData(256 * 256 * 4, 255); // 4 components (RGBA) - * auto texture = std::make_shared(pixelData.data(), 256, 256, NxTextureFormat::RGBA8); - * ``` - */ - NxOpenGlTexture2D(const uint8_t *buffer, unsigned int width, unsigned int height, NxTextureFormat format); - - /** - * @brief Creates an OpenGL 2D texture from a file in memory (raw content). - * - * Loads the texture data from the specified memory buffer. The buffer must contain - * image data in a supported format (e.g., PNG, JPG). The texture will be ready - * for rendering after creation. - * - * @param buffer The memory buffer containing the texture image data. - * @param len The length of the memory buffer in bytes. - * - * Example: - * ```cpp - * std::vector imageData = ...; // Load image data into a buffer - * auto texture = std::make_shared(imageData.data(), imageData.size()); - * ``` - */ - NxOpenGlTexture2D(const uint8_t *buffer, unsigned int len); - - [[nodiscard]] unsigned int getWidth() const override {return m_width;}; - [[nodiscard]] unsigned int getHeight() const override {return m_height;}; - - /** - * @brief Retrieves the maximum texture size supported by the OpenGL context. - * - * Queries the OpenGL context to determine the largest supported dimension for - * a texture. Useful for ensuring texture dimensions are within supported limits. - * - * @return The maximum texture size in pixels. - */ - [[nodiscard]] unsigned int getMaxTextureSize() const override; - - [[nodiscard]] unsigned int getId() const override {return m_id;}; - - /** - * @brief Binds the texture to a specified texture slot. - * - * Activates the given texture slot and binds this texture to it. This makes the - * texture available for use in subsequent rendering operations. - * - * @param slot The texture slot to bind to (default is 0). - * - * Example: - * ```cpp - * texture->bind(1); // Bind to texture slot 1 - * ``` - */ - void bind(unsigned int slot = 0) const override; - - /** - * @brief Unbinds the texture from a specified texture slot. - * - * Deactivates the given texture slot, unbinding any texture associated with it. - * - * @param slot The texture slot to unbind from (default is 0). - * - * Example: - * ```cpp - * texture->unbind(1); // Unbind from texture slot 1 - * ``` - */ - void unbind(unsigned int slot = 0) const override; - - /** - * @brief Updates the texture data. - * - * Uploads new pixel data to the GPU for the texture. The data must match the - * texture's dimensions and format. - * - * @param data A pointer to the pixel data. - * @param size The size of the data in bytes. - * @throw NxTextureSizeMismatch If the size of the data does not match the texture's dimensions. - * - * Example: - * ```cpp - * unsigned char pixels[256 * 256 * 4] = { ... }; // RGBA data - * texture->setData(pixels, sizeof(pixels)); - * ``` - */ - void setData(void *data, size_t size) override; - private: - /** - * @brief Ingest and load texture data from stb_image buffer. - * - * @param data Pointer to the stb_image buffer. - * @param width Width of the texture. - * @param height Height of the texture. - * @param channels Number of channels in the image data. - * @param debugPath Path (of potential file) for error reporting. (Default: "(buffer)") - * - * @warning data MUST be a valid pointer to a buffer allocated by stb_image. - * - * Example: - * ```cpp - * int width, height, channels; - * stbi_uc *data = stbi_load(path.c_str(), &width, &height, &channels, 0); - * ingestDataFromStb(data, width, height, channels, path); - * ``` - */ - void ingestDataFromStb(const uint8_t *data, int width, int height, int channels, const std::string& debugPath = "(buffer)"); - - /** - * @brief Creates an OpenGL texture with the specified parameters. - * - * @param buffer Pointer to the texture data (can be nullptr, as per OpenGL spec). - * @param width Width of the texture. - * @param height Height of the texture. - * @param internalFormat Internal format of the texture. - * @param dataFormat Data format of the texture. - * - * Example: - * ```cpp - * createOpenGLTexture(buffer, width, height, GL_RGBA8, GL_RGBA); - * ``` - */ - void createOpenGLTexture(const uint8_t* buffer, unsigned int width, unsigned int height, GLint internalFormat, GLenum dataFormat); - - std::string m_path; - unsigned int m_width{}; - unsigned int m_height{}; - unsigned int m_id{}; - GLint m_internalFormat{}; - GLenum m_dataFormat{}; + public: + ~NxOpenGlTexture2D() override; + + /** + * @brief Loads an OpenGL 2D texture from an image file. + * + * Reads texture data from the specified file path and uploads it to the GPU. + * Supports common image formats (e.g., PNG, JPG). Automatically detects the + * internal and data formats based on the number of channels in the image. + * + * @param path The file path to the texture image. + * @throw NxFileNotFoundException If the file cannot be found. + * @throw NxTextureUnsupportedFormat If the image format is unsupported. + * + * Example: + * ```cpp + * auto texture = std::make_shared("textures/wood.jpg"); + * ``` + */ + explicit NxOpenGlTexture2D(const std::string &path); + + /** + * @brief Creates a blank OpenGL 2D texture with the specified dimensions. + * + * Initializes a texture with the given width and height, using default OpenGL + * parameters for filtering and wrapping. + * + * @param width The width of the texture in pixels. + * @param height The height of the texture in pixels. + * + * Example: + * ```cpp + * auto texture = std::make_shared(256, 256); + * ``` + */ + NxOpenGlTexture2D(unsigned int width, unsigned int height); + + /** + * @brief Creates an OpenGL 2D texture from raw pixel data. + * + * Creates a texture from a raw pixel buffer with the specified dimensions and format. + * This is useful for creating textures from procedurally generated data or when working + * with raw pixel data from other sources. + * + * @param buffer Pointer to the raw pixel data. The buffer should contain pixel data + * in a format that matches the specified NxTextureFormat. The data consists + * of height scanlines of width pixels, with each pixel consisting of N components + * (where N depends on the format). The first pixel pointed to is bottom-left-most + * in the image. There is no padding between image scanlines or between pixels. + * Each component is an 8-bit unsigned value (uint8_t). + * @param width The width of the texture in pixels. Must not exceed the maximum texture + * size supported by the GPU. + * @param height The height of the texture in pixels. Must not exceed the maximum texture + * size supported by the GPU. + * @param format The format of the pixel data, which determines the number of components + * per pixel and the internal OpenGL representation: + * - NxTextureFormat::R8: Single channel (GL_R8/GL_RED) + * - NxTextureFormat::RG8: Two channels (GL_RG8/GL_RG) + * - NxTextureFormat::RGB8: Three channels (GL_RGB8/GL_RGB) + * - NxTextureFormat::RGBA8: Four channels (GL_RGBA8/GL_RGBA) + * @return A shared pointer to the created NxTexture2D instance. + * + * @throws NxInvalidValue If the buffer is null. + * @throws NxTextureUnsupportedFormat If the specified format is not supported. + * @throws NxTextureInvalidSize If the dimensions exceed the maximum texture size. + * + * Example: + * ```cpp + * // Create a 256x256 RGBA texture with custom data + * std::vector pixelData(256 * 256 * 4, 255); // 4 components (RGBA) + * auto texture = std::make_shared(pixelData.data(), 256, 256, NxTextureFormat::RGBA8); + * ``` + */ + NxOpenGlTexture2D(const uint8_t *buffer, unsigned int width, unsigned int height, NxTextureFormat format); + + /** + * @brief Creates an OpenGL 2D texture from a file in memory (raw content). + * + * Loads the texture data from the specified memory buffer. The buffer must contain + * image data in a supported format (e.g., PNG, JPG). The texture will be ready + * for rendering after creation. + * + * @param buffer The memory buffer containing the texture image data. + * @param len The length of the memory buffer in bytes. + * + * Example: + * ```cpp + * // Load image data into a buffer + * std::vector imageData = ...; + * auto texture = std::make_shared(imageData.data(), imageData.size()); + * ``` + */ + NxOpenGlTexture2D(const uint8_t *buffer, unsigned int len); + + [[nodiscard]] unsigned int getWidth() const override + { + return m_width; + } + [[nodiscard]] unsigned int getHeight() const override + { + return m_height; + } + + /** + * @brief Retrieves the maximum texture size supported by the OpenGL context. + * + * Queries the OpenGL context to determine the largest supported dimension for + * a texture. Useful for ensuring texture dimensions are within supported limits. + * + * @return The maximum texture size in pixels. + */ + [[nodiscard]] unsigned int getMaxTextureSize() const override; + + /** + * @brief Retrieves the OpenGL texture ID. + * + * Returns the unique identifier assigned to this texture by OpenGL. This ID + * is used for binding and managing the texture within the OpenGL context. + * + * @return The OpenGL texture ID as an unsigned integer. + */ + [[nodiscard]] unsigned int getId() const override + { + return m_id; + } + + /** + * @brief Binds the texture to a specified texture slot. + * + * Activates the given texture slot and binds this texture to it. This makes the + * texture available for use in subsequent rendering operations. + * + * @param slot The texture slot to bind to (default is 0). + * + * Example: + * ```cpp + * texture->bind(1); // Bind to texture slot 1 + * ``` + */ + void bind(unsigned int slot = 0) const override; + + /** + * @brief Unbinds the texture from a specified texture slot. + * + * Deactivates the given texture slot, unbinding any texture associated with it. + * + * @param slot The texture slot to unbind from (default is 0). + * + * Example: + * ```cpp + * texture->unbind(1); // Unbind from texture slot 1 + * ``` + */ + void unbind(unsigned int slot = 0) const override; + + /** + * @brief Updates the texture data. + * + * Uploads new pixel data to the GPU for the texture. The data must match the + * texture's dimensions and format. + * + * @param data A pointer to the pixel data. + * @param size The size of the data in bytes. + * @throw NxTextureSizeMismatch If the size of the data does not match the texture's dimensions. + * + * Example: + * ```cpp + * unsigned char pixels[256 * 256 * 4] = { ... }; // RGBA data + * texture->setData(pixels, sizeof(pixels)); + * ``` + */ + void setData(void *data, size_t size) override; + + private: + /** + * @brief Ingest and load texture data from stb_image buffer. + * + * @param data Pointer to the stb_image buffer. + * @param width Width of the texture. + * @param height Height of the texture. + * @param channels Number of channels in the image data. + * @param debugPath Path (of potential file) for error reporting. (Default: "(buffer)") + * + * @warning data MUST be a valid pointer to a buffer allocated by stb_image. + * + * Example: + * ```cpp + * int width, height, channels; + * stbi_uc *data = stbi_load(path.c_str(), &width, &height, &channels, 0); + * ingestDataFromStb(data, width, height, channels, path); + * ``` + */ + void ingestDataFromStb(const uint8_t *data, int width, int height, int channels, + const std::string &debugPath = "(buffer)"); + + /** + * @brief Creates an OpenGL texture with the specified parameters. + * + * @param buffer Pointer to the texture data (can be nullptr, as per OpenGL spec). + * @param width Width of the texture. + * @param height Height of the texture. + * @param internalFormat Internal format of the texture. + * @param dataFormat Data format of the texture. + * + * Example: + * ```cpp + * createOpenGLTexture(buffer, width, height, GL_RGBA8, GL_RGBA); + * ``` + */ + void createOpenGLTexture(const uint8_t *buffer, unsigned int width, unsigned int height, GLint internalFormat, + GLenum dataFormat); + + std::string m_path; + unsigned int m_width{}; + unsigned int m_height{}; + unsigned int m_id{}; + GLint m_internalFormat{}; + GLenum m_dataFormat{}; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlVertexArray.cpp b/engine/src/renderer/opengl/OpenGlVertexArray.cpp index d5a2c61cc..3148dc242 100644 --- a/engine/src/renderer/opengl/OpenGlVertexArray.cpp +++ b/engine/src/renderer/opengl/OpenGlVertexArray.cpp @@ -21,45 +21,62 @@ namespace nexo::renderer { /** - * @brief Converts an `NxShaderDataType` enum value to the corresponding OpenGL type. - * - * @param type The shader data type to convert. - * @return The OpenGL equivalent type (e.g., `GL_FLOAT`). - * - * Example: - * ```cpp - * GLenum glType = nxShaderDataTypeToOpenGltype(NxShaderDataType::FLOAT3); - * ``` - */ + * @brief Converts an `NxShaderDataType` enum value to the corresponding OpenGL type. + * + * @param type The shader data type to convert. + * @return The OpenGL equivalent type (e.g., `GL_FLOAT`). + * + * Example: + * ```cpp + * GLenum glType = nxShaderDataTypeToOpenGltype(NxShaderDataType::FLOAT3); + * ``` + **/ static GLenum nxShaderDataTypeToOpenGltype(const NxShaderDataType type) { - switch (type) - { - case NxShaderDataType::FLOAT: return GL_FLOAT; - case NxShaderDataType::FLOAT2: return GL_FLOAT; - case NxShaderDataType::FLOAT3: return GL_FLOAT; - case NxShaderDataType::FLOAT4: return GL_FLOAT; - case NxShaderDataType::INT: return GL_INT; - case NxShaderDataType::INT2: return GL_INT; - case NxShaderDataType::INT3: return GL_INT; - case NxShaderDataType::INT4: return GL_INT; - case NxShaderDataType::MAT3: return GL_FLOAT; - case NxShaderDataType::MAT4: return GL_FLOAT; - case NxShaderDataType::BOOL: return GL_BOOL; - default: return 0; + switch (type) { + using enum NxShaderDataType; + case FLOAT: + case FLOAT2: + case FLOAT3: + case FLOAT4: + case MAT3: + case MAT4: + return GL_FLOAT; + case INT: + case INT2: + case INT3: + case INT4: + return GL_INT; + case BOOL: + return GL_BOOL; + default: + return 0; } } + /** + * @brief Checks if the given `NxShaderDataType` is an integer type. + * + * @param type The shader data type to check. + * @return `true` if the type is an integer type, `false` otherwise. + * + * Example: + * ```cpp + * bool isIntType = isInt(NxShaderDataType::INT3); // returns true + * ``` + **/ static bool isInt(const NxShaderDataType type) { - switch (type) - { - case NxShaderDataType::INT: return true; - case NxShaderDataType::INT2: return true; - case NxShaderDataType::INT3: return true; - case NxShaderDataType::INT4: return true; - case NxShaderDataType::BOOL: return true; - default: return false; + switch (type) { + using enum NxShaderDataType; + case INT: + case INT2: + case INT3: + case INT4: + case BOOL: + return true; + default: + return false; } } @@ -80,40 +97,27 @@ namespace nexo::renderer { void NxOpenGlVertexArray::addVertexBuffer(const std::shared_ptr &vertexBuffer) { - if (!vertexBuffer) - THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Vertex buffer is null"); + if (!vertexBuffer) THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Vertex buffer is null"); glBindVertexArray(_id); vertexBuffer->bind(); - if (vertexBuffer->getLayout().getElements().empty()) - THROW_EXCEPTION(NxBufferLayoutEmpty, "OPENGL"); + if (vertexBuffer->getLayout().getElements().empty()) THROW_EXCEPTION(NxBufferLayoutEmpty, "OPENGL"); - auto index = static_cast(!_vertexBuffers.empty() - ? _vertexBuffers.back()->getLayout().getElements().size() - : 0); - const auto& layout = vertexBuffer->getLayout(); + auto index = static_cast( + !_vertexBuffers.empty() ? _vertexBuffers.back()->getLayout().getElements().size() : 0); + const auto &layout = vertexBuffer->getLayout(); for (const auto &element : layout) { glEnableVertexAttribArray(index); - if (isInt(element.type)) - { - glVertexAttribIPointer( - index, - static_cast(element.getComponentCount()), - nxShaderDataTypeToOpenGltype(element.type), - static_cast(layout.getStride()), - reinterpret_cast(static_cast(element.offset)) - ); - } - else - { - glVertexAttribPointer( - index, - static_cast(element.getComponentCount()), - nxShaderDataTypeToOpenGltype(element.type), - element.normalized ? GL_TRUE : GL_FALSE, - static_cast(layout.getStride()), - reinterpret_cast(static_cast(element.offset)) - ); + if (isInt(element.type)) { + glVertexAttribIPointer(index, static_cast(element.getComponentCount()), + nxShaderDataTypeToOpenGltype(element.type), static_cast(layout.getStride()), + static_cast(reinterpret_cast(element.offset))); + + } else { + glVertexAttribPointer(index, static_cast(element.getComponentCount()), + nxShaderDataTypeToOpenGltype(element.type), + element.normalized ? GL_TRUE : GL_FALSE, static_cast(layout.getStride()), + reinterpret_cast(static_cast(element.offset))); } index++; } @@ -122,8 +126,7 @@ namespace nexo::renderer { void NxOpenGlVertexArray::setIndexBuffer(const std::shared_ptr &indexBuffer) { - if (!indexBuffer) - THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Index buffer cannot be null"); + if (!indexBuffer) THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Index buffer cannot be null"); glBindVertexArray(_id); indexBuffer->bind(); @@ -145,4 +148,4 @@ namespace nexo::renderer { return _id; } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlVertexArray.hpp b/engine/src/renderer/opengl/OpenGlVertexArray.hpp index 051907575..97d9f7bae 100644 --- a/engine/src/renderer/opengl/OpenGlVertexArray.hpp +++ b/engine/src/renderer/opengl/OpenGlVertexArray.hpp @@ -19,76 +19,102 @@ namespace nexo::renderer { /** - * @class NxOpenGlVertexArray - * @brief OpenGL-specific implementation of the `NxVertexArray` class. - * - * The `NxOpenGlVertexArray` class manages vertex and index buffers in an OpenGL - * context. It handles the configuration of vertex attributes and facilitates - * binding/unbinding of the vertex array for rendering. - * - * Responsibilities: - * - Create and manage an OpenGL vertex array object (VAO). - * - Configure vertex attributes using buffer layouts. - * - Bind/unbind the VAO for rendering operations. - */ + * @class NxOpenGlVertexArray + * @brief OpenGL-specific implementation of the `NxVertexArray` class. + * + * The `NxOpenGlVertexArray` class manages vertex and index buffers in an OpenGL + * context. It handles the configuration of vertex attributes and facilitates + * binding/unbinding of the vertex array for rendering. + * + * Responsibilities: + * - Create and manage an OpenGL vertex array object (VAO). + * - Configure vertex attributes using buffer layouts. + * - Bind/unbind the VAO for rendering operations. + */ class NxOpenGlVertexArray final : public NxVertexArray { - public: - /** - * @brief Creates an OpenGL vertex array object (VAO). - * - * Initializes a new VAO and assigns it a unique ID. This ID is used to reference - * the VAO in OpenGL operations. - */ - NxOpenGlVertexArray(); - ~NxOpenGlVertexArray() override = default; + public: + /** + * @brief Creates an OpenGL vertex array object (VAO). + * + * Initializes a new VAO and assigns it a unique ID. This ID is used to reference + * the VAO in OpenGL operations. + */ + NxOpenGlVertexArray(); + ~NxOpenGlVertexArray() override = default; - /** - * @brief Binds the vertex array object (VAO) to the OpenGL context. - * - * Activates the VAO for subsequent rendering operations. This ensures that - * the vertex and index buffers associated with the VAO are used. - */ - void bind() const override; + /** + * @brief Binds the vertex array object (VAO) to the OpenGL context. + * + * Activates the VAO for subsequent rendering operations. This ensures that + * the vertex and index buffers associated with the VAO are used. + */ + void bind() const override; - /** - * @brief Unbinds the vertex array object (VAO) from the OpenGL context. - * - * Deactivates the currently bound VAO. This prevents unintended modifications - * to the VAO in subsequent OpenGL calls. - */ - void unbind() const override; + /** + * @brief Unbinds the vertex array object (VAO) from the OpenGL context. + * + * Deactivates the currently bound VAO. This prevents unintended modifications + * to the VAO in subsequent OpenGL calls. + */ + void unbind() const override; - /** - * @brief Adds a vertex buffer to the vertex array. - * - * Configures the vertex attributes for the given vertex buffer based on its - * buffer layout. The attributes are assigned sequential indices. - * - * @param vertexBuffer The vertex buffer to add. - * @throw NxInvalidValue If the vertex buffer is null. - * @throw NxBufferLayoutEmpty If the vertex buffer's layout is empty. - */ - void addVertexBuffer(const std::shared_ptr &vertexBuffer) override; + /** + * @brief Adds a vertex buffer to the vertex array. + * + * Configures the vertex attributes for the given vertex buffer based on its + * buffer layout. The attributes are assigned sequential indices. + * + * @param vertexBuffer The vertex buffer to add. + * @throw NxInvalidValue If the vertex buffer is null. + * @throw NxBufferLayoutEmpty If the vertex buffer's layout is empty. + */ + void addVertexBuffer(const std::shared_ptr &vertexBuffer) override; - /** - * @brief Sets the index buffer for the vertex array. - * - * Associates an index buffer with the vertex array, enabling indexed rendering. - * - * @param indexBuffer The index buffer to set. - * @throw NxInvalidValue If the index buffer is null. - */ - void setIndexBuffer(const std::shared_ptr &indexBuffer) override; + /** + * @brief Sets the index buffer for the vertex array. + * + * Associates an index buffer with the vertex array, enabling indexed rendering. + * + * @param indexBuffer The index buffer to set. + * @throw NxInvalidValue If the index buffer is null. + */ + void setIndexBuffer(const std::shared_ptr &indexBuffer) override; - [[nodiscard]] const std::vector> &getVertexBuffers() const override; - [[nodiscard]] const std::shared_ptr &getIndexBuffer() const override; + /** + * @brief Retrieves the list of vertex buffers associated with the vertex array. + * + * Returns a constant reference to the vector of shared pointers to the vertex + * buffers that have been added to the vertex array. + * + * @return A constant reference to the vector of vertex buffers. + */ + [[nodiscard]] const std::vector> &getVertexBuffers() const override; - [[nodiscard]] unsigned int getId() const override; - private: - std::vector> _vertexBuffers; - std::shared_ptr _indexBuffer; + /** + * @brief Retrieves the index buffer associated with the vertex array. + * + * Returns a constant reference to the shared pointer of the index buffer + * set for the vertex array. + * + * @return A constant reference to the index buffer. + */ + [[nodiscard]] const std::shared_ptr &getIndexBuffer() const override; - unsigned int _id{}; + /** + * @brief Retrieves the OpenGL ID of the vertex array object (VAO). + * + * Returns the unique identifier assigned to the VAO by OpenGL. This ID is + * used for binding and managing the VAO within the OpenGL context. + * + * @return The OpenGL VAO ID as an unsigned integer. + */ + [[nodiscard]] unsigned int getId() const override; + + private: + std::vector> _vertexBuffers; + std::shared_ptr _indexBuffer; + + unsigned int _id{}; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlWindow.cpp b/engine/src/renderer/opengl/OpenGlWindow.cpp index 0c19d9db2..127c52031 100644 --- a/engine/src/renderer/opengl/OpenGlWindow.cpp +++ b/engine/src/renderer/opengl/OpenGlWindow.cpp @@ -16,9 +16,9 @@ #include +#include "Logger.hpp" #include "renderer/Renderer.hpp" #include "renderer/RendererExceptions.hpp" -#include "Logger.hpp" #if defined(_WIN32) || defined(_WIN64) #include @@ -26,10 +26,25 @@ #define GLFW_EXPOSE_NATIVE_WGL #define GLFW_NATIVE_INCLUDE_NONE #include - #pragma comment (lib, "Dwmapi") + #pragma comment(lib, "Dwmapi") #endif namespace nexo::renderer { + /** + * @brief Callback function for handling GLFW errors. + * + * This function is called by GLFW whenever an error occurs. It logs the error code + * and description to the standard error output. + * + * @param errorCode The error code provided by GLFW. + * @param errorStr A human-readable description of the error. + * + * Example: + * ```cpp + * // Set the error callback during GLFW initialization + * glfwSetErrorCallback(glfwErrorCallback); + * ``` + */ static void glfwErrorCallback(const int errorCode, const char *errorStr) { std::cerr << "[GLFW ERROR] Code : " << errorCode << " / Description : " << errorStr << std::endl; @@ -38,70 +53,56 @@ namespace nexo::renderer { void NxOpenGlWindow::setupCallback() const { // Resize event - glfwSetWindowSizeCallback(_openGlWindow, [](GLFWwindow *window, const int width, const int height) - { - if (width <= 0 || height <= 0) - return; - auto *props = static_cast(glfwGetWindowUserPointer(window)); - props->width = width; + glfwSetWindowSizeCallback(_openGlWindow, [](GLFWwindow *window, const int width, const int height) { + if (width <= 0 || height <= 0) return; + auto *props = static_cast(glfwGetWindowUserPointer(window)); + props->width = width; props->height = height; NxRenderer::onWindowResize(width, height); - if (props->resizeCallback) - props->resizeCallback(width, height); + if (props->resizeCallback) props->resizeCallback(width, height); }); // Close event - glfwSetWindowCloseCallback(_openGlWindow, [](GLFWwindow *window) - { + glfwSetWindowCloseCallback(_openGlWindow, [](GLFWwindow *window) { const auto *props = static_cast(glfwGetWindowUserPointer(window)); - if (props->closeCallback) - props->closeCallback(); + if (props->closeCallback) props->closeCallback(); }); // Keyboard events - glfwSetKeyCallback(_openGlWindow, [](GLFWwindow *window, const int key, [[maybe_unused]]int scancode, const int action, const int mods) - { + glfwSetKeyCallback(_openGlWindow, [](GLFWwindow *window, const int key, [[maybe_unused]] int scancode, + const int action, const int mods) { const auto *props = static_cast(glfwGetWindowUserPointer(window)); - if (props->keyCallback) - props->keyCallback(key, action, mods); + if (props->keyCallback) props->keyCallback(key, action, mods); }); // Mouse click callback - glfwSetMouseButtonCallback(_openGlWindow, [](GLFWwindow *window, const int button, const int action, const int mods) - { - const auto *props = static_cast(glfwGetWindowUserPointer(window)); - if (props->mouseClickCallback) - props->mouseClickCallback(button, action, mods); - }); + glfwSetMouseButtonCallback( + _openGlWindow, [](GLFWwindow *window, const int button, const int action, const int mods) { + const auto *props = static_cast(glfwGetWindowUserPointer(window)); + if (props->mouseClickCallback) props->mouseClickCallback(button, action, mods); + }); // Mouse scroll event - glfwSetScrollCallback(_openGlWindow, [](GLFWwindow *window, const double xOffset, const double yOffset) - { + glfwSetScrollCallback(_openGlWindow, [](GLFWwindow *window, const double xOffset, const double yOffset) { const auto *props = static_cast(glfwGetWindowUserPointer(window)); - if (props->mouseScrollCallback) - props->mouseScrollCallback(xOffset, yOffset); + if (props->mouseScrollCallback) props->mouseScrollCallback(xOffset, yOffset); }); // Mouse move event - glfwSetCursorPosCallback(_openGlWindow, [](GLFWwindow *window, const double xpos, const double ypos) - { + glfwSetCursorPosCallback(_openGlWindow, [](GLFWwindow *window, const double xpos, const double ypos) { const auto *props = static_cast(glfwGetWindowUserPointer(window)); - if (props->mouseMoveCallback) - props->mouseMoveCallback(xpos, ypos); + if (props->mouseMoveCallback) props->mouseMoveCallback(xpos, ypos); }); - glfwSetDropCallback(_openGlWindow, [](GLFWwindow *window, const int count, const char **paths) - { + glfwSetDropCallback(_openGlWindow, [](GLFWwindow *window, const int count, const char **paths) { const auto *props = static_cast(glfwGetWindowUserPointer(window)); - if (props->fileDropCallback) - props->fileDropCallback(count, paths); + if (props->fileDropCallback) props->fileDropCallback(count, paths); }); } void NxOpenGlWindow::init() { - if (!glfwInit()) - THROW_EXCEPTION(NxGraphicsApiInitFailure, "OPENGL"); + if (!glfwInit()) THROW_EXCEPTION(NxGraphicsApiInitFailure, "OPENGL"); LOG(NEXO_DEV, "Initializing opengl window"); glfwSetErrorCallback(glfwErrorCallback); @@ -116,15 +117,16 @@ namespace nexo::renderer { } #endif - // TODO: add in documentation, if a function of opengl segv, it might be bcs this hints a version older than the function's opengl version + // TODO: add in documentation, if a function of opengl segv, it might be bcs this hints a version older than the + // function's opengl version glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - _openGlWindow = glfwCreateWindow(static_cast(_props.width), static_cast(_props.height), _props.title.c_str(), nullptr, nullptr); - if (!_openGlWindow) - THROW_EXCEPTION(NxGraphicsApiWindowInitFailure, "OPENGL"); + _openGlWindow = glfwCreateWindow(static_cast(_props.width), static_cast(_props.height), + _props.title.c_str(), nullptr, nullptr); + if (!_openGlWindow) THROW_EXCEPTION(NxGraphicsApiWindowInitFailure, "OPENGL"); glfwMakeContextCurrent(_openGlWindow); glfwSetWindowUserPointer(_openGlWindow, &_props); setVsync(true); @@ -227,17 +229,17 @@ namespace nexo::renderer { // Linux specific method #ifdef __linux__ - void NxOpenGlWindow::setWaylandAppId(const char* appId) + void NxOpenGlWindow::setWaylandAppId(const char *appId) { _waylandAppId = appId; LOG(NEXO_DEV, "Wayland app id set to '{}'", appId); } - void NxOpenGlWindow::setWmClass(const char* className, const char* instanceName) + void NxOpenGlWindow::setWmClass(const char *className, const char *instanceName) { - _x11ClassName = className; + _x11ClassName = className; _x11InstanceName = instanceName; LOG(NEXO_DEV, "X11 class name set to '{}' and instance name set to '{}'", className, instanceName); } #endif -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/opengl/OpenGlWindow.hpp b/engine/src/renderer/opengl/OpenGlWindow.hpp index 1c5848d4d..3d5430a61 100644 --- a/engine/src/renderer/opengl/OpenGlWindow.hpp +++ b/engine/src/renderer/opengl/OpenGlWindow.hpp @@ -19,112 +19,253 @@ namespace nexo::renderer { /** - * @class NxOpenGlWindow - * @brief OpenGL-specific implementation of the `NxWindow` class. - * - * The `NxOpenGlWindow` class manages the creation and behavior of a window in - * an OpenGL context. It integrates with GLFW for window management and event - * handling. - * - * Responsibilities: - * - Create and manage an OpenGL-compatible window. - * - Provide event handling for window, keyboard, and mouse events. - * - Manage OpenGL context initialization and VSync settings. - */ + * @class NxOpenGlWindow + * @brief OpenGL-specific implementation of the `NxWindow` class. + * + * The `NxOpenGlWindow` class manages the creation and behavior of a window in + * an OpenGL context. It integrates with GLFW for window management and event + * handling. + * + * Responsibilities: + * - Create and manage an OpenGL-compatible window. + * - Provide event handling for window, keyboard, and mouse events. + * - Manage OpenGL context initialization and VSync settings. + */ class NxOpenGlWindow final : public NxWindow { - public: - /** - * @brief Creates an OpenGL window with the specified properties. - * - * Initializes the `NxWindowProperty` structure with the given width, height, - * and title. The window itself is created during the `init()` call. - * - * @param width Initial width of the window. - * @param height Initial height of the window. - * @param title Title of the window. - */ - explicit NxOpenGlWindow(const int width = 1920, - const int height = 1080, - const std::string &title = "Nexo window") : - _props(width, height, title) {} - - /** - * @brief Initializes the OpenGL window and its associated resources. - * - * Creates the window using GLFW, sets up the OpenGL context, and configures - * callbacks for handling window events like resizing, closing, and input. - * - * @throw NxGraphicsApiInitFailure If GLFW initialization fails. - * @throw NxGraphicsApiWindowInitFailure If the window creation fails. - */ - void init() override; - - /** - * @brief Shuts down the OpenGL window and releases its resources. - * - * Destroys the GLFW window and terminates GLFW. Should be called during - * cleanup to ensure proper resource management. - */ - void shutdown() override; - - /** - * @brief Updates the window's state and processes events. - * - * Swaps the front and back buffers for rendering and polls for window events. - */ - void onUpdate() override; - - [[nodiscard]] unsigned int getWidth() const override { return _props.width; }; - [[nodiscard]] unsigned int getHeight() const override {return _props.height; }; - - void getDpiScale(float *x, float *y) const override; - - void setWindowIcon(const std::filesystem::path& iconPath) override; - - void setTitle(const std::string& title) override; - [[nodiscard]] const std::string& getTitle() const override; - - void setDarkMode(bool enabled) override; - [[nodiscard]] bool isDarkMode() const override; - - /** - * @brief Enables or disables vertical synchronization (VSync). - * - * When VSync is enabled, the frame rate is synchronized with the display's - * refresh rate to prevent screen tearing. - * - * @param enabled True to enable VSync, false to disable it. - */ - void setVsync(bool enabled) override; - [[nodiscard]] bool isVsync() const override; - - - [[nodiscard]] bool isOpen() const override { return !glfwWindowShouldClose(_openGlWindow);}; - void close() override { glfwSetWindowShouldClose(_openGlWindow, GLFW_TRUE); }; - - [[nodiscard]] void *window() const override { return _openGlWindow; }; - void setErrorCallback(void *fctPtr) override; - void setResizeCallback(ResizeCallback callback) override { _props.resizeCallback = std::move(callback); } - void setCloseCallback(CloseCallback callback) override { _props.closeCallback = std::move(callback); } - void setKeyCallback(KeyCallback callback) override { _props.keyCallback = std::move(callback); } - void setMouseClickCallback(MouseClickCallback callback) override { _props.mouseClickCallback = std::move(callback); } - void setMouseScrollCallback(MouseScrollCallback callback) override { _props.mouseScrollCallback = std::move(callback); } - void setMouseMoveCallback(MouseMoveCallback callback) override { _props.mouseMoveCallback = std::move(callback); } - void setFileDropCallback(FileDropCallback callback) override { _props.fileDropCallback = std::move(callback); } - - // Linux specific method + public: + /** + * @brief Creates an OpenGL window with the specified properties. + * + * Initializes the `NxWindowProperty` structure with the given width, height, + * and title. The window itself is created during the `init()` call. + * + * @param width Initial width of the window. + * @param height Initial height of the window. + * @param title Title of the window. + */ + explicit NxOpenGlWindow(const int width = 1920, const int height = 1080, + const std::string& title = "Nexo window") + : _props(width, height, title) + {} + + /** + * @brief Initializes the OpenGL window and its associated resources. + * + * Creates the window using GLFW, sets up the OpenGL context, and configures + * callbacks for handling window events like resizing, closing, and input. + * + * @throw NxGraphicsApiInitFailure If GLFW initialization fails. + * @throw NxGraphicsApiWindowInitFailure If the window creation fails. + */ + void init() override; + + /** + * @brief Shuts down the OpenGL window and releases its resources. + * + * Destroys the GLFW window and terminates GLFW. Should be called during + * cleanup to ensure proper resource management. + */ + void shutdown() override; + + /** + * @brief Updates the window's state and processes events. + * + * Swaps the front and back buffers for rendering and polls for window events. + */ + void onUpdate() override; + + /** + * @brief Gets the current width of the window. + * @return The width in pixels. + */ + [[nodiscard]] unsigned int getWidth() const override + { + return _props.width; + } + + /** + * @brief Gets the current height of the window. + * @return The height in pixels. + */ + [[nodiscard]] unsigned int getHeight() const override + { + return _props.height; + } + + /** + * @brief Retrieves the DPI scaling factors for the window. + * @param x Pointer to store the horizontal DPI scale. + * @param y Pointer to store the vertical DPI scale. + */ + void getDpiScale(float* x, float* y) const override; + + /** + * @brief Sets the window icon from the specified file path. + * @param iconPath Path to the icon image file. + */ + void setWindowIcon(const std::filesystem::path& iconPath) override; + + /** + * @brief Sets the window title. + * @param title The new title for the window. + */ + void setTitle(const std::string& title) override; + + /** + * @brief Gets the current window title. + * @return The window title as a string. + */ + [[nodiscard]] const std::string& getTitle() const override; + + /** + * @brief Enables or disables dark mode for the window. + * @param enabled True to enable dark mode, false to disable. + */ + void setDarkMode(bool enabled) override; + + /** + * @brief Checks if dark mode is enabled. + * @return True if dark mode is enabled, false otherwise. + */ + [[nodiscard]] bool isDarkMode() const override; + /** + * @brief Enables or disables vertical synchronization (VSync). + * + * When VSync is enabled, the frame rate is synchronized with the display's + * refresh rate to prevent screen tearing. + * + * @param enabled True to enable VSync, false to disable it. + */ + void setVsync(bool enabled) override; + + /** + * @brief Checks if vertical synchronization (VSync) is enabled. + * + * @return True if VSync is enabled, false otherwise. + */ + [[nodiscard]] bool isVsync() const override; + + /** + * @brief Checks if the window is currently open. + * + * @return True if the window is open, false if it should close. + */ + [[nodiscard]] bool isOpen() const override + { + return !glfwWindowShouldClose(_openGlWindow); + } + + /** + * @brief Requests the window to close. + * + * Sets the window's close flag, which will be processed in the next update cycle. + */ + void close() override + { + glfwSetWindowShouldClose(_openGlWindow, GLFW_TRUE); + } + + /** + * @brief Returns the native window handle. + * + * @return Pointer to the underlying GLFWwindow. + */ + [[nodiscard]] void* window() const override + { + return _openGlWindow; + } + + /** + * @brief Sets the error callback function for the window. + * + * @param fctPtr Pointer to the error callback function. + */ + void setErrorCallback(void* fctPtr) override; + + /** + * @brief Sets the callback function for window resize events. + * + * @param callback The function to call when the window is resized. + */ + void setResizeCallback(ResizeCallback callback) override + { + _props.resizeCallback = std::move(callback); + } + + /** + * @brief Sets the callback function for window close events. + * + * @param callback The function to call when the window is requested to close. + */ + void setCloseCallback(CloseCallback callback) override + { + _props.closeCallback = std::move(callback); + } + + /** + * @brief Sets the callback function for key events. + * + * @param callback The function to call when a key event occurs. + */ + void setKeyCallback(KeyCallback callback) override + { + _props.keyCallback = std::move(callback); + } + + /** + * @brief Sets the callback function for mouse click events. + * + * @param callback The function to call when a mouse button is clicked. + */ + void setMouseClickCallback(MouseClickCallback callback) override + { + _props.mouseClickCallback = std::move(callback); + } + + /** + * @brief Sets the callback function for mouse scroll events. + * + * @param callback The function to call when the mouse wheel is scrolled. + */ + void setMouseScrollCallback(MouseScrollCallback callback) override + { + _props.mouseScrollCallback = std::move(callback); + } + + /** + * @brief Sets the callback function for mouse movement events. + * + * @param callback The function to call when the mouse is moved. + */ + void setMouseMoveCallback(MouseMoveCallback callback) override + { + _props.mouseMoveCallback = std::move(callback); + } + + /** + * @brief Sets the callback function for file drop events. + * + * @param callback The function to call when files are dropped onto the window. + */ + void setFileDropCallback(FileDropCallback callback) override + { + _props.fileDropCallback = std::move(callback); + } + + // Linux specific method #ifdef __linux__ - void setWaylandAppId(const char* appId) override; - void setWmClass(const char* className, const char* instanceName) override; - private: - std::string _waylandAppId = "nexo"; - std::string _x11ClassName = "nexo"; - std::string _x11InstanceName = "nexo"; + void setWaylandAppId(const char* appId) override; + void setWmClass(const char* className, const char* instanceName) override; + + private: + std::string _waylandAppId = "nexo"; + std::string _x11ClassName = "nexo"; + std::string _x11InstanceName = "nexo"; #endif - private: - GLFWwindow *_openGlWindow{}; - NxWindowProperty _props; + private: + GLFWwindow* _openGlWindow{}; + NxWindowProperty _props; - void setupCallback() const; + void setupCallback() const; }; -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/primitives/Billboard.cpp b/engine/src/renderer/primitives/Billboard.cpp index 828c2b763..0dd201c7f 100644 --- a/engine/src/renderer/primitives/Billboard.cpp +++ b/engine/src/renderer/primitives/Billboard.cpp @@ -12,22 +12,20 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "renderer/Renderer3D.hpp" - #include #include +#include "renderer/Renderer3D.hpp" #define GLM_ENABLE_EXPERIMENTAL #include #include -namespace nexo::renderer -{ +namespace nexo::renderer { // Quad vertices for a 1x1 billboard centered at origin constexpr glm::vec3 billboardPositions[4] = { {-0.5f, -0.5f, 0.0f}, // Bottom left - {0.5f, -0.5f, 0.0f}, // Bottom right - {0.5f, 0.5f, 0.0f}, // Top right - {-0.5f, 0.5f, 0.0f} // Top left + {0.5f, -0.5f, 0.0f}, // Bottom right + {0.5f, 0.5f, 0.0f}, // Top right + {-0.5f, 0.5f, 0.0f} // Top left }; constexpr glm::vec2 billboardTexCoords[4] = { @@ -66,29 +64,23 @@ namespace nexo::renderer texCoords[5] = billboardTexCoords[0]; // Bottom left // All normals point forward for billboard (will be transformed to face camera) - for (int i = 0; i < 6; ++i) - { + for (int i = 0; i < 6; ++i) { normals[i] = {0.0f, 0.0f, 1.0f}; } } std::shared_ptr NxRenderer3D::getBillboardVAO() { - constexpr unsigned int nbVerticesBillboard = 6; + constexpr unsigned int nbVerticesBillboard = 6; static std::shared_ptr billboardVao = nullptr; - if (billboardVao) - return billboardVao; + if (billboardVao) return billboardVao; - billboardVao = createVertexArray(); - const auto vertexBuffer = createVertexBuffer(nbVerticesBillboard * sizeof(NxVertex)); + billboardVao = createVertexArray(); + const auto vertexBuffer = createVertexBuffer(nbVerticesBillboard * sizeof(NxVertex)); const NxBufferLayout cubeVertexBufferLayout = { - {NxShaderDataType::FLOAT3, "aPos"}, - {NxShaderDataType::FLOAT2, "aTexCoord"}, - {NxShaderDataType::FLOAT3, "aNormal"}, - {NxShaderDataType::FLOAT3, "aTangent"}, - {NxShaderDataType::FLOAT3, "aBiTangent"}, - {NxShaderDataType::INT, "aEntityID"} - }; + {NxShaderDataType::FLOAT3, "aPos"}, {NxShaderDataType::FLOAT2, "aTexCoord"}, + {NxShaderDataType::FLOAT3, "aNormal"}, {NxShaderDataType::FLOAT3, "aTangent"}, + {NxShaderDataType::FLOAT3, "aBiTangent"}, {NxShaderDataType::INT, "aEntityID"}}; vertexBuffer->setLayout(cubeVertexBufferLayout); std::array vertices{}; @@ -97,22 +89,20 @@ namespace nexo::renderer genBillboardMesh(vertices, texCoords, normals); std::vector vertexData(nbVerticesBillboard); - for (unsigned int i = 0; i < nbVerticesBillboard; ++i) - { - vertexData[i].position = glm::vec4(vertices[i], 1.0f); - vertexData[i].texCoord = texCoords[i]; - vertexData[i].normal = normals[i]; - vertexData[i].tangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default tangent + for (unsigned int i = 0; i < nbVerticesBillboard; ++i) { + vertexData[i].position = glm::vec4(vertices[i], 1.0f); + vertexData[i].texCoord = texCoords[i]; + vertexData[i].normal = normals[i]; + vertexData[i].tangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default tangent vertexData[i].bitangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default bi tangent - vertexData[i].entityID = 0; // Default entity ID + vertexData[i].entityID = 0; // Default entity ID } vertexBuffer->setData(vertexData.data(), static_cast(vertexData.size() * sizeof(NxVertex))); billboardVao->addVertexBuffer(vertexBuffer); std::vector indices(nbVerticesBillboard); - for (uint32_t i = 0; i < nbVerticesBillboard; ++i) - indices[i] = i; + for (uint32_t i = 0; i < nbVerticesBillboard; ++i) indices[i] = i; const auto indexBuffer = createIndexBuffer(); indexBuffer->setData(indices.data(), static_cast(indices.size())); @@ -120,4 +110,4 @@ namespace nexo::renderer return billboardVao; } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/primitives/Box.cpp b/engine/src/renderer/primitives/Box.cpp new file mode 100644 index 000000000..e8d290513 --- /dev/null +++ b/engine/src/renderer/primitives/Box.cpp @@ -0,0 +1,64 @@ +//// Box.cpp ////////////////////////////////////////////////////////////////// +// +// ⢀⢀⢀⣤⣤⣤⡀⢀⢀⢀⢀⢀⢀⢠⣤⡄⢀⢀⢀⢀⣠⣤⣤⣤⣤⣤⣤⣤⣤⣤⡀⢀⢀⢀⢠⣤⣄⢀⢀⢀⢀⢀⢀⢀⣤⣤⢀⢀⢀⢀⢀⢀⢀⢀⣀⣄⢀⢀⢠⣄⣀⢀⢀⢀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⣿⣷⡀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡟⡛⡛⡛⡛⡛⡛⡛⢁⢀⢀⢀⢀⢻⣿⣦⢀⢀⢀⢀⢠⣾⡿⢃⢀⢀⢀⢀⢀⣠⣾⣿⢿⡟⢀⢀⡙⢿⢿⣿⣦⡀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⡛⣿⣷⡀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡙⣿⡷⢀⢀⣰⣿⡟⢁⢀⢀⢀⢀⢀⣾⣿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⣿⡆⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⡈⢿⣷⡄⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣇⣀⣀⣀⣀⣀⣀⣀⢀⢀⢀⢀⢀⢀⢀⡈⢀⢀⣼⣿⢏⢀⢀⢀⢀⢀⢀⣼⣿⡏⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡘⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⡈⢿⣿⡄⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣿⢿⢿⢿⢿⢿⢿⢿⢇⢀⢀⢀⢀⢀⢀⢀⢠⣾⣿⣧⡀⢀⢀⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⡈⢿⣿⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣰⣿⡟⡛⣿⣷⡄⢀⢀⢀⢀⢀⢿⣿⣇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⡈⢿⢀⢀⢸⣿⡇⢀⢀⢀⢀⡛⡟⢁⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⡟⢀⢀⡈⢿⣿⣄⢀⢀⢀⢀⡘⣿⣿⣄⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⢏⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⢀⢀⢀⣠⣾⡿⢃⢀⢀⢀⢀⢀⢻⣿⣧⡀⢀⢀⢀⡈⢻⣿⣷⣦⣄⢀⢀⣠⣤⣶⣿⡿⢋⢀⢀⢀⢀ +// ⢀⢀⢀⢿⢿⢀⢀⢀⢀⢀⢀⢀⢀⢸⢿⢃⢀⢀⢀⢀⢻⢿⢿⢿⢿⢿⢿⢿⢿⢿⢃⢀⢀⢀⢿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⡗⢀⢀⢀⢀⢀⡈⡉⡛⡛⢀⢀⢹⡛⢋⢁⢀⢀⢀⢀⢀⢀ +// +// Author: Mehdy MORVAN +// Date: 05/10/2025 +// Description: Source file for the box primitive (used for debugging AABB) +// +/////////////////////////////////////////////////////////////////////////////// + +#include "renderer/Renderer3D.hpp" + +namespace nexo::renderer { + + struct NxBoxVertex { + glm::vec3 pos{}; + }; + + static constexpr std::array kBoxEdgeIndices = { + 0,1, 1,3, 3,2, 2,0, + 4,5, 5,7, 7,6, 6,4, + 0,4, 1,5, 2,6, 3,7 + }; + + std::shared_ptr NxRenderer3D::getBoxVAO() + { + static std::shared_ptr s_boxVao; // cached + if (s_boxVao) return s_boxVao; + + static constexpr std::array kCorners = { + glm::vec3{-1,-1,-1}, glm::vec3{+1,-1,-1}, glm::vec3{-1,-1,+1}, glm::vec3{+1,-1,+1}, + glm::vec3{-1,+1,-1}, glm::vec3{+1,+1,-1}, glm::vec3{-1,+1,+1}, glm::vec3{+1,+1,+1} + }; + + auto vao = createVertexArray(); + + auto vbo = createVertexBuffer(static_cast(kCorners.size() * sizeof(NxBoxVertex))); + const NxBufferLayout layout = { { NxShaderDataType::FLOAT3, "aPos" } }; + vbo->setLayout(layout); + + // Pack positions into NxBoxVertex + std::array verts{}; + for (size_t i = 0; i < kCorners.size(); ++i) verts[i].pos = kCorners[i]; + + vbo->setData(verts.data(), static_cast(verts.size() * sizeof(NxBoxVertex))); + vao->addVertexBuffer(vbo); + + auto ebo = createIndexBuffer(); + std::array indices = kBoxEdgeIndices; + ebo->setData(indices.data(), static_cast(indices.size())); + vao->setIndexBuffer(ebo); + + s_boxVao = std::move(vao); + return s_boxVao; + } +} diff --git a/engine/src/renderer/primitives/Cube.cpp b/engine/src/renderer/primitives/Cube.cpp index 469742d5d..e9e351340 100644 --- a/engine/src/renderer/primitives/Cube.cpp +++ b/engine/src/renderer/primitives/Cube.cpp @@ -66,13 +66,13 @@ namespace nexo::renderer std::ranges::copy(verts, vertices.begin()); - glm::vec2 texturesCoord[] = { - glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), - glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), - glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), - glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), - glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), - glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 0), glm::vec2(1, 0), glm::vec2(1, 1), glm::vec2(0, 1), + constexpr std::array texturesCoord = { + glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 0), glm::vec2(1, 0), + glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 0), glm::vec2(1, 0), + glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 0), glm::vec2(1, 0), + glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 0), glm::vec2(1, 0), + glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 0), glm::vec2(1, 0), + glm::vec2(1, 1), glm::vec2(0, 1), glm::vec2(0, 0), glm::vec2(1, 1), glm::vec2(0, 0), glm::vec2(1, 0), }; std::ranges::copy(texturesCoord, texCoords.begin()); diff --git a/engine/src/renderer/primitives/Cylinder.cpp b/engine/src/renderer/primitives/Cylinder.cpp index 324d5c5a4..a828b19df 100644 --- a/engine/src/renderer/primitives/Cylinder.cpp +++ b/engine/src/renderer/primitives/Cylinder.cpp @@ -12,20 +12,19 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "renderer/Renderer3D.hpp" #include +#include "renderer/Renderer3D.hpp" #ifndef M_PI -#define M_PI 3.14159265358979323846 + #define M_PI 3.14159265358979323846 #endif #include #define GLM_ENABLE_EXPERIMENTAL #include -#include #include #include +#include -namespace nexo::renderer -{ +namespace nexo::renderer { constexpr float CYLINDER_HEIGHT = 1.0f; // Height of the cylinder should be 1.0f static std::vector generateCylinderVertices(const unsigned int nbSegment) @@ -34,35 +33,31 @@ namespace nexo::renderer vertices.reserve(nbSegment * 4); // Reserve memory for all vertices (2 caps + 2 sides) unsigned int i = 0; - for (unsigned int k = nbSegment - 1; i < nbSegment; ++i, --k) - { + for (unsigned int k = nbSegment - 1; i < nbSegment; ++i, --k) { const float angle = static_cast(k) / static_cast(nbSegment) * 2.0f * static_cast(M_PI); - const float x = std::cos(angle); - const float z = std::sin(angle); + const float x = std::cos(angle); + const float z = std::sin(angle); vertices.emplace_back(x, CYLINDER_HEIGHT, z); } - for (unsigned int k = nbSegment - 1; i < nbSegment * 2; ++i, --k) - { + for (unsigned int k = nbSegment - 1; i < nbSegment * 2; ++i, --k) { const float angle = static_cast(k) / static_cast(nbSegment) * 2.0f * static_cast(M_PI); - const float x = std::cos(angle); - const float z = std::sin(angle); + const float x = std::cos(angle); + const float z = std::sin(angle); vertices.emplace_back(x, -CYLINDER_HEIGHT, z); } - for (int k = 0; i < nbSegment * 3; ++i, ++k) - { + for (int k = 0; i < nbSegment * 3; ++i, ++k) { const float angle = static_cast(k) / static_cast(nbSegment) * 2.0f * static_cast(M_PI); - const float x = std::cos(angle); - const float z = std::sin(angle); + const float x = std::cos(angle); + const float z = std::sin(angle); vertices.emplace_back(x, CYLINDER_HEIGHT, z); } - for (int k = 0; i < nbSegment * 4; ++i, ++k) - { + for (int k = 0; i < nbSegment * 4; ++i, ++k) { const float angle = static_cast(k) / static_cast(nbSegment) * 2.0f * static_cast(M_PI); - const float x = std::cos(angle); - const float z = std::sin(angle); + const float x = std::cos(angle); + const float z = std::sin(angle); vertices.emplace_back(x, -CYLINDER_HEIGHT, z); } @@ -70,78 +65,94 @@ namespace nexo::renderer } /** - * @brief Generates indices for the caps of a cylinder mesh. + * @brief Generates indices for the caps of a cylinder mesh using recursion. * - * This function calculates the indices required to form the top and bottom caps of a cylinder mesh. - * It uses a recursive approach to divide the cap into triangles, ensuring proper tessellation. + * This function is a helper function that recursively calculates the indices required to form + * the top and bottom caps of a cylinder mesh. It divides the cap into triangles based on the + * number of edges and ensures proper tessellation. * * @param indices A reference to the vector where the generated indices will be stored. - * @param transformer An offset applied to the indices to account for the starting position of the cap vertices. Different for top and bottom caps. - * @param nbSegment The number of segments (or divisions) around the cylinder's circumference. (ex 3 for triangle, more if there is sub triangles. + * @param transformer An offset applied to the indices to account for the starting position of the cap vertices. + * Different for top and bottom caps. + * @param nbSegment The number of segments (or divisions) around the cylinder's circumference. + * @param start The starting index for the current section of the cap being processed. + * @param nbEdge The number of edges in the current section of the cap being processed. + * @param clockwise A boolean indicating the winding order of the triangles. If true, triangles are wound clockwise; */ - static void capIndices(std::vector& indices, const int transformer, const unsigned int nbSegment) + static void capIndicesRec(std::vector& indices, const int transformer, const unsigned int nbSegment, + const unsigned int start, const unsigned int nbEdge, const bool clockwise = true) { - std::function capIndicesRec; - - capIndicesRec = [&indices, &transformer, &capIndicesRec, &nbSegment](const unsigned int start, const unsigned int nbEdge) - { - assert(nbEdge > 0 && "Number of edges must be greater than 0"); - // Calculate the step size for dividing the cap into triangles - // Base case: If the step size is 1, form a single triangle - if (const auto step = static_cast(std::ceil(static_cast(nbEdge) / 3.0)); step == 1) { - const unsigned int tmp = start + 2 < nbSegment ? start + 2 : 0; + assert(nbEdge > 0 && "Number of edges must be greater than 0"); + // Calculate the step size for dividing the cap into triangles + // Base case: If the step size is 1, form a single triangle + if (const auto step = static_cast(std::ceil(static_cast(nbEdge) / 3.0)); step == 1) { + const unsigned int tmp = start + 2 < nbSegment ? start + 2 : 0; + indices.push_back(start + transformer); + indices.push_back(clockwise ? tmp + transformer : start + 1 + transformer); + indices.push_back(clockwise ? start + 1 + transformer : tmp + transformer); + } else { + // Recursive case: Divide the cap into smaller sections + capIndicesRec(indices, transformer, nbSegment, start, step + 1, clockwise); + + if (start + 2 * step < start + nbEdge - 1) { + unsigned int tmp = 0; + if (start + 2 * step < start + nbEdge - 1) { + tmp = start + nbEdge - 1; + capIndicesRec(indices, transformer, nbSegment, start + step, tmp - (start + step) + 1, clockwise); + } else if (start + 2 * step < nbSegment) { + tmp = start + 2 * step; + capIndicesRec(indices, transformer, nbSegment, start + step, step + 1, clockwise); + } + tmp = tmp > nbSegment - 1 ? 0 : tmp; indices.push_back(start + transformer); - indices.push_back(tmp + transformer); - indices.push_back(start + 1 + transformer); + indices.push_back(clockwise ? tmp + transformer : start + step + transformer); + indices.push_back(clockwise ? start + step + transformer : tmp + transformer); } else { - // Recursive case: Divide the cap into smaller sections - capIndicesRec(start, step + 1); - - if (start + 2 * step < start + nbEdge - 1) { - unsigned int tmp = 0; - if (start + 2 * step < start + nbEdge - 1) { - tmp = start + nbEdge - 1; - capIndicesRec(start + step, tmp - (start + step) + 1); - } - else if (start + 2 * step < nbSegment) { - tmp = start + 2 * step; - capIndicesRec(start + step, step + 1); - } - tmp = tmp > nbSegment - 1 ? 0 : tmp; - indices.push_back(start + transformer); - indices.push_back(tmp + transformer); - indices.push_back(start + step + transformer); - } else { - const unsigned int tmp = start + nbEdge - 1 < nbSegment ? start + nbEdge - 1 : 0; - indices.push_back(start + transformer); - indices.push_back(tmp + transformer); - indices.push_back(start + step + transformer); - if (start + nbEdge - 1 - (start + step) > 1) { - capIndicesRec(start + step, step + 1); - } + const unsigned int tmp = start + nbEdge - 1 < nbSegment ? start + nbEdge - 1 : 0; + indices.push_back(start + transformer); + indices.push_back(clockwise ? tmp + transformer : start + step + transformer); + indices.push_back(clockwise ? start + step + transformer : tmp + transformer); + if (start + nbEdge - 1 - (start + step) > 1) { + capIndicesRec(indices, transformer, nbSegment, start + step, step + 1, clockwise); } } - }; + } + } + /** + * @brief Generates indices for the caps of a cylinder mesh. + * + * This function calculates the indices required to form the top and bottom caps of a cylinder mesh. + * It uses a recursive approach to divide the cap into triangles, ensuring proper tessellation. + * + * @param indices A reference to the vector where the generated indices will be stored. + * @param transformer An offset applied to the indices to account for the starting position of the cap vertices. + * Different for top and bottom caps. + * @param nbSegment The number of segments (or divisions) around the cylinder's circumference. (ex 3 for triangles, + * more if there is sub triangles) + * @param clockwise A boolean indicating the winding order of the triangles. If true, triangles are wound clockwise; + */ + static void capIndices(std::vector& indices, const int transformer, const unsigned int nbSegment, const bool clockwise = true) + { // Initial setup: Define the starting point and step size constexpr unsigned int start = 0; - const auto step = static_cast(ceil(static_cast(nbSegment) / 3.0)); + const auto step = static_cast(ceil(static_cast(nbSegment) / 3.0)); // Add the first triangle to the indices indices.push_back(start + transformer); - indices.push_back(start + 2 * step + transformer); - indices.push_back(start + step + transformer); + indices.push_back(clockwise ? start + 2 * step + transformer : start + step + transformer); + indices.push_back(clockwise ? start + step + transformer : start + 2 * step + transformer); // Generate additional triangles: at least for 2 subsections if nbSegment > 3 - if (nbSegment > 3) - { - capIndicesRec(start, step + 1); - capIndicesRec(start + step, step + 1); + if (nbSegment > 3) { + capIndicesRec(indices, transformer, nbSegment, start, step + 1, clockwise); + capIndicesRec(indices, transformer, nbSegment, start + step, step + 1, clockwise); } // Generate additional triangles: add 3td subsections if nbSegment > 5 - if (nbSegment > 5) - capIndicesRec(start + 2 * step, nbSegment - 2 * step + 1); + if (nbSegment > 5) { + capIndicesRec(indices, transformer, nbSegment, start + 2 * step, nbSegment - 2 * step + 1, clockwise); + } } /** @@ -163,8 +174,7 @@ namespace nexo::renderer // Generate indices for the side faces of the cylinder unsigned int i = 0; - for (; i < nbSegment - 1; ++i) - { + for (; i < nbSegment - 1; ++i) { // Create two triangles for each segment indices.push_back(i); indices.push_back(i + nbSegment); @@ -185,7 +195,7 @@ namespace nexo::renderer // Generate indices for the top and bottom caps of the cylinder capIndices(indices, static_cast(nbSegment * 2), nbSegment); - capIndices(indices, static_cast(nbSegment * 3), nbSegment); + capIndices(indices, static_cast(nbSegment * 3), nbSegment, false); return indices; } @@ -207,21 +217,18 @@ namespace nexo::renderer texCoords.reserve(nbSegment * 4); // Reserve memory for all texture coordinates (one for each vertex) // Generate texture coordinates for cylinder sides - for (unsigned int i = 0; i < nbSegment; ++i) - { + for (unsigned int i = 0; i < nbSegment; ++i) { const float u = static_cast(i) / static_cast(nbSegment); texCoords.emplace_back(u, 1.0f); // Top edge } - for (unsigned int i = 0; i < nbSegment; ++i) - { + for (unsigned int i = 0; i < nbSegment; ++i) { const float u = static_cast(i) / static_cast(nbSegment); texCoords.emplace_back(u, 0.0f); // Bottom edge } // Cap vertices use radial UV mapping - for (unsigned int i = 0; i < nbSegment * 2; ++i) - { - const float angle = static_cast(i % nbSegment) / static_cast(nbSegment) * 2.0f * static_cast< - float>(M_PI); + for (unsigned int i = 0; i < nbSegment * 2; ++i) { + const float angle = + static_cast(i % nbSegment) / static_cast(nbSegment) * 2.0f * static_cast(M_PI); const float u = (std::cos(angle) + 1.0f) * 0.5f; const float v = (std::sin(angle) + 1.0f) * 0.5f; texCoords.emplace_back(u, v); @@ -229,7 +236,6 @@ namespace nexo::renderer return texCoords; } - /** * @brief Generates normal vectors for a cylinder mesh. * @@ -250,28 +256,24 @@ namespace nexo::renderer unsigned int i = 0; // Generate normals for the top cap of the cylinder - for (; i < nbSegment * 1; ++i) - { + for (; i < nbSegment * 1; ++i) { const glm::vec3 vector1 = vertices[i] - glm::vec3(0, 1, 0); normals.emplace_back(vector1); } // Generate normals for the bottom cap of the cylinder - for (; i < nbSegment * 2; ++i) - { + for (; i < nbSegment * 2; ++i) { const glm::vec3 vector2 = vertices[i] - glm::vec3(0, -1, 0); normals.emplace_back(vector2); } // Generate normals for the side faces of the cylinder (top half) - for (; i < nbSegment * 3; ++i) - { + for (; i < nbSegment * 3; ++i) { normals.emplace_back(0, 1, 0); } // Generate normals for the side faces of the cylinder (bottom half) - for (; i < nbSegment * 4; ++i) - { + for (; i < nbSegment * 4; ++i) { normals.emplace_back(0, -1, 0); } @@ -292,8 +294,7 @@ namespace nexo::renderer std::shared_ptr NxRenderer3D::getCylinderVAO(unsigned int nbSegment) { // Ensure the number of segments is at least 3, defaulting to 8 if not. - if (nbSegment < 3) - { + if (nbSegment < 3) { LOG(NEXO_WARN, "Cylinder segments must be at least 3, using default value of 8."); nbSegment = 8; } @@ -302,43 +303,41 @@ namespace nexo::renderer static std::map> cylinderVaoMap; // If a VAO for the given segment count already exists, return it. - if (cylinderVaoMap.contains(nbSegment)) - return cylinderVaoMap[nbSegment]; + if (cylinderVaoMap.contains(nbSegment)) return cylinderVaoMap[nbSegment]; // Calculate the total number of vertices for the cylinder. const unsigned int nbVerticesCylinder = nbSegment * 4; // Create a new VAO and vertex buffer for the cylinder. cylinderVaoMap[nbSegment] = createVertexArray(); - const auto vertexBuffer = createVertexBuffer(nbVerticesCylinder * sizeof(NxVertex)); + const auto vertexBuffer = createVertexBuffer(nbVerticesCylinder * sizeof(NxVertex)); // Define the layout of the vertex buffer. const NxBufferLayout cylinderVertexBufferLayout = { - {NxShaderDataType::FLOAT3, "aPos"}, // Position attribute - {NxShaderDataType::FLOAT2, "aTexCoord"}, // Texture coordinate attribute - {NxShaderDataType::FLOAT3, "aNormal"}, // Normal vector attribute - {NxShaderDataType::FLOAT3, "aTangent"}, // Tangent vector attribute + {NxShaderDataType::FLOAT3, "aPos"}, // Position attribute + {NxShaderDataType::FLOAT2, "aTexCoord"}, // Texture coordinate attribute + {NxShaderDataType::FLOAT3, "aNormal"}, // Normal vector attribute + {NxShaderDataType::FLOAT3, "aTangent"}, // Tangent vector attribute {NxShaderDataType::FLOAT3, "aBiTangent"}, // Bi tangent vector attribute - {NxShaderDataType::INT, "aEntityID"} // Entity ID attribute + {NxShaderDataType::INT, "aEntityID"} // Entity ID attribute }; vertexBuffer->setLayout(cylinderVertexBufferLayout); // Generate the vertex data for the cylinder. - const std::vector vertices = generateCylinderVertices(nbSegment); + const std::vector vertices = generateCylinderVertices(nbSegment); const std::vector texCoords = generateTextureCoords(nbSegment); - const std::vector normals = generateNormals(vertices, nbSegment); - std::vector indices = generateCylinderIndices(nbSegment); + const std::vector normals = generateNormals(vertices, nbSegment); + std::vector indices = generateCylinderIndices(nbSegment); // Populate the vertex buffer with the generated data. std::vector vertexData(nbVerticesCylinder); - for (unsigned int i = 0; i < nbVerticesCylinder; ++i) - { - vertexData[i].position = glm::vec4(vertices[i], 1.0f); - vertexData[i].texCoord = texCoords[i]; - vertexData[i].normal = normals[i]; - vertexData[i].tangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default tangent + for (unsigned int i = 0; i < nbVerticesCylinder; ++i) { + vertexData[i].position = glm::vec4(vertices[i], 1.0f); + vertexData[i].texCoord = texCoords[i]; + vertexData[i].normal = normals[i]; + vertexData[i].tangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default tangent vertexData[i].bitangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default bi tangent - vertexData[i].entityID = 0; // Default entity ID + vertexData[i].entityID = 0; // Default entity ID } vertexBuffer->setData(vertexData.data(), vertexData.size() * sizeof(NxVertex)); cylinderVaoMap[nbSegment]->addVertexBuffer(vertexBuffer); @@ -351,4 +350,4 @@ namespace nexo::renderer // Return the newly created VAO. return cylinderVaoMap[nbSegment]; } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/primitives/Pyramid.cpp b/engine/src/renderer/primitives/Pyramid.cpp index bb2883fef..583442978 100644 --- a/engine/src/renderer/primitives/Pyramid.cpp +++ b/engine/src/renderer/primitives/Pyramid.cpp @@ -22,64 +22,49 @@ #include #include -namespace nexo::renderer -{ +namespace nexo::renderer { /** - * @brief Generates the vertex, texture coordinate, and normal data for a pyramid mesh. - * - * Fills the provided arrays with 36 vertices, texture coordinates, and normals for a pyramid. - * - * @param vertices Array to store generated vertex positions. - * @param texCoords Array to store generated texture coordinates. - * @param normals Array to store generated normals. - */ - static void genPyramidMesh(std::array& vertices, - std::array& texCoords, + * @brief Generates the vertex, texture coordinate, and normal data for a pyramid mesh. + * + * Fills the provided arrays with 36 vertices, texture coordinates, and normals for a pyramid. + * + * @param vertices Array to store generated vertex positions. + * @param texCoords Array to store generated texture coordinates. + * @param normals Array to store generated normals. + */ + static void genPyramidMesh(std::array& vertices, std::array& texCoords, std::array& normals) { // Define the five vertices of the pyramid - constexpr glm::vec3 v0 = {0.0f, 1.0f, 0.0f}; // Top vertex + constexpr glm::vec3 v0 = {0.0f, 1.0f, 0.0f}; // Top vertex constexpr glm::vec3 v1 = {-1.0f, -1.0f, -1.0f}; // Bottom-left-back - constexpr glm::vec3 v2 = {1.0f, -1.0f, -1.0f}; // Bottom-right-back - constexpr glm::vec3 v3 = {1.0f, -1.0f, 1.0f}; // Bottom-right-front - constexpr glm::vec3 v4 = {-1.0f, -1.0f, 1.0f}; // Bottom-left-front + constexpr glm::vec3 v2 = {1.0f, -1.0f, -1.0f}; // Bottom-right-back + constexpr glm::vec3 v3 = {1.0f, -1.0f, 1.0f}; // Bottom-right-front + constexpr glm::vec3 v4 = {-1.0f, -1.0f, 1.0f}; // Bottom-left-front // Define the 4 triangular faces + the base (2 triangles) - glm::vec3 verts[] = { - v1, v2, v3, v1, v3, v4, // Base face - // Side faces - v0, v2, v1, - v0, v3, v2, - v0, v4, v3, - v0, v1, v4 + constexpr std::array verts = { + v1, v2, v3, v1, v3, v4, // Base face + v0, v2, v1, v0, v3, v2, v0, v4, v3, v0, v1, v4 // Side faces }; - std::ranges::copy(verts, vertices.begin()); // Basic UV mapping for each face - glm::vec2 texturesCoord[] = { - // Base face - {0.5f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, - {0.5f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}, + constexpr std::array texturesCoord = { + glm::vec2(0.5f, 0.0f), glm::vec2(0.0f, 1.0f), glm::vec2(1.0f, 1.0f), glm::vec2(0.5f, 0.0f), + glm::vec2(1.0f, 1.0f), glm::vec2(0.0f, 1.0f), // Side faces - {0.5f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f}, - {0.5f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f}, - {0.5f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f}, - {0.5f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f} - }; - + glm::vec2(0.5f, 1.0f), glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 0.0f), glm::vec2(0.5f, 1.0f), + glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 0.0f), glm::vec2(0.5f, 1.0f), glm::vec2(0.0f, 0.0f), + glm::vec2(1.0f, 0.0f), glm::vec2(0.5f, 1.0f), glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 0.0f)}; std::ranges::copy(texturesCoord, texCoords.begin()); // Compute normals for each face of the pyramid - glm::vec3 norm[18]; - for (int i = 0; i < 18; i += 3) - { - const glm::vec3 normal = glm::normalize( - glm::cross( - verts[i + 1] - verts[i], - verts[i + 2] - verts[i])); - - norm[i] = normal; + std::array norm{}; + for (int i = 0; i < 18; i += 3) { + const glm::vec3 normal = glm::normalize(glm::cross(verts[i + 1] - verts[i], verts[i + 2] - verts[i])); + + norm[i] = normal; norm[i + 1] = normal; norm[i + 2] = normal; } @@ -87,7 +72,6 @@ namespace nexo::renderer std::ranges::copy(norm, normals.begin()); } - /** * * @brief Creates a vertex array object (VAO) for a pyramid mesh. @@ -96,21 +80,16 @@ namespace nexo::renderer */ std::shared_ptr NxRenderer3D::getPyramidVAO() { - constexpr unsigned int nbVerticesPyramid = 18; + constexpr unsigned int nbVerticesPyramid = 18; static std::shared_ptr pyramidVao = nullptr; - if (pyramidVao) - return pyramidVao; + if (pyramidVao) return pyramidVao; - pyramidVao = createVertexArray(); - const auto vertexBuffer = createVertexBuffer(nbVerticesPyramid * sizeof(NxVertex)); + pyramidVao = createVertexArray(); + const auto vertexBuffer = createVertexBuffer(nbVerticesPyramid * sizeof(NxVertex)); const NxBufferLayout pyramidVertexBufferLayout = { - {NxShaderDataType::FLOAT3, "aPos"}, - {NxShaderDataType::FLOAT2, "aTexCoord"}, - {NxShaderDataType::FLOAT3, "aNormal"}, - {NxShaderDataType::FLOAT3, "aTangent"}, - {NxShaderDataType::FLOAT3, "aBiTangent"}, - {NxShaderDataType::INT, "aEntityID"} - }; + {NxShaderDataType::FLOAT3, "aPos"}, {NxShaderDataType::FLOAT2, "aTexCoord"}, + {NxShaderDataType::FLOAT3, "aNormal"}, {NxShaderDataType::FLOAT3, "aTangent"}, + {NxShaderDataType::FLOAT3, "aBiTangent"}, {NxShaderDataType::INT, "aEntityID"}}; vertexBuffer->setLayout(pyramidVertexBufferLayout); std::array vertices{}; @@ -119,22 +98,20 @@ namespace nexo::renderer genPyramidMesh(vertices, texCoords, normals); std::vector vertexData(nbVerticesPyramid); - for (unsigned int i = 0; i < nbVerticesPyramid; ++i) - { - vertexData[i].position = vertices[i]; - vertexData[i].texCoord = texCoords[i]; - vertexData[i].normal = normals[i]; - vertexData[i].tangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default tangent + for (unsigned int i = 0; i < nbVerticesPyramid; ++i) { + vertexData[i].position = vertices[i]; + vertexData[i].texCoord = texCoords[i]; + vertexData[i].normal = normals[i]; + vertexData[i].tangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default tangent vertexData[i].bitangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default bi tangent - vertexData[i].entityID = 0; // Default entity ID + vertexData[i].entityID = 0; // Default entity ID } vertexBuffer->setData(vertexData.data(), vertexData.size() * sizeof(NxVertex)); pyramidVao->addVertexBuffer(vertexBuffer); std::vector indices(nbVerticesPyramid); - for (uint32_t i = 0; i < nbVerticesPyramid; ++i) - indices[i] = i; + for (uint32_t i = 0; i < nbVerticesPyramid; ++i) indices[i] = i; const auto indexBuffer = createIndexBuffer(); indexBuffer->setData(indices.data(), indices.size()); @@ -142,4 +119,4 @@ namespace nexo::renderer return pyramidVao; } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/primitives/Sphere.cpp b/engine/src/renderer/primitives/Sphere.cpp index 31cd1296a..28ed8f311 100644 --- a/engine/src/renderer/primitives/Sphere.cpp +++ b/engine/src/renderer/primitives/Sphere.cpp @@ -16,24 +16,22 @@ #include #ifndef M_PI -#define M_PI 3.14159265358979323846 + #define M_PI 3.14159265358979323846 #endif #ifndef M_PI_2 -#define M_PI_2 1.57079632679489661923 // π/2 + #define M_PI_2 1.57079632679489661923 // π/2 #endif #include #define GLM_ENABLE_EXPERIMENTAL #include -#include #include #include +#include -namespace nexo::renderer -{ +namespace nexo::renderer { static void normalizeVertices(std::vector& vertices) { - for (auto& vertex : vertices) - { + for (auto& vertex : vertices) { vertex = normalize(vertex); } } @@ -44,20 +42,20 @@ namespace nexo::renderer vertices.reserve(12); // Reserve space for the 12 vertices of the icosahedron const float phi = (1.0f + sqrtf(5.0f)) * 0.5f; // golden ratio - float a = 1.0f; - float b = 1.0f / phi; - - vertices.emplace_back(0, b, -a); // 0 - vertices.emplace_back(b, a, 0); // 1 - vertices.emplace_back(-b, a, 0); // 2 - vertices.emplace_back(0, b, a); // 3 - vertices.emplace_back(0, -b, a); // 4 - vertices.emplace_back(-a, 0, b); // 5 + float a = 1.0f; + float b = 1.0f / phi; + + vertices.emplace_back(0, b, -a); // 0 + vertices.emplace_back(b, a, 0); // 1 + vertices.emplace_back(-b, a, 0); // 2 + vertices.emplace_back(0, b, a); // 3 + vertices.emplace_back(0, -b, a); // 4 + vertices.emplace_back(-a, 0, b); // 5 vertices.emplace_back(0, -b, -a); // 6 - vertices.emplace_back(a, 0, -b); // 7 - vertices.emplace_back(a, 0, b); // 8 + vertices.emplace_back(a, 0, -b); // 7 + vertices.emplace_back(a, 0, b); // 8 vertices.emplace_back(-a, 0, -b); // 9 - vertices.emplace_back(b, -a, 0); // 10 + vertices.emplace_back(b, -a, 0); // 10 vertices.emplace_back(-b, -a, 0); // 11 // Normalize the vertices to create a unit sphere @@ -68,34 +66,14 @@ namespace nexo::renderer static std::vector generateSphereIndices() { - std::vector indices = { - 2, 1, 0, - 1, 2, 3, - 5, 4, 3, - 4, 8, 3, - 7, 6, 0, - 6, 9, 0, - 11, 10, 4, - 10, 11, 6, - 9, 5, 2, - 5, 9, 11, - 8, 7, 1, - 7, 8, 10, - 2, 5, 3, - 8, 1, 3, - 9, 2, 0, - 1, 7, 0, - 11, 9, 6, - 7, 10, 6, - 5, 11, 4, - 10, 8, 4 - }; + std::vector indices = {2, 1, 0, 1, 2, 3, 5, 4, 3, 4, 8, 3, 7, 6, 0, 6, 9, 0, 11, 10, + 4, 10, 11, 6, 9, 5, 2, 5, 9, 11, 8, 7, 1, 7, 8, 10, 2, 5, 3, 8, + 1, 3, 9, 2, 0, 1, 7, 0, 11, 9, 6, 7, 10, 6, 5, 11, 4, 10, 8, 4}; return indices; } - struct Vec3Comparator - { + struct Vec3Comparator { bool operator()(const glm::vec3& a, const glm::vec3& b) const { if (a.x != b.x) return a.x < b.x; @@ -105,15 +83,13 @@ namespace nexo::renderer }; void loopSubdivision(std::vector& indices, std::vector& vertices, - const unsigned int nbSubdivision) + const unsigned int nbSubdivision) { - for (unsigned int i = 0; i < nbSubdivision; ++i) - { + for (unsigned int i = 0; i < nbSubdivision; ++i) { std::vector newIndices{}; std::map newVertices{}; - for (size_t j = 0; j < indices.size(); j += 3) - { + for (size_t j = 0; j < indices.size(); j += 3) { const unsigned int v1 = indices[j]; const unsigned int v2 = indices[j + 1]; const unsigned int v3 = indices[j + 2]; @@ -122,18 +98,15 @@ namespace nexo::renderer const glm::vec3 m2_pos = (vertices[v2] + vertices[v3]) / 2.0f; const glm::vec3 m3_pos = (vertices[v1] + vertices[v3]) / 2.0f; - if (!newVertices.contains(m1_pos)) - { + if (!newVertices.contains(m1_pos)) { vertices.emplace_back(m1_pos); newVertices.insert({m1_pos, static_cast(vertices.size() - 1)}); } - if (!newVertices.contains(m2_pos)) - { + if (!newVertices.contains(m2_pos)) { vertices.emplace_back(m2_pos); newVertices.insert({m2_pos, static_cast(vertices.size() - 1)}); } - if (!newVertices.contains(m3_pos)) - { + if (!newVertices.contains(m3_pos)) { vertices.emplace_back(m3_pos); newVertices.insert({m3_pos, static_cast(vertices.size() - 1)}); } @@ -170,8 +143,7 @@ namespace nexo::renderer std::vector texCoords{}; texCoords.reserve(vertices.size()); // Reserve space for texture coordinates - for (const auto vertex : vertices) - { + for (const auto vertex : vertices) { auto u = (atan2(vertex.z, vertex.x) + M_PI) / (2 * M_PI); auto v = acos(vertex.y) / M_PI; @@ -185,8 +157,7 @@ namespace nexo::renderer std::vector normals{}; normals.reserve(vertices.size()); // Reserve space for normals - for (auto vec : vertices) - { + for (auto vec : vertices) { const glm::vec3 vector1 = vec - glm::vec3(0, 0, 0); normals.emplace_back(vector1); } @@ -205,39 +176,34 @@ namespace nexo::renderer */ std::shared_ptr NxRenderer3D::getSphereVAO(const unsigned int nbSubdivision) { - static std::map > sphereVaoMap; - if (sphereVaoMap.contains(nbSubdivision)) - return sphereVaoMap[nbSubdivision]; + static std::map> sphereVaoMap; + if (sphereVaoMap.contains(nbSubdivision)) return sphereVaoMap[nbSubdivision]; - const unsigned int nbVertices = getNbVerticesSphere(nbSubdivision); - sphereVaoMap[nbSubdivision] = createVertexArray(); - const auto vertexBuffer = createVertexBuffer(nbVertices * sizeof(NxVertex)); + const unsigned int nbVertices = getNbVerticesSphere(nbSubdivision); + sphereVaoMap[nbSubdivision] = createVertexArray(); + const auto vertexBuffer = createVertexBuffer(nbVertices * sizeof(NxVertex)); const NxBufferLayout sphereVertexBufferLayout = { - {NxShaderDataType::FLOAT3, "aPos"}, - {NxShaderDataType::FLOAT2, "aTexCoord"}, - {NxShaderDataType::FLOAT3, "aNormal"}, - {NxShaderDataType::FLOAT3, "aTangent"}, - {NxShaderDataType::FLOAT3, "aBiTangent"}, - {NxShaderDataType::INT, "aEntityID"} - }; + {NxShaderDataType::FLOAT3, "aPos"}, {NxShaderDataType::FLOAT2, "aTexCoord"}, + {NxShaderDataType::FLOAT3, "aNormal"}, {NxShaderDataType::FLOAT3, "aTangent"}, + {NxShaderDataType::FLOAT3, "aBiTangent"}, {NxShaderDataType::INT, "aEntityID"}}; vertexBuffer->setLayout(sphereVertexBufferLayout); - std::vector vertices = generateSphereVertices(); + std::vector vertices = generateSphereVertices(); std::vector indices = generateSphereIndices(); loopSubdivision(indices, vertices, nbSubdivision); - const std::vector normals = generateSphereNormals(vertices); + const std::vector normals = generateSphereNormals(vertices); const std::vector texCoords = generateTextureCoords(vertices); std::vector vertexData(nbVertices); for (unsigned int i = 0; i < nbVertices; ++i) { - vertexData[i].position = glm::vec4(vertices[i], 1.0f); - vertexData[i].texCoord = texCoords[i]; - vertexData[i].normal = normals[i]; - vertexData[i].tangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default tangent + vertexData[i].position = glm::vec4(vertices[i], 1.0f); + vertexData[i].texCoord = texCoords[i]; + vertexData[i].normal = normals[i]; + vertexData[i].tangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default tangent vertexData[i].bitangent = glm::vec3(0.0f, 0.0f, 0.0f); // Default bi tangent - vertexData[i].entityID = 0; // Default entity ID + vertexData[i].entityID = 0; // Default entity ID } vertexBuffer->setData(vertexData.data(), static_cast(vertexData.size() * sizeof(NxVertex))); @@ -249,4 +215,4 @@ namespace nexo::renderer return sphereVaoMap[nbSubdivision]; } -} +} // namespace nexo::renderer diff --git a/engine/src/renderer/primitives/Tetrahedron.cpp b/engine/src/renderer/primitives/Tetrahedron.cpp index c5f2fc8e6..ea1e5917e 100644 --- a/engine/src/renderer/primitives/Tetrahedron.cpp +++ b/engine/src/renderer/primitives/Tetrahedron.cpp @@ -12,7 +12,6 @@ // /////////////////////////////////////////////////////////////////////////////// - #include "renderer/Renderer3D.hpp" #include @@ -22,19 +21,17 @@ #include #include -namespace nexo::renderer -{ +namespace nexo::renderer { /** - * @brief Generates the vertex, texture coordinate, and normal data for a tetrahedron mesh. - * - * Fills the provided arrays with 12 vertices, texture coordinates, and normals for a tetrahedron. - * - * @param vertices Array to store generated vertex positions. - * @param texCoords Array to store generated texture coordinates. - * @param normals Array to store generated normals. - */ - static void genTetrahedronMesh(std::array& vertices, - std::array& texCoords, + * @brief Generates the vertex, texture coordinate, and normal data for a tetrahedron mesh. + * + * Fills the provided arrays with 12 vertices, texture coordinates, and normals for a tetrahedron. + * + * @param vertices Array to store generated vertex positions. + * @param texCoords Array to store generated texture coordinates. + * @param normals Array to store generated normals. + */ + static void genTetrahedronMesh(std::array& vertices, std::array& texCoords, std::array& normals) { constexpr float size = 1.0f; @@ -46,39 +43,38 @@ namespace nexo::renderer constexpr auto v3 = glm::vec3(size, size, -size); // Define the 4 triangular faces (each has 3 vertices) - std::array verts = { - v0, v1, v2, - v0, v2, v3, - v0, v3, v1, - v1, v3, v2 - }; + std::array verts = {v0, v1, v2, v0, v2, v3, v0, v3, v1, v1, v3, v2}; vertices = verts; // Basic UV mapping for each face const std::array textureCoords = {{ - {0.5f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f}, // Front face - {1.0f, 0.5f}, {1.0f, 1.0f}, {0.0f, 0.0f}, // Right face - {0.0f, 0.5f}, {1.0f, 0.0f}, {1.0f, 1.0f}, // Left face - {0.0f, 1.0f}, {1.0f, 1.0f}, {0.5f, 0.0f} // Bottom face + {0.5f, 1.0f}, + {0.0f, 0.0f}, + {1.0f, 0.0f}, // Front face + {1.0f, 0.5f}, + {1.0f, 1.0f}, + {0.0f, 0.0f}, // Right face + {0.0f, 0.5f}, + {1.0f, 0.0f}, + {1.0f, 1.0f}, // Left face + {0.0f, 1.0f}, + {1.0f, 1.0f}, + {0.5f, 0.0f} // Bottom face }}; texCoords = textureCoords; - for (int i = 0; i < 12; i += 3) - { - const glm::vec3 normal = glm::normalize( - glm::cross( - vertices[i + 1] - vertices[i], - vertices[i + 2] - vertices[i])); + for (int i = 0; i < 12; i += 3) { + const glm::vec3 normal = + glm::normalize(glm::cross(vertices[i + 1] - vertices[i], vertices[i + 2] - vertices[i])); - normals[i] = normal; + normals[i] = normal; normals[i + 1] = normal; normals[i + 2] = normal; } } - /** * @brief Creates a vertex array object (VAO) for a tetrahedron mesh. * @@ -86,21 +82,16 @@ namespace nexo::renderer */ std::shared_ptr NxRenderer3D::getTetrahedronVAO() { - constexpr unsigned int nbVerticesTetrahedron = 12; + constexpr unsigned int nbVerticesTetrahedron = 12; static std::shared_ptr tetrahedronVao = nullptr; - if (tetrahedronVao) - return tetrahedronVao; + if (tetrahedronVao) return tetrahedronVao; - tetrahedronVao = createVertexArray(); + tetrahedronVao = createVertexArray(); const auto vertexBuffer = createVertexBuffer(nbVerticesTetrahedron * sizeof(NxVertex)); const NxBufferLayout tetrahedronVertexBufferLayout = { - {NxShaderDataType::FLOAT3, "aPos"}, - {NxShaderDataType::FLOAT2, "aTexCoord"}, - {NxShaderDataType::FLOAT3, "aNormal"}, - {NxShaderDataType::FLOAT3, "aTangent"}, - {NxShaderDataType::FLOAT3, "aBiTangent"}, - {NxShaderDataType::INT, "aEntityID"} - }; + {NxShaderDataType::FLOAT3, "aPos"}, {NxShaderDataType::FLOAT2, "aTexCoord"}, + {NxShaderDataType::FLOAT3, "aNormal"}, {NxShaderDataType::FLOAT3, "aTangent"}, + {NxShaderDataType::FLOAT3, "aBiTangent"}, {NxShaderDataType::INT, "aEntityID"}}; vertexBuffer->setLayout(tetrahedronVertexBufferLayout); std::array vertices{}; @@ -110,20 +101,19 @@ namespace nexo::renderer std::vector vertexData(nbVerticesTetrahedron); for (unsigned int i = 0; i < nbVerticesTetrahedron; ++i) { - vertexData[i].position = glm::vec4(vertices[i], 1.0f); - vertexData[i].texCoord = texCoords[i]; - vertexData[i].normal = normals[i]; - vertexData[i].tangent = glm::vec3(0.0f); // Default tangent + vertexData[i].position = glm::vec4(vertices[i], 1.0f); + vertexData[i].texCoord = texCoords[i]; + vertexData[i].normal = normals[i]; + vertexData[i].tangent = glm::vec3(0.0f); // Default tangent vertexData[i].bitangent = glm::vec3(0.0f); // Default bi tangent - vertexData[i].entityID = 0; // Default entity ID + vertexData[i].entityID = 0; // Default entity ID } vertexBuffer->setData(vertexData.data(), static_cast(vertexData.size() * sizeof(NxVertex))); tetrahedronVao->addVertexBuffer(vertexBuffer); std::vector indices(nbVerticesTetrahedron); - for (uint32_t i = 0; i < nbVerticesTetrahedron; ++i) - indices[i] = i; + for (uint32_t i = 0; i < nbVerticesTetrahedron; ++i) indices[i] = i; const auto indexBuffer = createIndexBuffer(); indexBuffer->setData(indices.data(), static_cast(indices.size())); @@ -131,4 +121,4 @@ namespace nexo::renderer return tetrahedronVao; } -} +} // namespace nexo::renderer diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index dd38ee246..c840af2d3 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -77,7 +77,11 @@ private unsafe struct NativeApiCallbacks [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate UInt32 CreateSphereDelegate(Vector3 position, Vector3 size, Vector3 rotation, Vector4 color, UInt32 nbSubdivision); - + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate UInt32 CreatePointLight(Vector3 position, Vector4 color, Single linear, Single quadratic); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate void CreateBodyFromShapeDelegate(UInt32 entityId, Vector3 position, Vector3 size, Vector3 rotation, UInt32 shapeType, UInt32 motionType); @@ -118,6 +122,7 @@ private unsafe struct NativeApiCallbacks public CreatePyramidDelegate NxCreatePyramid; public CreateCylinderDelegate NxCreateCylinder; public CreateSphereDelegate NxCreateSphere; + public CreatePointLight NxCreatePointLight; public CreateBodyFromShapeDelegate NxCreateBodyFromShape; public ApplyForceDelegate NxApplyForce; public GetTransformDelegate NxGetTransform; @@ -268,6 +273,19 @@ public static UInt32 CreateCube(in Vector3 position, in Vector3 size, in Vector3 } } + public static UInt32 CreatePointLight(in Vector3 position, in Vector4 color, Single linear = 0.01f, Single quadratic = 0.0010f) + { + try + { + return s_callbacks.NxCreatePointLight.Invoke(position, color, linear, quadratic); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling CreatePointLight: {ex.Message}"); + return UInt32.MaxValue; + } + } + /// /// Creates a tetrahedron entity in the native engine /// diff --git a/engine/src/scripting/managed/Scripts/CubeSystem.cs b/engine/src/scripting/managed/Scripts/CubeSystem.cs index f41af769e..4baacb3c2 100644 --- a/engine/src/scripting/managed/Scripts/CubeSystem.cs +++ b/engine/src/scripting/managed/Scripts/CubeSystem.cs @@ -38,6 +38,16 @@ private struct CubeAnimationState : IComponentBase } private readonly List _cubes = []; + private readonly List _lights = []; + private readonly List _colors = new(){ + new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Magenta + new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Blue + new Vector4(1.0f, 0.5f, 0.0f, 1.0f), // Orange + new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // Green + new Vector4(1.0f, 1.0f, 0.0f, 1.0f) // Yellow + }; + private static int _colorIndex = 0; + private static bool _spawnLightOrCube = true; protected override void OnInitialize(WorldState worldState) { @@ -56,7 +66,7 @@ private void MoveCube(UInt32 cubeId, Single deltaTime) // Circling cube effect float speed = 1.0f; float radius = 7.0f; - Vector3 origin = new Vector3(0, 5, 0); + Vector3 origin = new Vector3(50.0f, 5.0f, 0.0f); state.Angle += speed * deltaTime; if (state.Angle > MathF.PI * 2.0f) @@ -83,6 +93,29 @@ private void MoveCube(UInt32 cubeId, Single deltaTime) transform.size.Z = startScale + ((MathF.Sin(state.BreathingPhase) * 0.5f + 0.5f) * (endScale - startScale)); } + + private void MoveLight(UInt32 lightId, Single deltaTime) + { + ref Transform transform = ref NativeInterop.GetComponent(lightId); + ref CubeAnimationState state = ref NativeInterop.GetComponent(lightId); + + // Circling light effect + float speed = 1.0f; + float radius = 7.0f; + Vector3 origin = new Vector3(50.0f, 5.0f, 0.0f); + + state.Angle += speed * deltaTime; + if (state.Angle > MathF.PI * 2.0f) + { + state.Angle -= MathF.PI * 2.0f; + } + + transform.pos = origin + new Vector3( + MathF.Cos(state.Angle) * radius, + 0, + MathF.Sin(state.Angle) * radius + ); + } private void SpawnCube(Vector3 position, Vector3 size, Vector3 rotation, Vector4 color) { @@ -97,10 +130,25 @@ private void SpawnCube(Vector3 position, Vector3 size, Vector3 rotation, Vector4 NativeInterop.AddComponent(cubeId, ref testComponent); _cubes.Add(cubeId); } + + private void SpawnLight(Vector3 position, Vector4 color) + { + var lightId = NativeInterop.CreatePointLight(position, color); + var state = new CubeAnimationState + { + Angle = Random.Shared.NextSingle() * MathF.PI * 2.0f, + BreathingPhase = Random.Shared.NextSingle() * MathF.PI * 2.0f + }; + NativeInterop.AddComponent(lightId, ref state); + _lights.Add(lightId); + } protected override void OnUpdate(WorldState worldState) { Single deltaTime = (Single)worldState.Time.DeltaTime; + + Byte maxCubes = 10; + Byte maxLights = 5; // If 2 seconds have passed since last spawn, spawn a new cube if (worldState.Time.TotalTime % 2.0 < deltaTime) @@ -114,18 +162,39 @@ protected override void OnUpdate(WorldState worldState) Random.Shared.NextSingle(), 1.0f ); - SpawnCube(position, size, rotation, color); + + if (_spawnLightOrCube) + { + if (_lights.Count < maxLights) + { + SpawnLight(position, _colors[_colorIndex]); + _colorIndex = (_colorIndex + 1) % _colors.Count; + } + } else + { + if (_cubes.Count < maxCubes) + { + SpawnCube(position, size, rotation, color); + } + } + Logger.Log(LogLevel.Info, $"Spawned new {(_spawnLightOrCube ? "light" : "cube")}, total cubes: {_cubes.Count}, total lights: {_lights.Count}"); + _spawnLightOrCube ^= true; } foreach (var cubeId in _cubes) { MoveCube(cubeId, deltaTime); } + foreach (var lightId in _lights) + { + MoveLight(lightId, deltaTime); + } } protected override void OnShutdown(WorldState worldState) { _cubes.Clear(); + _lights.Clear(); Logger.Log(LogLevel.Info, $"Shutting down {Name} system"); } diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index e184eac59..333d3833b 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -15,13 +15,16 @@ #include #include "NativeApi.hpp" + +#include + #include "EntityFactory3D.hpp" #include "Logger.hpp" #include "Nexo.hpp" -#include "components/Uuid.hpp" #include "components/PhysicsBodyComponent.hpp" -#include "ui/Field.hpp" +#include "components/Uuid.hpp" #include "systems/PhysicsSystem.hpp" +#include "ui/Field.hpp" namespace nexo::scripting { @@ -49,6 +52,14 @@ namespace nexo::scripting { LOG(static_cast(level), "[Scripting] {}", message); } + ecs::Entity NxCreatePointLight(const Vector3 position, const Vector4 color, const float linear, const float quadratic) + { + auto& app = Application::getInstance(); + const ecs::Entity pointLight = LightFactory::createPointLight(position, color, linear, quadratic); + app.getSceneManager().getScene(0).addEntity(pointLight); + return pointLight; + } + ecs::Entity NxCreateCube(const Vector3 position, const Vector3 size, const Vector3 rotation, const Vector4 color) { auto& app = Application::getInstance(); diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index c92e57e1a..3a79b1326 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -100,6 +100,8 @@ namespace nexo::scripting { NEXO_RET(ecs::Entity) NxCreatePyramid(Vector3 position, Vector3 size, Vector3 rotation, Vector4 color); NEXO_RET(ecs::Entity) NxCreateCylinder(Vector3 position, Vector3 size, Vector3 rotation, Vector4 color, UInt32 nbSegment); NEXO_RET(ecs::Entity) NxCreateSphere(Vector3 position, Vector3 size, Vector3 rotation, Vector4 color, UInt32 nbSubdivision); + + NEXO_RET(ecs::Entity) NxCreatePointLight(Vector3 position, Vector4 color, float linear, float quadratic); // Physics functions NEXO_RET(void) NxCreateBodyFromShape(ecs::Entity entity, Vector3 position, Vector3 size, Vector3 rotation, UInt32 shapeType, UInt32 motionType); @@ -118,6 +120,8 @@ namespace nexo::scripting { ApiCallback NxCreatePyramid{&scripting::NxCreatePyramid}; ApiCallback NxCreateCylinder{&scripting::NxCreateCylinder}; ApiCallback NxCreateSphere{&scripting::NxCreateSphere}; + + ApiCallback NxCreatePointLight{&scripting::NxCreatePointLight}; // Physics callbacks ApiCallback NxCreateBodyFromShape{&scripting::NxCreateBodyFromShape}; diff --git a/engine/src/systems/AABBDebugSystem.cpp b/engine/src/systems/AABBDebugSystem.cpp new file mode 100644 index 000000000..f6cb69efc --- /dev/null +++ b/engine/src/systems/AABBDebugSystem.cpp @@ -0,0 +1,96 @@ +//// AABBDebugSystem.cpp /////////////////////////////////////////////////// +// +// ⢀⢀⢀⣤⣤⣤⡀⢀⢀⢀⢀⢀⢀⢠⣤⡄⢀⢀⢀⢀⣠⣤⣤⣤⣤⣤⣤⣤⣤⣤⡀⢀⢀⢀⢠⣤⣄⢀⢀⢀⢀⢀⢀⢀⣤⣤⢀⢀⢀⢀⢀⢀⢀⢀⣀⣄⢀⢀⢠⣄⣀⢀⢀⢀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⣿⣷⡀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡟⡛⡛⡛⡛⡛⡛⡛⢁⢀⢀⢀⢀⢻⣿⣦⢀⢀⢀⢀⢠⣾⡿⢃⢀⢀⢀⢀⢀⣠⣾⣿⢿⡟⢀⢀⡙⢿⢿⣿⣦⡀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⡛⣿⣷⡀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡙⣿⡷⢀⢀⣰⣿⡟⢁⢀⢀⢀⢀⢀⣾⣿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⣿⡆⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⡈⢿⣷⡄⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣇⣀⣀⣀⣀⣀⣀⣀⢀⢀⢀⢀⢀⢀⢀⡈⢀⢀⣼⣿⢏⢀⢀⢀⢀⢀⢀⣼⣿⡏⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡘⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⡈⢿⣿⡄⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣿⢿⢿⢿⢿⢿⢿⢿⢇⢀⢀⢀⢀⢀⢀⢀⢠⣾⣿⣧⡀⢀⢀⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⡈⢿⣿⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣰⣿⡟⡛⣿⣷⡄⢀⢀⢀⢀⢀⢿⣿⣇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⡈⢿⢀⢀⢸⣿⡇⢀⢀⢀⢀⡛⡟⢁⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⡟⢀⢀⡈⢿⣿⣄⢀⢀⢀⢀⡘⣿⣿⣄⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⢏⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⢀⢀⢀⣠⣾⡿⢃⢀⢀⢀⢀⢀⢻⣿⣧⡀⢀⢀⢀⡈⢻⣿⣷⣦⣄⢀⢀⣠⣤⣶⣿⡿⢋⢀⢀⢀⢀ +// ⢀⢀⢀⢿⢿⢀⢀⢀⢀⢀⢀⢀⢀⢸⢿⢃⢀⢀⢀⢀⢻⢿⢿⢿⢿⢿⢿⢿⢿⢿⢃⢀⢀⢀⢿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⡗⢀⢀⢀⢀⢀⡈⡉⡛⡛⢀⢀⢹⡛⢋⢁⢀⢀⢀⢀⢀⢀ +// +// Author: Mehdy MORVAN +// Date: 05/10/2025 +// Description: Source file for the aabb debug system +// +/////////////////////////////////////////////////////////////////////////////// + +#include "AABBDebugSystem.hpp" +#include "DrawCommand.hpp" +#include "Logger.hpp" +#include "Renderer3D.hpp" +#include "ShaderLibrary.hpp" +#include "components/Parent.hpp" +#include "math/Bounds.hpp" +#include "renderPasses/Masks.hpp" + +namespace nexo::system { + + static glm::mat4 modelFromAABB(const math::AABB &box) + { + const glm::vec3 center = 0.5f * (box.min + box.max); + const glm::vec3 extent = 0.5f * (box.max - box.min); + glm::mat4 M(1.0f); + M = glm::translate(M, center); + return glm::scale(M, extent); + } + + static renderer::DrawCommand createDrawCommand(const std::shared_ptr &shader, + const math::AABB &box, + const components::TransformComponent &transform) + { + renderer::DrawCommand cmd; + cmd.type = renderer::CommandType::LINE; + cmd.vao = renderer::NxRenderer3D::getBoxVAO(); + cmd.shader = shader; + cmd.uniforms["uMatModel"] = transform.worldMatrix * modelFromAABB(box); + cmd.uniforms["uColor"] = glm::vec3(1.0f, 0.0f, 0.0f); + + cmd.filterMask = 0; + cmd.filterMask |= renderer::F_FORWARD_PASS; + return cmd; + } + + void AABBDebugSystem::update() + { + auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; + + const auto sceneRendered = static_cast(renderContext.sceneRendered); + + std::vector drawCommands; + auto shader = renderer::ShaderLibrary::getInstance().get("AABB Debug"); + if (!shader) { + LOG_ONCE(NEXO_WARN, "Could not load AABB debug shader, skipping rendering"); + return; + } + Logger::resetOnce(NEXO_LOG_ONCE_KEY("Could not load AABB debug shader, skipping rendering")); + + for (const ecs::Entity entity : entities) { + auto &sceneTag = getComponent(entity); + if (sceneTag.id != sceneRendered) continue; + auto &rootComponent = getComponent(entity); + auto &transform = getComponent(entity); + auto model = rootComponent.modelRef.lock(); + if (!model) { + LOG_ONCE(NEXO_WARN, "Model for entity {} is not available (maybe not loaded ?)", entity); + continue; + } + Logger::resetOnce(NEXO_LOG_ONCE_KEY("Model for entity {} is not available (maybe not loaded ?)", entity)); + if (model->rootBounds.empty()) { + LOG_ONCE(NEXO_WARN, "AABB for entity {} does not seem to exist", entity); + continue; + } + Logger::resetOnce(NEXO_LOG_ONCE_KEY("AABB for entity {} does not seem to exist", entity)); + drawCommands.push_back(createDrawCommand(shader, model->rootBounds, transform)); + } + + for (auto &camera : renderContext.cameras) { + for (auto &cmd : drawCommands) { + cmd.uniforms["uViewProjection"] = camera.viewProjectionMatrix; + } + camera.pipeline.addDrawCommands(drawCommands); + } + } +} diff --git a/engine/src/systems/AABBDebugSystem.hpp b/engine/src/systems/AABBDebugSystem.hpp new file mode 100644 index 000000000..02027aecf --- /dev/null +++ b/engine/src/systems/AABBDebugSystem.hpp @@ -0,0 +1,37 @@ +//// AABBDebugSystem.hpp /////////////////////////////////////////////////// +// +// ⢀⢀⢀⣤⣤⣤⡀⢀⢀⢀⢀⢀⢀⢠⣤⡄⢀⢀⢀⢀⣠⣤⣤⣤⣤⣤⣤⣤⣤⣤⡀⢀⢀⢀⢠⣤⣄⢀⢀⢀⢀⢀⢀⢀⣤⣤⢀⢀⢀⢀⢀⢀⢀⢀⣀⣄⢀⢀⢠⣄⣀⢀⢀⢀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⣿⣷⡀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡟⡛⡛⡛⡛⡛⡛⡛⢁⢀⢀⢀⢀⢻⣿⣦⢀⢀⢀⢀⢠⣾⡿⢃⢀⢀⢀⢀⢀⣠⣾⣿⢿⡟⢀⢀⡙⢿⢿⣿⣦⡀⢀⢀⢀⢀ +// ⢀⢀⢀⣿⣿⡛⣿⣷⡀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡙⣿⡷⢀⢀⣰⣿⡟⢁⢀⢀⢀⢀⢀⣾⣿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⣿⡆⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⡈⢿⣷⡄⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣇⣀⣀⣀⣀⣀⣀⣀⢀⢀⢀⢀⢀⢀⢀⡈⢀⢀⣼⣿⢏⢀⢀⢀⢀⢀⢀⣼⣿⡏⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⡘⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⡈⢿⣿⡄⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⣿⢿⢿⢿⢿⢿⢿⢿⢇⢀⢀⢀⢀⢀⢀⢀⢠⣾⣿⣧⡀⢀⢀⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⡈⢿⣿⢀⢀⢸⣿⡇⢀⢀⢀⢀⣿⣿⡇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣰⣿⡟⡛⣿⣷⡄⢀⢀⢀⢀⢀⢿⣿⣇⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣿⣿⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⡈⢿⢀⢀⢸⣿⡇⢀⢀⢀⢀⡛⡟⢁⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⡟⢀⢀⡈⢿⣿⣄⢀⢀⢀⢀⡘⣿⣿⣄⢀⢀⢀⢀⢀⢀⢀⢀⢀⣼⣿⢏⢀⢀⢀ +// ⢀⢀⢀⣿⣿⢀⢀⢀⢀⢀⢀⢀⢀⢸⣿⡇⢀⢀⢀⢀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⢀⢀⢀⣠⣾⡿⢃⢀⢀⢀⢀⢀⢻⣿⣧⡀⢀⢀⢀⡈⢻⣿⣷⣦⣄⢀⢀⣠⣤⣶⣿⡿⢋⢀⢀⢀⢀ +// ⢀⢀⢀⢿⢿⢀⢀⢀⢀⢀⢀⢀⢀⢸⢿⢃⢀⢀⢀⢀⢻⢿⢿⢿⢿⢿⢿⢿⢿⢿⢃⢀⢀⢀⢿⡟⢁⢀⢀⢀⢀⢀⢀⢀⡙⢿⡗⢀⢀⢀⢀⢀⡈⡉⡛⡛⢀⢀⢹⡛⢋⢁⢀⢀⢀⢀⢀⢀ +// +// Author: Mehdy MORVAN +// Date: 05/10/2025 +// Description: Header file for the aabb debug system +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "components/Parent.hpp" +#include "ecs/QuerySystem.hpp" +#include "components/Model.hpp" +#include "components/SceneComponents.hpp" +#include "components/Transform.hpp" +#include "components/RenderContext.hpp" + +namespace nexo::system { + class AABBDebugSystem final + : public ecs::QuerySystem< + ecs::Read, + ecs::Read, ecs::Read, + ecs::WriteSingleton> { + public: + void update(); + }; +} diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index 402e905dd..6b33aecc6 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -12,171 +12,156 @@ /////////////////////////////////////////////////////////////////////////////// #include "CameraSystem.hpp" +#include +#include +#include "Application.hpp" +#include "components/Camera.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" -#include "components/Camera.hpp" #include "components/Transform.hpp" #include "core/event/Input.hpp" #include "core/event/KeyCodes.hpp" -#include "Application.hpp" #include "core/event/WindowEvent.hpp" -#include -#include namespace nexo::system { - void CameraContextSystem::update() - { - auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + void CameraContextSystem::update() + { + auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; - const auto sceneRendered = static_cast(renderContext.sceneRendered); + const auto sceneRendered = static_cast(renderContext.sceneRendered); - const auto scenePartition = m_group->getPartitionView( - [](const components::SceneTag& tag) { return tag.id; } - ); + const auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag &tag) { return tag.id; }); - const auto *partition = scenePartition.getPartition(sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); - auto &app = Application::getInstance(); + auto &app = Application::getInstance(); const std::string &sceneName = app.getSceneManager().getScene(sceneRendered).getName(); - if (!partition) { + if (!partition) { LOG_ONCE(NEXO_WARN, "No camera found in scene {}, skipping", sceneName); return; } nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No camera found in scene {}, skipping", sceneName)); - const auto cameraSpan = get(); - const auto transformComponentArray = get(); - const auto entitySpan = m_group->entities(); - renderContext.cameras.reserve(partition->count); - - for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) - { - const auto &cameraComponent = cameraSpan[i]; - if (!cameraComponent.render) - continue; - const auto &transformComponent = transformComponentArray->get(entitySpan[i]); - glm::mat4 projectionMatrix = cameraComponent.getProjectionMatrix(); - glm::mat4 viewMatrix = cameraComponent.getViewMatrix(transformComponent); - const glm::mat4 viewProjectionMatrix = projectionMatrix * viewMatrix; - components::CameraContext context{viewProjectionMatrix, transformComponent.pos, cameraComponent.clearColor, cameraComponent.m_renderTarget, cameraComponent.pipeline}; - renderContext.cameras.push_back(context); - } - } - - PerspectiveCameraControllerSystem::PerspectiveCameraControllerSystem() - { - Application::getInstance().getEventManager()->registerListener(this); - Application::getInstance().getEventManager()->registerListener(this); - } - - void PerspectiveCameraControllerSystem::update(const Timestep ts) - { - const auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; - - const auto sceneRendered = static_cast(renderContext.sceneRendered); - const auto deltaTime = static_cast(ts); - - for (const ecs::Entity entity : entities) - { - auto &sceneTag = getComponent(entity); - if (!sceneTag.isActive || sceneTag.id != sceneRendered) - continue; - auto &cameraComponent = getComponent(entity); - if (!cameraComponent.active) - continue; - auto &transform = getComponent(entity); - auto &cameraController = getComponent(entity); - - cameraComponent.resizing = false; - - if (event::isKeyPressed(NEXO_KEY_SHIFT)) - cameraController.translationSpeed = 10.0f; - if (event::isKeyReleased(NEXO_KEY_SHIFT)) - cameraController.translationSpeed = 5.0f; - - glm::vec3 front = transform.quat * glm::vec3(0.0f, 0.0f, -1.0f); - glm::vec3 up = transform.quat * glm::vec3(0.0f, 1.0f, 0.0f); - glm::vec3 right = transform.quat * glm::vec3(1.0f, 0.0f, 0.0f); - - if (event::isKeyPressed(NEXO_KEY_Z)) - transform.pos += front * cameraController.translationSpeed * deltaTime; // Forward - if (event::isKeyPressed(NEXO_KEY_S)) - transform.pos -= front * cameraController.translationSpeed * deltaTime; // Backward - if (event::isKeyPressed(NEXO_KEY_Q)) - transform.pos -= right * cameraController.translationSpeed * deltaTime; // Left - if (event::isKeyPressed(NEXO_KEY_D)) - transform.pos += right * cameraController.translationSpeed * deltaTime; // Right - if (event::isKeyPressed(NEXO_KEY_SPACE)) - transform.pos += up * cameraController.translationSpeed * deltaTime; // Up - if (event::isKeyPressed(NEXO_KEY_TAB)) - transform.pos -= up * cameraController.translationSpeed * deltaTime; // Down - } - } - - void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseScroll &event) - { - const auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; - - const auto sceneRendered = static_cast(renderContext.sceneRendered); - - for (const ecs::Entity entity : entities) - { - constexpr float zoomSpeed = 0.5f; - auto &sceneTag = getComponent(entity); - const auto &cameraComponent = getComponent(entity); - if (!sceneTag.isActive || sceneTag.id != sceneRendered || !cameraComponent.active) - continue; - auto &transform = getComponent(entity); - glm::vec3 front = transform.quat * glm::vec3(0.0f, 0.0f, -1.0f); - transform.pos += front * event.y * zoomSpeed; - event.consumed = true; - } - } - - void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseMove &event) - { + const auto cameraSpan = get(); + const auto transformComponentArray = get(); + const auto entitySpan = m_group->entities(); + renderContext.cameras.reserve(partition->count); + + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { + const auto &cameraComponent = cameraSpan[i]; + if (!cameraComponent.render) continue; + const auto &transformComponent = transformComponentArray->get(entitySpan[i]); + glm::mat4 projectionMatrix = cameraComponent.getProjectionMatrix(); + glm::mat4 viewMatrix = cameraComponent.getViewMatrix(transformComponent); + const glm::mat4 viewProjectionMatrix = projectionMatrix * viewMatrix; + components::CameraContext context{viewProjectionMatrix, transformComponent.pos, cameraComponent.clearColor, + cameraComponent.m_renderTarget, cameraComponent.pipeline}; + renderContext.cameras.push_back(context); + } + } + + PerspectiveCameraControllerSystem::PerspectiveCameraControllerSystem() + { + Application::getInstance().getEventManager()->registerListener(this); + Application::getInstance().getEventManager()->registerListener(this); + } + + void PerspectiveCameraControllerSystem::update(const Timestep ts) + { + const auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; + + const auto sceneRendered = static_cast(renderContext.sceneRendered); + const auto deltaTime = static_cast(ts); + + for (const ecs::Entity entity : entities) { + auto &sceneTag = getComponent(entity); + if (!sceneTag.isActive || sceneTag.id != sceneRendered) continue; + auto &cameraComponent = getComponent(entity); + if (!cameraComponent.active) continue; + auto &transform = getComponent(entity); + auto &cameraController = getComponent(entity); + + cameraComponent.resizing = false; + + if (event::isKeyPressed(NEXO_KEY_SHIFT)) cameraController.translationSpeed = 10.0f; + if (event::isKeyReleased(NEXO_KEY_SHIFT)) cameraController.translationSpeed = 5.0f; + + glm::vec3 front = transform.quat * glm::vec3(0.0f, 0.0f, -1.0f); + glm::vec3 up = transform.quat * glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 right = transform.quat * glm::vec3(1.0f, 0.0f, 0.0f); + + if (event::isKeyPressed(NEXO_KEY_Z)) + transform.pos += front * cameraController.translationSpeed * deltaTime; // Forward + if (event::isKeyPressed(NEXO_KEY_S)) + transform.pos -= front * cameraController.translationSpeed * deltaTime; // Backward + if (event::isKeyPressed(NEXO_KEY_Q)) + transform.pos -= right * cameraController.translationSpeed * deltaTime; // Left + if (event::isKeyPressed(NEXO_KEY_D)) + transform.pos += right * cameraController.translationSpeed * deltaTime; // Right + if (event::isKeyPressed(NEXO_KEY_SPACE)) + transform.pos += up * cameraController.translationSpeed * deltaTime; // Up + if (event::isKeyPressed(NEXO_KEY_TAB)) + transform.pos -= up * cameraController.translationSpeed * deltaTime; // Down + } + } + + void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseScroll &event) + { + const auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; + + const auto sceneRendered = static_cast(renderContext.sceneRendered); + + for (const ecs::Entity entity : entities) { + constexpr float zoomSpeed = 0.5f; + auto &sceneTag = getComponent(entity); + const auto &cameraComponent = getComponent(entity); + if (!sceneTag.isActive || sceneTag.id != sceneRendered || !cameraComponent.active) continue; + auto &transform = getComponent(entity); + glm::vec3 front = transform.quat * glm::vec3(0.0f, 0.0f, -1.0f); + transform.pos += front * event.y * zoomSpeed; + event.consumed = true; + } + } + + void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseMove &event) + { auto const &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); const glm::vec2 currentMousePosition(event.x, event.y); - for (const ecs::Entity entity : entities) - { - auto &controller = getComponent(entity); - const auto &sceneTag = getComponent(entity); + for (const ecs::Entity entity : entities) { + auto &controller = getComponent(entity); + const auto &sceneTag = getComponent(entity); const auto &cameraComponent = getComponent(entity); - const bool isActiveScene = sceneTag.isActive && sceneTag.id == sceneRendered; - const bool isActiveCamera = isActiveScene && cameraComponent.active; - const bool mouseDown = event::isMouseDown(NEXO_MOUSE_LEFT); + const bool isActiveScene = sceneTag.isActive && sceneTag.id == sceneRendered; + const bool isActiveCamera = isActiveScene && cameraComponent.active; + const bool mouseDown = event::isMouseDown(NEXO_MOUSE_LEFT); // Check for scene transition - if the camera wasn't active before but is now - const bool sceneTransition = isActiveCamera && !controller.wasActiveLastFrame; + const bool sceneTransition = isActiveCamera && !controller.wasActiveLastFrame; controller.wasActiveLastFrame = isActiveCamera; // Reset position on scene transition to prevent abrupt rotation if (sceneTransition) { controller.lastMousePosition = currentMousePosition; - controller.wasMouseReleased = true; + controller.wasMouseReleased = true; continue; } - if (!isActiveCamera) - continue; + if (!isActiveCamera) continue; // Always update lastMousePosition if this is the active scene, even if not moving the camera // This ensures the position is current when we start dragging if (!mouseDown || controller.wasMouseReleased) { controller.lastMousePosition = currentMousePosition; - controller.wasMouseReleased = false; + controller.wasMouseReleased = false; continue; } @@ -186,16 +171,17 @@ namespace nexo::system { } auto &transform = getComponent(entity); - const glm::vec2 mouseDelta = (currentMousePosition - controller.lastMousePosition) * controller.mouseSensitivity; + const glm::vec2 mouseDelta = + (currentMousePosition - controller.lastMousePosition) * controller.mouseSensitivity; // Extract camera orientation vectors from current quaternion glm::vec3 right = transform.quat * glm::vec3(1.0f, 0.0f, 0.0f); - // Create rotation quaternions based on mouse movement glm::quat pitchRotation = glm::angleAxis(glm::radians(-mouseDelta.y), right); - glm::quat yawRotation = glm::angleAxis(glm::radians(-mouseDelta.x), glm::vec3(0.0f, 1.0f, 0.0f)); // World up for yaw - glm::quat newQuat = glm::normalize(yawRotation * pitchRotation * transform.quat); + glm::quat yawRotation = + glm::angleAxis(glm::radians(-mouseDelta.x), glm::vec3(0.0f, 1.0f, 0.0f)); // World up for yaw + glm::quat newQuat = glm::normalize(yawRotation * pitchRotation * transform.quat); const glm::vec3 newFront = newQuat * glm::vec3(0.0f, 0.0f, -1.0f); // Check if the resulting orientation would flip the camera (pitch constraint) @@ -206,116 +192,109 @@ namespace nexo::system { transform.quat = newQuat; controller.lastMousePosition = currentMousePosition; - event.consumed = true; + event.consumed = true; } - } - - PerspectiveCameraTargetSystem::PerspectiveCameraTargetSystem() - { - Application::getInstance().getEventManager()->registerListener(this); - Application::getInstance().getEventManager()->registerListener(this); - } - - void PerspectiveCameraTargetSystem::handleEvent(event::EventMouseScroll &event) - { - auto const &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; - - const auto sceneRendered = static_cast(renderContext.sceneRendered); - - for (const ecs::Entity entity : entities) - { - constexpr float zoomSpeed = 0.5f; - auto &tag = getComponent(entity); - const auto &cameraComponent = getComponent(entity); - if (!tag.isActive || sceneRendered != tag.id || !cameraComponent.active) - continue; - auto &target = getComponent(entity); - target.distance -= event.y * zoomSpeed; - if (target.distance < 0.1f) - target.distance = 0.1f; - - auto &transformCamera = getComponent(entity); - const auto &transformTarget = getComponent(target.targetEntity); - - glm::vec3 offset = transformCamera.pos - transformTarget.pos; - // If offset is near zero, choose a default direction. - if(glm::length(offset) < 0.001f) - offset = glm::vec3(0, 0, 1); - - offset = glm::normalize(offset) * target.distance; - - transformCamera.pos = transformTarget.pos + offset; - - glm::vec3 newFront = glm::normalize(transformTarget.pos - transformCamera.pos); - transformCamera.quat = glm::normalize(glm::quatLookAt(newFront, glm::vec3(0,1,0))); - - event.consumed = true; + } + + PerspectiveCameraTargetSystem::PerspectiveCameraTargetSystem() + { + Application::getInstance().getEventManager()->registerListener(this); + Application::getInstance().getEventManager()->registerListener(this); + } + + void PerspectiveCameraTargetSystem::handleEvent(event::EventMouseScroll &event) + { + auto const &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; + + const auto sceneRendered = static_cast(renderContext.sceneRendered); + + for (const ecs::Entity entity : entities) { + constexpr float zoomSpeed = 0.5f; + auto &tag = getComponent(entity); + const auto &cameraComponent = getComponent(entity); + if (!tag.isActive || sceneRendered != tag.id || !cameraComponent.active) continue; + auto &target = getComponent(entity); + target.distance -= event.y * zoomSpeed; + if (target.distance < 0.1f) target.distance = 0.1f; + + auto &transformCamera = getComponent(entity); + const auto &transformTarget = getComponent(target.targetEntity); + + glm::vec3 offset = transformCamera.pos - transformTarget.pos; + // If offset is near zero, choose a default direction. + if (glm::length(offset) < 0.001f) offset = glm::vec3(0, 0, 1); + + offset = glm::normalize(offset) * target.distance; + + transformCamera.pos = transformTarget.pos + offset; + + glm::vec3 newFront = glm::normalize(transformTarget.pos - transformCamera.pos); + transformCamera.quat = glm::normalize(glm::quatLookAt(newFront, glm::vec3(0, 1, 0))); + + event.consumed = true; } - } + } - void PerspectiveCameraTargetSystem::handleEvent(event::EventMouseMove &event) - { - const auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + void PerspectiveCameraTargetSystem::handleEvent(event::EventMouseMove &event) + { + const auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; - const auto sceneRendered = static_cast(renderContext.sceneRendered); + const auto sceneRendered = static_cast(renderContext.sceneRendered); - glm::vec2 currentMousePosition(event.x, event.y); + glm::vec2 currentMousePosition(event.x, event.y); - for (const ecs::Entity entity : entities) - { - const auto &sceneTag = getComponent(entity); - const auto &cameraComponent = getComponent(entity); - auto &targetComponent = getComponent(entity); - if (!sceneTag.isActive || sceneTag.id != sceneRendered || cameraComponent.resizing || !event::isMouseDown(NEXO_MOUSE_RIGHT) || !cameraComponent.active) - { - targetComponent.lastMousePosition = currentMousePosition; - continue; - } + for (const ecs::Entity entity : entities) { + const auto &sceneTag = getComponent(entity); + const auto &cameraComponent = getComponent(entity); + auto &targetComponent = getComponent(entity); + if (!sceneTag.isActive || sceneTag.id != sceneRendered || cameraComponent.resizing || + !event::isMouseDown(NEXO_MOUSE_RIGHT) || !cameraComponent.active) { + targetComponent.lastMousePosition = currentMousePosition; + continue; + } - auto &transformCameraComponent = getComponent(entity); - const auto &transformTargetComponent = getComponent(targetComponent.targetEntity); + auto &transformCameraComponent = getComponent(entity); + const auto &transformTargetComponent = + getComponent(targetComponent.targetEntity); - float deltaX = targetComponent.lastMousePosition.x - currentMousePosition.x; - float deltaY = targetComponent.lastMousePosition.y - currentMousePosition.y; + float deltaX = targetComponent.lastMousePosition.x - currentMousePosition.x; + float deltaY = targetComponent.lastMousePosition.y - currentMousePosition.y; - // Compute rotation angles based on screen dimensions. - float xAngle = deltaX * (2.0f * std::numbers::pi_v / static_cast(cameraComponent.width)); - float yAngle = deltaY * (std::numbers::pi_v / static_cast(cameraComponent.height)); + // Compute rotation angles based on screen dimensions. + float xAngle = deltaX * (2.0f * std::numbers::pi_v / static_cast(cameraComponent.width)); + float yAngle = deltaY * (std::numbers::pi_v / static_cast(cameraComponent.height)); - // Prevent excessive pitch rotation when the camera is nearly vertical. - glm::vec3 front = glm::normalize(transformTargetComponent.pos - transformCameraComponent.pos); - auto sgn = [](const float x) { return (x >= 0.0f ? 1.0f : -1.0f); }; - if (glm::dot(front, glm::vec3(0, 1, 0)) * sgn(yAngle) > 0.99f) - yAngle = 0.0f; + // Prevent excessive pitch rotation when the camera is nearly vertical. + glm::vec3 front = glm::normalize(transformTargetComponent.pos - transformCameraComponent.pos); + auto sgn = [](const float x) { return (x >= 0.0f ? 1.0f : -1.0f); }; + if (glm::dot(front, glm::vec3(0, 1, 0)) * sgn(yAngle) > 0.99f) yAngle = 0.0f; - glm::vec3 offset = (transformCameraComponent.pos - transformTargetComponent.pos); + glm::vec3 offset = (transformCameraComponent.pos - transformTargetComponent.pos); - glm::quat qYaw = glm::angleAxis(xAngle, glm::vec3(0, 1, 0)); + glm::quat qYaw = glm::angleAxis(xAngle, glm::vec3(0, 1, 0)); - // For the pitch (vertical rotation), compute the right axis. - // This is the normalized cross product between the world up and the offset vector. - glm::vec3 rightAxis = glm::normalize(glm::cross(glm::vec3(0, 1, 0), offset)); - if (glm::length(rightAxis) < 0.001f) // Fallback if the vector is degenerate. - rightAxis = glm::vec3(1, 0, 0); - glm::quat qPitch = glm::angleAxis(yAngle, rightAxis); + // For the pitch (vertical rotation), compute the right axis. + // This is the normalized cross product between the world up and the offset vector. + glm::vec3 rightAxis = glm::normalize(glm::cross(glm::vec3(0, 1, 0), offset)); + if (glm::length(rightAxis) < 0.001f) // Fallback if the vector is degenerate. + rightAxis = glm::vec3(1, 0, 0); + glm::quat qPitch = glm::angleAxis(yAngle, rightAxis); - glm::quat incrementalRotation = qYaw * qPitch; + glm::quat incrementalRotation = qYaw * qPitch; - glm::vec3 newOffset = incrementalRotation * offset; + glm::vec3 newOffset = incrementalRotation * offset; - newOffset = glm::normalize(newOffset) * targetComponent.distance; + newOffset = glm::normalize(newOffset) * targetComponent.distance; - transformCameraComponent.pos = transformTargetComponent.pos + newOffset; + transformCameraComponent.pos = transformTargetComponent.pos + newOffset; - glm::vec3 newFront = glm::normalize(transformTargetComponent.pos - transformCameraComponent.pos); - transformCameraComponent.quat = glm::normalize(glm::quatLookAt(newFront, glm::vec3(0, 1, 0))); + glm::vec3 newFront = glm::normalize(transformTargetComponent.pos - transformCameraComponent.pos); + transformCameraComponent.quat = glm::normalize(glm::quatLookAt(newFront, glm::vec3(0, 1, 0))); - targetComponent.lastMousePosition = currentMousePosition; - event.consumed = true; - } - } -} + targetComponent.lastMousePosition = currentMousePosition; + event.consumed = true; + } + } +} // namespace nexo::system diff --git a/engine/src/systems/CameraSystem.hpp b/engine/src/systems/CameraSystem.hpp index cd87fa443..02bad0018 100644 --- a/engine/src/systems/CameraSystem.hpp +++ b/engine/src/systems/CameraSystem.hpp @@ -13,114 +13,145 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "ecs/System.hpp" -#include "ecs/GroupSystem.hpp" -#include "ecs/QuerySystem.hpp" #include "Timestep.hpp" -#include "core/event/Event.hpp" -#include "core/event/WindowEvent.hpp" #include "components/Camera.hpp" -#include "components/SceneComponents.hpp" #include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" +#include "core/event/Event.hpp" +#include "core/event/WindowEvent.hpp" +#include "ecs/GroupSystem.hpp" +#include "ecs/QuerySystem.hpp" +#include "ecs/System.hpp" namespace nexo::system { - /** - * @brief System responsible for updating the camera context. - * - * This system iterates over all active camera entities and computes their view-projection - * matrices using the CameraComponent and TransformComponent. The computed CameraContext is - * then pushed into the RenderContext (a singleton component). - * - * @note Component Access Rights: - * - READ access to components::CameraComponent (owned) - * - READ access to components::SceneTag (non-owned) - * - READ access to components::TransformComponent (non-owned) - * - WRITE access to components::RenderContext (singleton) - * - * @note The system uses scene partitioning to only process camera entities belonging to the - * currently active scene (identified by RenderContext.sceneRendered). - */ - class CameraContextSystem final : public ecs::GroupSystem< - ecs::Owned< - ecs::Read>, - ecs::NonOwned< - ecs::Read, - ecs::Read>, - ecs::WriteSingleton> { - public: - void update(); - }; + /** + * @brief System responsible for updating the camera context. + * + * This system iterates over all active camera entities and computes their view-projection + * matrices using the CameraComponent and TransformComponent. The computed CameraContext is + * then pushed into the RenderContext (a singleton component). + * + * @note Component Access Rights: + * - READ access to components::CameraComponent (owned) + * - READ access to components::SceneTag (non-owned) + * - READ access to components::TransformComponent (non-owned) + * - WRITE access to components::RenderContext (singleton) + * + * @note The system uses scene partitioning to only process camera entities belonging to the + * currently active scene (identified by RenderContext.sceneRendered). + */ + class CameraContextSystem final + : public ecs::GroupSystem< + ecs::Owned>, + ecs::NonOwned, ecs::Read>, + ecs::WriteSingleton> { + public: + void update(); + }; + + /** + * @brief System for controlling perspective cameras via keyboard and mouse input. + * + * This system handles movement of perspective cameras based on keyboard input (e.g. WASD, + * space, tab) and adjusts camera orientation based on mouse movement. It also processes mouse + * scroll events for zooming. + * + * @note Component Access Rights: + * - WRITE access to components::CameraComponent + * - WRITE access to components::PerspectiveCameraController + * - READ access to components::SceneTag + * - WRITE access to components::TransformComponent + * - READ access to components::RenderContext (singleton) + * + * @note Event Listeners: + * - event::EventMouseScroll - For camera zoom functionality + * - event::EventMouseMove - For camera rotation functionality + * + * @note The system only processes camera entities belonging to the currently active scene. + */ + class PerspectiveCameraControllerSystem final + : public ecs::QuerySystem, + ecs::Write, ecs::Read, + ecs::Write, + ecs::ReadSingleton>, + LISTENS_TO(event::EventMouseScroll, event::EventMouseMove) { + public: + PerspectiveCameraControllerSystem(); + + /** + * @brief Updates camera position based on keyboard input. + * This method processes keyboard inputs (WASD, space, tab) to move the camera + * in the corresponding directions. Movement speed is adjusted based on whether + * the shift key is held down. The camera's position is updated accordingly. + * @param ts The timestep representing the time elapsed since the last update. + */ + void update(Timestep ts); + + /** + * @brief Handles mouse scroll events for zooming the camera. + * This method processes mouse scroll events to adjust the camera's distance + * from its target entity, effectively implementing a zoom functionality. + * The camera's position is updated accordingly. + * @param event The mouse scroll event containing scroll delta information. + */ + void handleEvent(event::EventMouseScroll &event) override; + + /** + * @brief Handles mouse move events for rotating the camera. + * This method processes mouse move events to rotate the camera based on + * mouse movement. It updates the camera's orientation to look around + * based on the change in mouse position. Pitch is constrained to prevent + * flipping. + * @param event The mouse move event containing current mouse position. + */ + void handleEvent(event::EventMouseMove &event) override; + }; - /** - * @brief System for controlling perspective cameras via keyboard and mouse input. - * - * This system handles movement of perspective cameras based on keyboard input (e.g. WASD, - * space, tab) and adjusts camera orientation based on mouse movement. It also processes mouse - * scroll events for zooming. - * - * @note Component Access Rights: - * - WRITE access to components::CameraComponent - * - WRITE access to components::PerspectiveCameraController - * - READ access to components::SceneTag - * - WRITE access to components::TransformComponent - * - READ access to components::RenderContext (singleton) - * - * @note Event Listeners: - * - event::EventMouseScroll - For camera zoom functionality - * - event::EventMouseMove - For camera rotation functionality - * - * @note The system only processes camera entities belonging to the currently active scene. - */ - class PerspectiveCameraControllerSystem final : public ecs::QuerySystem< - ecs::Write, - ecs::Write, - ecs::Read, - ecs::Write, - ecs::ReadSingleton>, - LISTENS_TO( - event::EventMouseScroll, - event::EventMouseMove) { - public: - PerspectiveCameraControllerSystem(); - void update(Timestep ts); + /** + * @brief System for controlling perspective cameras that orbit around a target. + * + * This system processes mouse scroll and mouse move events to adjust the camera's distance + * from its target entity as well as its orientation to always face the target. The camera's + * position is updated accordingly. + * + * @note Component Access Rights: + * - WRITE access to components::CameraComponent + * - WRITE access to components::PerspectiveCameraTarget + * - READ access to components::SceneTag + * - WRITE access to components::TransformComponent + * - READ access to components::RenderContext (singleton) + * + * @note Event Listeners: + * - event::EventMouseScroll - For adjusting camera distance from target + * - event::EventMouseMove - For orbiting camera around target + * + * @note The system only processes camera entities belonging to the currently active scene. + */ + class PerspectiveCameraTargetSystem final + : public ecs::QuerySystem, + ecs::Write, ecs::Read, + ecs::Write, + ecs::ReadSingleton>, + LISTENS_TO(event::EventMouseScroll, event::EventMouseMove) { + public: + PerspectiveCameraTargetSystem(); - void handleEvent(event::EventMouseScroll &event) override; - void handleEvent(event::EventMouseMove &event) override; - }; + /** * @brief Handles mouse move events to orbit the camera around its target. + * This method processes mouse move events to rotate the camera around its + * target entity based on mouse movement. The camera's position and orientation + * are updated accordingly to maintain focus on the target. + * @param event The mouse move event containing current mouse position. + */ + void handleEvent(event::EventMouseMove &event) override; - /** - * @brief System for controlling perspective cameras that orbit around a target. - * - * This system processes mouse scroll and mouse move events to adjust the camera's distance - * from its target entity as well as its orientation to always face the target. The camera's - * position is updated accordingly. - * - * @note Component Access Rights: - * - WRITE access to components::CameraComponent - * - WRITE access to components::PerspectiveCameraTarget - * - READ access to components::SceneTag - * - WRITE access to components::TransformComponent - * - READ access to components::RenderContext (singleton) - * - * @note Event Listeners: - * - event::EventMouseScroll - For adjusting camera distance from target - * - event::EventMouseMove - For orbiting camera around target - * - * @note The system only processes camera entities belonging to the currently active scene. - */ - class PerspectiveCameraTargetSystem final : public ecs::QuerySystem< - ecs::Write, - ecs::Write, - ecs::Read, - ecs::Write, - ecs::ReadSingleton>, - LISTENS_TO( - event::EventMouseScroll, - event::EventMouseMove) { - public: - PerspectiveCameraTargetSystem(); - void handleEvent(event::EventMouseMove &event) override; - void handleEvent(event::EventMouseScroll &event) override; - }; -} + /** + * @brief Handles mouse scroll events to adjust camera distance from target. + * This method processes mouse scroll events to change the camera's distance + * from its target entity, effectively implementing a zoom functionality. + * The camera's position is updated accordingly. + * @param event The mouse scroll event containing scroll delta information. + */ + void handleEvent(event::EventMouseScroll &event) override; + }; +} // namespace nexo::system diff --git a/engine/src/systems/LightSystem.cpp b/engine/src/systems/LightSystem.cpp index fec3ff012..ea84bf207 100644 --- a/engine/src/systems/LightSystem.cpp +++ b/engine/src/systems/LightSystem.cpp @@ -15,11 +15,11 @@ #include "LightSystem.hpp" namespace nexo::system { - void LightSystem::update() const - { - m_ambientLightSystem->update(); - m_directionalLightSystem->update(); - m_pointLightSystem->update(); - m_spotLightSystem->update(); - } -} + void LightSystem::update() const + { + m_ambientLightSystem->update(); + m_directionalLightSystem->update(); + m_pointLightSystem->update(); + m_spotLightSystem->update(); + } +} // namespace nexo::system diff --git a/engine/src/systems/LightSystem.hpp b/engine/src/systems/LightSystem.hpp index fbda39f38..1d2be7e66 100644 --- a/engine/src/systems/LightSystem.hpp +++ b/engine/src/systems/LightSystem.hpp @@ -20,7 +20,7 @@ namespace nexo::system { - /** + /** * @brief High-level system that aggregates and updates all light systems. * * The LightSystem manages the update calls for the various light systems: @@ -32,22 +32,37 @@ namespace nexo::system { * - PointLightsSystem * - SpotLightsSystem */ - class LightSystem { - public: - LightSystem(const std::shared_ptr &ambientSystem, - const std::shared_ptr &directionalSystem, - const std::shared_ptr &pointSystem, - const std::shared_ptr &spotSystem) : - m_ambientLightSystem(ambientSystem), - m_directionalLightSystem(directionalSystem), - m_pointLightSystem(pointSystem), - m_spotLightSystem(spotSystem) {} + class LightSystem { + public: + /** + * @brief Constructs a LightSystem with the specified light subsystems. + * + * @param ambientSystem Shared pointer to the AmbientLightSystem. + * @param directionalSystem Shared pointer to the DirectionalLightsSystem. + * @param pointSystem Shared pointer to the PointLightsSystem. + * @param spotSystem Shared pointer to the SpotLightsSystem. + */ + LightSystem(const std::shared_ptr &ambientSystem, + const std::shared_ptr &directionalSystem, + const std::shared_ptr &pointSystem, + const std::shared_ptr &spotSystem) + : m_ambientLightSystem(ambientSystem), m_directionalLightSystem(directionalSystem), + m_pointLightSystem(pointSystem), m_spotLightSystem(spotSystem) + {} - void update() const; - private: - std::shared_ptr m_ambientLightSystem = nullptr; - std::shared_ptr m_directionalLightSystem = nullptr; - std::shared_ptr m_pointLightSystem = nullptr; - std::shared_ptr m_spotLightSystem = nullptr; - }; -} + /** + * @brief Updates all light systems to reflect the current scene state. + * + * This method calls the update function of each individual light system + * to ensure that the RenderContext is populated with the correct light + * information for rendering. + */ + void update() const; + + private: + std::shared_ptr m_ambientLightSystem = nullptr; + std::shared_ptr m_directionalLightSystem = nullptr; + std::shared_ptr m_pointLightSystem = nullptr; + std::shared_ptr m_spotLightSystem = nullptr; + }; +} // namespace nexo::system diff --git a/engine/src/systems/PhysicsSystem.cpp b/engine/src/systems/PhysicsSystem.cpp index 89fa0a9be..51ec3edca 100644 --- a/engine/src/systems/PhysicsSystem.cpp +++ b/engine/src/systems/PhysicsSystem.cpp @@ -58,7 +58,6 @@ namespace nexo::system { std::chrono::high_resolution_clock::now().time_since_epoch()).count(); const double delta = currentTime - m_lastPhysicsTime; - if (delta < fixedTimestep) return; diff --git a/engine/src/systems/PhysicsSystem.hpp b/engine/src/systems/PhysicsSystem.hpp index 2ef0ea822..822742524 100644 --- a/engine/src/systems/PhysicsSystem.hpp +++ b/engine/src/systems/PhysicsSystem.hpp @@ -31,71 +31,61 @@ #include #include -namespace nexo::system -{ - namespace Layers - { +namespace nexo::system { + namespace Layers { constexpr JPH::ObjectLayer NON_MOVING = 0; - constexpr JPH::ObjectLayer MOVING = 1; + constexpr JPH::ObjectLayer MOVING = 1; constexpr JPH::ObjectLayer NUM_LAYERS = 2; - } + } // namespace Layers - namespace BroadPhaseLayers - { + namespace BroadPhaseLayers { constexpr JPH::BroadPhaseLayer NON_MOVING(0); constexpr JPH::BroadPhaseLayer MOVING(1); constexpr JPH::uint NUM_LAYERS = 2; - } + } // namespace BroadPhaseLayers - class MyContactListener final : public JPH::ContactListener - { - public: + class MyContactListener final : public JPH::ContactListener { + public: // See: ContactListener JPH::ValidateResult OnContactValidate( - [[maybe_unused]] const JPH::Body& inBody1, - [[maybe_unused]] const JPH::Body& inBody2, + [[maybe_unused]] const JPH::Body& inBody1, [[maybe_unused]] const JPH::Body& inBody2, [[maybe_unused]] JPH::RVec3Arg inBaseOffset, [[maybe_unused]] const JPH::CollideShapeResult& inCollisionResult) override { - //cout << "Contact validate callback" << endl; + // cout << "Contact validate callback" << endl; - // Allows you to ignore a contact before it is created (using layers to not make objects collide is cheaper!) + // Allows you to ignore a contact before it is created (using layers to not make objects collide is + // cheaper!) return JPH::ValidateResult::AcceptAllContactsForThisBodyPair; } - void OnContactAdded( - [[maybe_unused]] const JPH::Body& inBody1, - [[maybe_unused]] const JPH::Body& inBody2, - [[maybe_unused]] const JPH::ContactManifold& inManifold, - [[maybe_unused]] JPH::ContactSettings& ioSettings) override + void OnContactAdded([[maybe_unused]] const JPH::Body& inBody1, [[maybe_unused]] const JPH::Body& inBody2, + [[maybe_unused]] const JPH::ContactManifold& inManifold, + [[maybe_unused]] JPH::ContactSettings& ioSettings) override { - //cout << "A contact was added" << endl; + // cout << "A contact was added" << endl; } - void OnContactPersisted( - [[maybe_unused]] const JPH::Body& inBody1, - [[maybe_unused]] const JPH::Body& inBody2, - [[maybe_unused]] const JPH::ContactManifold& inManifold, - [[maybe_unused]] JPH::ContactSettings& ioSettings) override + void OnContactPersisted([[maybe_unused]] const JPH::Body& inBody1, [[maybe_unused]] const JPH::Body& inBody2, + [[maybe_unused]] const JPH::ContactManifold& inManifold, + [[maybe_unused]] JPH::ContactSettings& ioSettings) override { - //cout << "A contact was persisted" << endl; + // cout << "A contact was persisted" << endl; } void OnContactRemoved([[maybe_unused]] const JPH::SubShapeIDPair& inSubShapePair) override { - //cout << "A contact was removed" << endl; + // cout << "A contact was removed" << endl; } }; - - class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface - { - public: + class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface { + public: BPLayerInterfaceImpl() { // Create a mapping table from object to broad phase layer mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING; - mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING; + mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING; } [[nodiscard]] JPH::uint GetNumBroadPhaseLayers() const override @@ -110,81 +100,80 @@ namespace nexo::system } #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED) - virtual const char * GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override + virtual const char* GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override { - switch ((BroadPhaseLayer::Type)inLayer) - { - case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING: return "NON_MOVING"; - case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING: return "MOVING"; - default: JPH_ASSERT(false); return "INVALID"; + switch ((BroadPhaseLayer::Type)inLayer) { + case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING: + return "NON_MOVING"; + case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING: + return "MOVING"; + default: + JPH_ASSERT(false); + return "INVALID"; } } #endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED - private: + private: JPH::BroadPhaseLayer mObjectToBroadPhase[Layers::NUM_LAYERS]{}; }; - class ObjectVsBroadPhaseLayerFilterImpl final : public JPH::ObjectVsBroadPhaseLayerFilter - { - public: + class ObjectVsBroadPhaseLayerFilterImpl final : public JPH::ObjectVsBroadPhaseLayerFilter { + public: [[nodiscard]] bool ShouldCollide(JPH::ObjectLayer inLayer1, JPH::BroadPhaseLayer inLayer2) const override { - switch (inLayer1) - { - case Layers::NON_MOVING: - return inLayer2 == BroadPhaseLayers::MOVING; - case Layers::MOVING: - return true; - default: - JPH_ASSERT(false); - return false; + switch (inLayer1) { + case Layers::NON_MOVING: + return inLayer2 == BroadPhaseLayers::MOVING; + case Layers::MOVING: + return true; + default: + JPH_ASSERT(false); + return false; } } }; - class ObjectLayerPairFilterImpl final : public JPH::ObjectLayerPairFilter - { - public: + class ObjectLayerPairFilterImpl final : public JPH::ObjectLayerPairFilter { + public: [[nodiscard]] bool ShouldCollide(JPH::ObjectLayer inObject1, JPH::ObjectLayer inObject2) const override { - switch (inObject1) - { - case Layers::NON_MOVING: - return inObject2 == Layers::MOVING; // Non moving only collides with moving - case Layers::MOVING: - return true; // Moving collides with everything - default: - JPH_ASSERT(false); - return false; + switch (inObject1) { + case Layers::NON_MOVING: + return inObject2 == Layers::MOVING; // Non moving only collides with moving + case Layers::MOVING: + return true; // Moving collides with everything + default: + JPH_ASSERT(false); + return false; } } }; enum class ShapeType { Box, Sphere, Cylinder, Tetrahedron, Pyramid }; - class PhysicsSystem final : public ecs::QuerySystem< - ecs::Write, - ecs::Write - > - { - public: + class PhysicsSystem final : public ecs::QuerySystem, + ecs::Write > { + public: PhysicsSystem(); ~PhysicsSystem() override; - PhysicsSystem(const PhysicsSystem&) = delete; + PhysicsSystem(const PhysicsSystem&) = delete; PhysicsSystem& operator=(const PhysicsSystem&) = delete; void init(); void update(); - JPH::BodyID createDynamicBody(ecs::Entity entity, const components::TransformComponent& transform) const; - JPH::BodyID createStaticBody(ecs::Entity entity, const components::TransformComponent& transform) const; - - JPH::BodyID createBody(const components::TransformComponent& transform, JPH::EMotionType motionType) const; - JPH::BodyID createBodyFromShape(ecs::Entity entity, const components::TransformComponent& transform, - ShapeType shapeType, JPH::EMotionType motionType) const; + [[nodiscard]] JPH::BodyID createDynamicBody(ecs::Entity entity, + const components::TransformComponent& transform) const; + [[nodiscard]] JPH::BodyID createStaticBody(ecs::Entity entity, + const components::TransformComponent& transform) const; + [[nodiscard]] JPH::BodyID createBody(const components::TransformComponent& transform, + JPH::EMotionType motionType) const; + [[nodiscard]] JPH::BodyID createBodyFromShape(ecs::Entity entity, + const components::TransformComponent& transform, + ShapeType shapeType, JPH::EMotionType motionType) const; void syncTransformsToBodies(ecs::Coordinator& coordinator) const; @@ -193,10 +182,16 @@ namespace nexo::system void activateBody(JPH::BodyID bodyID) const; void deactivateBody(JPH::BodyID bodyID) const; - JPH::BodyInterface* getBodyInterface() const { return bodyInterface; } - const JPH::BodyLockInterface* getBodyLockInterface() const { return bodyLockInterface; } + [[nodiscard]] JPH::BodyInterface* getBodyInterface() const + { + return bodyInterface; + } + [[nodiscard]] const JPH::BodyLockInterface* getBodyLockInterface() const + { + return bodyLockInterface; + } - private: + private: JPH::TempAllocatorImpl* tempAllocator{}; JPH::JobSystemThreadPool* jobSystem{}; JPH::PhysicsSystem* physicsSystem{}; @@ -210,9 +205,10 @@ namespace nexo::system double m_lastPhysicsTime = 0.0; - // Using hard value because Jolt documentation advice that the physics simulation should be able to be at 60fps all the time + // Using hard value because Jolt documentation advice that the physics simulation should be able to be at 60fps + // all the time constexpr static float fixedTimestep = 1.0f / 60.0f; }; -} +} // namespace nexo::system #endif // PHYSICS_SYSTEM_HPP diff --git a/engine/src/systems/RenderBillboardSystem.cpp b/engine/src/systems/RenderBillboardSystem.cpp index a58de3f41..d53d43da0 100644 --- a/engine/src/systems/RenderBillboardSystem.cpp +++ b/engine/src/systems/RenderBillboardSystem.cpp @@ -13,162 +13,167 @@ /////////////////////////////////////////////////////////////////////////////// #include "RenderBillboardSystem.hpp" +#include "Application.hpp" #include "components/BillboardMesh.hpp" +#include "components/Editor.hpp" #include "renderPasses/Masks.hpp" -#include "Application.hpp" -#include "renderer/ShaderLibrary.hpp" #include "renderer/Renderer3D.hpp" -#include "components/Editor.hpp" +#include "renderer/ShaderLibrary.hpp" namespace nexo::system { /** - * @brief Sets up the lighting uniforms in the given shader. - * - * This static helper function binds the provided shader and sets uniforms for ambient, directional, - * point, and spot lights based on the current lightContext data. After updating the uniforms, the shader is unbound. - * - * @param shader Shared pointer to the shader used for rendering. - * @param lightContext The light context containing lighting information for the scene. - * - * @note The light context must contain valid values for: - * - ambientLight - * - directionalLights (and directionalLightCount) - * - pointLights (and pointLightCount) - * - spotLights (and spotLightCount) - */ - void RenderBillboardSystem::setupLights(renderer::DrawCommand &cmd, const components::LightContext& lightContext) + * @brief Sets up the lighting uniforms in the given shader. + * + * This static helper function binds the provided shader and sets uniforms for ambient, directional, + * point, and spotlights based on the current lightContext data. After updating the uniforms, the shader is unbound. + * + * @param cmd The draw command containing the shader to set up. + * @param lightContext The light context containing lighting information for the scene. + * + * @note The light context must contain valid values for: + * - ambientLight + * - directionalLights (and directionalLightCount) + * - pointLights (and pointLightCount) + * - spotLights (and spotLightCount) + */ + void RenderBillboardSystem::setupLights(renderer::DrawCommand &cmd, const components::LightContext &lightContext) { cmd.uniforms["uAmbientLight"] = lightContext.ambientLight; cmd.uniforms["uNumPointLights"] = static_cast(lightContext.pointLightCount); - cmd.uniforms["uNumSpotLights"] = static_cast(lightContext.spotLightCount); + cmd.uniforms["uNumSpotLights"] = static_cast(lightContext.spotLightCount); - const auto &directionalLight = lightContext.dirLight; + const auto &directionalLight = lightContext.dirLight; cmd.uniforms["uDirLight.direction"] = directionalLight.direction; - cmd.uniforms["uDirLight.color"] = glm::vec4(directionalLight.color, 1.0f); + cmd.uniforms["uDirLight.color"] = glm::vec4(directionalLight.color, 1.0f); const auto &pointLightComponentArray = coord->getComponentArray(); - const auto &transformComponentArray = coord->getComponentArray(); - for (unsigned int i = 0; i < lightContext.pointLightCount; ++i) - { + const auto &transformComponentArray = coord->getComponentArray(); + for (unsigned int i = 0; i < lightContext.pointLightCount; ++i) { const auto &pointLight = pointLightComponentArray->get(lightContext.pointLights[i]); - const auto &transform = transformComponentArray->get(lightContext.pointLights[i]); - cmd.uniforms[std::format("uPointLights[{}].position", i)] = transform.pos; - cmd.uniforms[std::format("uPointLights[{}].color", i)] = glm::vec4(pointLight.color, 1.0f); - cmd.uniforms[std::format("uPointLights[{}].constant", i)] = pointLight.constant; - cmd.uniforms[std::format("uPointLights[{}].linear", i)] = pointLight.linear; + const auto &transform = transformComponentArray->get(lightContext.pointLights[i]); + cmd.uniforms[std::format("uPointLights[{}].position", i)] = transform.pos; + cmd.uniforms[std::format("uPointLights[{}].color", i)] = glm::vec4(pointLight.color, 1.0f); + cmd.uniforms[std::format("uPointLights[{}].constant", i)] = pointLight.constant; + cmd.uniforms[std::format("uPointLights[{}].linear", i)] = pointLight.linear; cmd.uniforms[std::format("uPointLights[{}].quadratic", i)] = pointLight.quadratic; } const auto &spotLightComponentArray = coord->getComponentArray(); - for (unsigned int i = 0; i < lightContext.spotLightCount; ++i) - { + for (unsigned int i = 0; i < lightContext.spotLightCount; ++i) { const auto &spotLight = spotLightComponentArray->get(lightContext.spotLights[i]); const auto &transform = transformComponentArray->get(lightContext.spotLights[i]); - cmd.uniforms[std::format("uSpotLights[{}].position", i)] = transform.pos; - cmd.uniforms[std::format("uSpotLights[{}].color", i)] = glm::vec4(spotLight.color, 1.0f); - cmd.uniforms[std::format("uSpotLights[{}].constant", i)] = spotLight.constant; - cmd.uniforms[std::format("uSpotLights[{}].linear", i)] = spotLight.linear; - cmd.uniforms[std::format("uSpotLights[{}].quadratic", i)] = spotLight.quadratic; - cmd.uniforms[std::format("uSpotLights[{}].direction", i)] = spotLight.direction; - cmd.uniforms[std::format("uSpotLights[{}].cutOff", i)] = spotLight.cutOff; + cmd.uniforms[std::format("uSpotLights[{}].position", i)] = transform.pos; + cmd.uniforms[std::format("uSpotLights[{}].color", i)] = glm::vec4(spotLight.color, 1.0f); + cmd.uniforms[std::format("uSpotLights[{}].constant", i)] = spotLight.constant; + cmd.uniforms[std::format("uSpotLights[{}].linear", i)] = spotLight.linear; + cmd.uniforms[std::format("uSpotLights[{}].quadratic", i)] = spotLight.quadratic; + cmd.uniforms[std::format("uSpotLights[{}].direction", i)] = spotLight.direction; + cmd.uniforms[std::format("uSpotLights[{}].cutOff", i)] = spotLight.cutOff; cmd.uniforms[std::format("uSpotLights[{}].outerCutoff", i)] = spotLight.outerCutoff; } } - static glm::mat4 createBillboardTransformMatrix( - const glm::vec3 &cameraPosition, - const components::TransformComponent &transform, - const glm::vec3& cameraUp = glm::vec3(0.0f, 1.0f, 0.0f), - bool constrainToY = false - ) { + static glm::mat4 createBillboardTransformMatrix(const glm::vec3 &cameraPosition, + const components::TransformComponent &transform, + const glm::vec3 &cameraUp = glm::vec3(0.0f, 1.0f, 0.0f), + bool constrainToY = false) + { glm::vec3 look = glm::normalize(cameraPosition - transform.pos); if (constrainToY) { look.y = 0.0f; - look = glm::normalize(look); + look = glm::normalize(look); glm::vec3 right = glm::normalize(glm::cross(cameraUp, look)); - glm::vec3 up = glm::cross(look, right); + glm::vec3 up = glm::cross(look, right); - return {glm::vec4(right, 0.0f), - glm::vec4(up, 0.0f), + return {glm::vec4(right, 0.0f), glm::vec4(up, 0.0f), glm::vec4(-look, 0.0f), // Negative look preserves winding - glm::vec4(0.0f, 0.0f, 0.0f, 1.0f) - }; + glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)}; } const glm::vec3 right = glm::normalize(glm::cross(cameraUp, look)); - const glm::vec3 up = glm::cross(look, right); + const glm::vec3 up = glm::cross(look, right); - return {glm::vec4(right, 0.0f), - glm::vec4(up, 0.0f), - glm::vec4(-look, 0.0f), // Negative look preserves winding - glm::vec4(0.0f, 0.0f, 0.0f, 1.0f) - }; + return {glm::vec4(right, 0.0f), glm::vec4(up, 0.0f), glm::vec4(-look, 0.0f), // Negative look preserves winding + glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)}; } - static renderer::DrawCommand createSelectedDrawCommand( - const glm::vec3 &cameraPosition, - const components::BillboardComponent &mesh, - const std::shared_ptr &materialAsset, - const components::TransformComponent &transform) + static renderer::DrawCommand createSelectedDrawCommand(const glm::vec3 &cameraPosition, + const components::BillboardComponent &mesh, + const std::shared_ptr &materialAsset, + const components::TransformComponent &transform) { renderer::DrawCommand cmd; - cmd.vao = mesh.vao; + cmd.vao = mesh.vao; const bool isOpaque = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->isOpaque : true; if (isOpaque) cmd.shader = renderer::ShaderLibrary::getInstance().get("Flat color"); else { cmd.shader = renderer::ShaderLibrary::getInstance().get("Albedo unshaded transparent"); - cmd.uniforms["uMaterial.albedoColor"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoColor : glm::vec4(0.0f); - const auto albedoTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoTexture.lock() : nullptr; - const auto albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.albedoColor"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoColor : glm::vec4(0.0f); + const auto albedoTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoTexture.lock() : nullptr; + const auto albedoTexture = + albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr; cmd.uniforms["uMaterial.albedoTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(albedoTexture); } const glm::mat4 &billboardRotation = createBillboardTransformMatrix(cameraPosition, transform); - cmd.uniforms["uMatModel"]= glm::translate(glm::mat4(1.0f), transform.pos) * - billboardRotation * + cmd.uniforms["uMatModel"] = glm::translate(glm::mat4(1.0f), transform.pos) * billboardRotation * glm::scale(glm::mat4(1.0f), glm::vec3(transform.size.x, transform.size.y, 1.0f)); cmd.filterMask = 0; cmd.filterMask = renderer::F_OUTLINE_MASK; return cmd; } - static renderer::DrawCommand createDrawCommand( - const ecs::Entity entity, - const glm::vec3 &cameraPosition, - const std::shared_ptr &shader, - const components::BillboardComponent &billboard, - const std::shared_ptr &materialAsset, - const components::TransformComponent &transform) + static renderer::DrawCommand createDrawCommand(const ecs::Entity entity, const glm::vec3 &cameraPosition, + const std::shared_ptr &shader, + const components::BillboardComponent &billboard, + const std::shared_ptr &materialAsset, + const components::TransformComponent &transform) { renderer::DrawCommand cmd; - cmd.vao = billboard.vao; - cmd.shader = shader; + cmd.vao = billboard.vao; + cmd.shader = shader; const glm::mat4 &billboardRotation = createBillboardTransformMatrix(cameraPosition, transform); - cmd.uniforms["uMatModel"]= glm::translate(glm::mat4(1.0f), transform.pos) * - billboardRotation * + cmd.uniforms["uMatModel"] = glm::translate(glm::mat4(1.0f), transform.pos) * billboardRotation * glm::scale(glm::mat4(1.0f), glm::vec3(transform.size.x, transform.size.y, 1.0f)); cmd.uniforms["uEntityId"] = static_cast(entity); - cmd.uniforms["uMaterial.albedoColor"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoColor : glm::vec4(0.0f); - const auto albedoTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoTexture.lock() : nullptr; - const auto albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.albedoColor"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoColor : glm::vec4(0.0f); + const auto albedoTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoTexture.lock() : nullptr; + const auto albedoTexture = + albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr; cmd.uniforms["uMaterial.albedoTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(albedoTexture); - cmd.uniforms["uMaterial.specularColor"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->specularColor : glm::vec4(0.0f); - const auto specularTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->metallicMap.lock() : nullptr; - const auto specularTexture = specularTextureAsset && specularTextureAsset->isLoaded() ? specularTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.specularColor"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->specularColor : glm::vec4(0.0f); + const auto specularTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->metallicMap.lock() : nullptr; + const auto specularTexture = specularTextureAsset && specularTextureAsset->isLoaded() ? + specularTextureAsset->getData()->texture : + nullptr; cmd.uniforms["uMaterial.specularTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(specularTexture); - cmd.uniforms["uMaterial.emissiveColor"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->emissiveColor : glm::vec3(0.0f); - const auto emissiveTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->emissiveMap.lock() : nullptr; - const auto emissiveTexture = emissiveTextureAsset && emissiveTextureAsset->isLoaded() ? emissiveTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.emissiveColor"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->emissiveColor : glm::vec3(0.0f); + const auto emissiveTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->emissiveMap.lock() : nullptr; + const auto emissiveTexture = emissiveTextureAsset && emissiveTextureAsset->isLoaded() ? + emissiveTextureAsset->getData()->texture : + nullptr; cmd.uniforms["uMaterial.emissiveTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(emissiveTexture); - cmd.uniforms["uMaterial.roughness"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->roughness : 1.0f; - const auto roughnessTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->roughnessMap.lock() : nullptr; - const auto roughnessTexture = roughnessTextureAsset && roughnessTextureAsset->isLoaded() ? roughnessTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.roughness"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->roughness : 1.0f; + const auto roughnessTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->roughnessMap.lock() : nullptr; + const auto roughnessTexture = roughnessTextureAsset && roughnessTextureAsset->isLoaded() ? + roughnessTextureAsset->getData()->texture : + nullptr; cmd.uniforms["uMaterial.roughnessTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(roughnessTexture); cmd.filterMask = 0; @@ -176,65 +181,59 @@ namespace nexo::system { return cmd; } - void RenderBillboardSystem::update() - { - auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + void RenderBillboardSystem::update() + { + auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; - const auto sceneRendered = static_cast(renderContext.sceneRendered); - const SceneType sceneType = renderContext.sceneType; + const auto sceneRendered = static_cast(renderContext.sceneRendered); + const SceneType sceneType = renderContext.sceneType; - const auto scenePartition = m_group->getPartitionView( - [](const components::SceneTag& tag) { return tag.id; } - ); - const auto *partition = scenePartition.getPartition(sceneRendered); - auto &app = Application::getInstance(); + const auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag &tag) { return tag.id; }); + const auto *partition = scenePartition.getPartition(sceneRendered); + auto &app = Application::getInstance(); const std::string &sceneName = app.getSceneManager().getScene(sceneRendered).getName(); - if (!partition) { + if (!partition) { LOG_ONCE(NEXO_WARN, "Nothing to render in scene {}, skipping", sceneName); return; - } + } Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneName)); - const auto transformComponentArray = get(); - const auto billboardSpan = get(); - const auto materialComponentArray = get(); - const std::span entitySpan = m_group->entities(); + const auto transformComponentArray = get(); + const auto billboardSpan = get(); + const auto materialComponentArray = get(); + const std::span entitySpan = m_group->entities(); - for (auto &camera : renderContext.cameras) { + for (auto &camera : renderContext.cameras) { std::vector drawCommands; for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { const ecs::Entity entity = entitySpan[i]; if (coord->entityHasComponent(entity) && sceneType != SceneType::EDITOR) continue; - const auto &transform = transformComponentArray->get(entitySpan[i]); + const auto &transform = transformComponentArray->get(entitySpan[i]); const auto &materialAsset = materialComponentArray->get(entitySpan[i]).material.lock(); - const auto &billboard = billboardSpan[i]; - std::string shaderStr = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->shader : ""; + const auto &billboard = billboardSpan[i]; + std::string shaderStr = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->shader : ""; auto shader = renderer::ShaderLibrary::getInstance().get(shaderStr); - auto cmd = createDrawCommand( - entity, - camera.cameraPosition, - shader, - billboard, - materialAsset, - transform - ); + auto cmd = + createDrawCommand(entity, camera.cameraPosition, shader, billboard, materialAsset, transform); cmd.uniforms["uViewProjection"] = camera.viewProjectionMatrix; - cmd.uniforms["uCamPos"] = camera.cameraPosition; + cmd.uniforms["uCamPos"] = camera.cameraPosition; setupLights(cmd, renderContext.sceneLights); drawCommands.push_back(cmd); if (coord->entityHasComponent(entity)) { - auto selectedCmd = createSelectedDrawCommand(camera.cameraPosition, billboard, materialAsset, transform); + auto selectedCmd = + createSelectedDrawCommand(camera.cameraPosition, billboard, materialAsset, transform); selectedCmd.uniforms["uViewProjection"] = camera.viewProjectionMatrix; - selectedCmd.uniforms["uCamPos"] = camera.cameraPosition; + selectedCmd.uniforms["uCamPos"] = camera.cameraPosition; setupLights(selectedCmd, renderContext.sceneLights); drawCommands.push_back(selectedCmd); } } camera.pipeline.addDrawCommands(drawCommands); - } - } -} + } + } +} // namespace nexo::system diff --git a/engine/src/systems/RenderBillboardSystem.hpp b/engine/src/systems/RenderBillboardSystem.hpp index 9387f6669..8723a6e9c 100644 --- a/engine/src/systems/RenderBillboardSystem.hpp +++ b/engine/src/systems/RenderBillboardSystem.hpp @@ -13,27 +13,45 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "components/MaterialComponent.hpp" -#include "components/Transform.hpp" #include "components/BillboardMesh.hpp" -#include "components/SceneComponents.hpp" -#include "components/Render3D.hpp" +#include "components/MaterialComponent.hpp" #include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" +#include "components/Transform.hpp" #include "ecs/GroupSystem.hpp" namespace nexo::system { - class RenderBillboardSystem final : public ecs::GroupSystem< - ecs::Owned< - ecs::Read>, - ecs::NonOwned< - ecs::Read, - ecs::Read, - ecs::Read>, - ecs::WriteSingleton> { - public: - void update(); + class RenderBillboardSystem final + : public ecs::GroupSystem< + ecs::Owned>, + ecs::NonOwned, ecs::Read, + ecs::Read>, + ecs::WriteSingleton> { + public: + /** @brief Updates the rendering of billboards in the active scene. + * This method retrieves the currently active scene from the RenderContext singleton. + * It then partitions entities by their SceneTag to find those belonging to the active scene. + * For each billboard entity found, it creates draw commands using the entity's + * BillboardComponent, MaterialComponent, and TransformComponent. + * The draw commands are then added to the camera pipelines in the RenderContext. + * If no billboard entities are found for the active scene, a warning is logged. + */ + void update(); - private: - static void setupLights(renderer::DrawCommand &cmd, const components::LightContext& lightContext); - }; -} + private: + /** @brief Sets up the lighting uniforms in the given shader. + * + * This static helper function binds the provided shader and sets uniforms for ambient, directional, + * point, and spotlights based on the current lightContext data. After updating the uniforms, the shader is + * unbound. + * @param cmd The draw command containing the shader to set up. + * @param lightContext The light context containing lighting information for the scene. + * @note The light context must contain valid values for: + * - ambientLight + * - directionalLights (and directionalLightCount) + * - pointLights (and pointLightCount) + * - spotLights (and spotLightCount) + */ + static void setupLights(renderer::DrawCommand& cmd, const components::LightContext& lightContext); + }; +} // namespace nexo::system diff --git a/engine/src/systems/RenderCommandSystem.cpp b/engine/src/systems/RenderCommandSystem.cpp index a24173b70..738d88432 100644 --- a/engine/src/systems/RenderCommandSystem.cpp +++ b/engine/src/systems/RenderCommandSystem.cpp @@ -13,21 +13,21 @@ /////////////////////////////////////////////////////////////////////////////// #include "RenderCommandSystem.hpp" +#include "Application.hpp" #include "Renderer3D.hpp" -#include "renderer/DrawCommand.hpp" +#include "components/Camera.hpp" #include "components/Editor.hpp" #include "components/Light.hpp" #include "components/Render3D.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" -#include "components/Camera.hpp" #include "components/StaticMesh.hpp" #include "components/Transform.hpp" #include "core/event/Input.hpp" #include "math/Projection.hpp" #include "math/Vector.hpp" #include "renderPasses/Masks.hpp" -#include "Application.hpp" +#include "renderer/DrawCommand.hpp" #include "renderer/ShaderLibrary.hpp" #include @@ -37,56 +37,55 @@ namespace nexo::system { /** - * @brief Sets up the lighting uniforms in the given shader. - * - * This static helper function binds the provided shader and sets uniforms for ambient, directional, - * point, and spot lights based on the current lightContext data. After updating the uniforms, the shader is unbound. - * - * @param shader Shared pointer to the shader used for rendering. - * @param lightContext The light context containing lighting information for the scene. - * - * @note The light context must contain valid values for: - * - ambientLight - * - directionalLights (and directionalLightCount) - * - pointLights (and pointLightCount) - * - spotLights (and spotLightCount) - */ - void RenderCommandSystem::setupLights(renderer::DrawCommand &cmd, const components::LightContext& lightContext) + * @brief Sets up the lighting uniforms in the given shader. + * + * This static helper function binds the provided shader and sets uniforms for ambient, directional, + * point, and spotlights based on the current lightContext data. After updating the uniforms, the shader is + * unbound. + * + * @param cmd + * @param lightContext The light context containing lighting information for the scene. + * + * @note The light context must contain valid values for: + * - ambientLight + * - directionalLights (and directionalLightCount) + * - pointLights (and pointLightCount) + * - spotLights (and spotLightCount) + */ + void RenderCommandSystem::setupLights(renderer::DrawCommand &cmd, const components::LightContext &lightContext) { cmd.uniforms["uAmbientLight"] = lightContext.ambientLight; cmd.uniforms["uNumPointLights"] = static_cast(lightContext.pointLightCount); - cmd.uniforms["uNumSpotLights"] = static_cast(lightContext.spotLightCount); + cmd.uniforms["uNumSpotLights"] = static_cast(lightContext.spotLightCount); - const auto &directionalLight = lightContext.dirLight; + const auto &directionalLight = lightContext.dirLight; cmd.uniforms["uDirLight.direction"] = directionalLight.direction; - cmd.uniforms["uDirLight.color"] = glm::vec4(directionalLight.color, 1.0f); + cmd.uniforms["uDirLight.color"] = glm::vec4(directionalLight.color, 1.0f); const auto &pointLightComponentArray = coord->getComponentArray(); - const auto &transformComponentArray = coord->getComponentArray(); - for (unsigned int i = 0; i < lightContext.pointLightCount; ++i) - { + const auto &transformComponentArray = coord->getComponentArray(); + for (unsigned int i = 0; i < lightContext.pointLightCount; ++i) { const auto &pointLight = pointLightComponentArray->get(lightContext.pointLights[i]); - const auto &transform = transformComponentArray->get(lightContext.pointLights[i]); - cmd.uniforms[std::format("uPointLights[{}].position", i)] = transform.pos; - cmd.uniforms[std::format("uPointLights[{}].color", i)] = glm::vec4(pointLight.color, 1.0f); - cmd.uniforms[std::format("uPointLights[{}].constant", i)] = pointLight.constant; - cmd.uniforms[std::format("uPointLights[{}].linear", i)] = pointLight.linear; + const auto &transform = transformComponentArray->get(lightContext.pointLights[i]); + cmd.uniforms[std::format("uPointLights[{}].position", i)] = transform.pos; + cmd.uniforms[std::format("uPointLights[{}].color", i)] = glm::vec4(pointLight.color, 1.0f); + cmd.uniforms[std::format("uPointLights[{}].constant", i)] = pointLight.constant; + cmd.uniforms[std::format("uPointLights[{}].linear", i)] = pointLight.linear; cmd.uniforms[std::format("uPointLights[{}].quadratic", i)] = pointLight.quadratic; } const auto &spotLightComponentArray = coord->getComponentArray(); - for (unsigned int i = 0; i < lightContext.spotLightCount; ++i) - { + for (unsigned int i = 0; i < lightContext.spotLightCount; ++i) { const auto &spotLight = spotLightComponentArray->get(lightContext.spotLights[i]); const auto &transform = transformComponentArray->get(lightContext.spotLights[i]); - cmd.uniforms[std::format("uSpotLights[{}].position", i)] = transform.pos; - cmd.uniforms[std::format("uSpotLights[{}].color", i)] = glm::vec4(spotLight.color, 1.0f); - cmd.uniforms[std::format("uSpotLights[{}].constant", i)] = spotLight.constant; - cmd.uniforms[std::format("uSpotLights[{}].linear", i)] = spotLight.linear; - cmd.uniforms[std::format("uSpotLights[{}].quadratic", i)] = spotLight.quadratic; - cmd.uniforms[std::format("uSpotLights[{}].direction", i)] = spotLight.direction; - cmd.uniforms[std::format("uSpotLights[{}].cutOff", i)] = spotLight.cutOff; + cmd.uniforms[std::format("uSpotLights[{}].position", i)] = transform.pos; + cmd.uniforms[std::format("uSpotLights[{}].color", i)] = glm::vec4(spotLight.color, 1.0f); + cmd.uniforms[std::format("uSpotLights[{}].constant", i)] = spotLight.constant; + cmd.uniforms[std::format("uSpotLights[{}].linear", i)] = spotLight.linear; + cmd.uniforms[std::format("uSpotLights[{}].quadratic", i)] = spotLight.quadratic; + cmd.uniforms[std::format("uSpotLights[{}].direction", i)] = spotLight.direction; + cmd.uniforms[std::format("uSpotLights[{}].cutOff", i)] = spotLight.cutOff; cmd.uniforms[std::format("uSpotLights[{}].outerCutoff", i)] = spotLight.outerCutoff; } } @@ -94,82 +93,70 @@ namespace nexo::system { static renderer::DrawCommand createOutlineDrawCommand(const components::CameraContext &camera) { renderer::DrawCommand cmd; - cmd.type = renderer::CommandType::FULL_SCREEN; + cmd.type = renderer::CommandType::FULL_SCREEN; cmd.filterMask = 0; cmd.filterMask |= renderer::F_OUTLINE_PASS; cmd.shader = renderer::ShaderLibrary::getInstance().get("Outline pulse flat"); cmd.uniforms["uViewProjection"] = camera.viewProjectionMatrix; - cmd.uniforms["uCamPos"] = camera.cameraPosition; + cmd.uniforms["uCamPos"] = camera.cameraPosition; - cmd.uniforms["uMaskTexture"] = 0; - cmd.uniforms["uDepthTexture"] = 1; + cmd.uniforms["uMaskTexture"] = 0; + cmd.uniforms["uDepthTexture"] = 1; cmd.uniforms["uDepthMaskTexture"] = 2; - cmd.uniforms["uTime"] = static_cast(glfwGetTime()); - const glm::vec2 screenSize = {camera.renderTarget->getSize().x, camera.renderTarget->getSize().y}; - cmd.uniforms["uScreenSize"] = screenSize; - cmd.uniforms["uOutlineWidth"] = 10.0f; + cmd.uniforms["uTime"] = static_cast(glfwGetTime()); + const glm::vec2 screenSize = {camera.renderTarget->getSize().x, camera.renderTarget->getSize().y}; + cmd.uniforms["uScreenSize"] = screenSize; + cmd.uniforms["uOutlineWidth"] = 10.0f; return cmd; } - static renderer::DrawCommand createGridDrawCommand(const components::CameraContext &camera, const components::RenderContext &renderContext) + static renderer::DrawCommand createGridDrawCommand(const components::CameraContext &camera, + const components::RenderContext &renderContext) { renderer::DrawCommand cmd; - cmd.type = renderer::CommandType::FULL_SCREEN; + cmd.type = renderer::CommandType::FULL_SCREEN; cmd.filterMask = 0; cmd.filterMask |= renderer::F_GRID_PASS; cmd.shader = renderer::ShaderLibrary::getInstance().get("Grid shader"); cmd.uniforms["uViewProjection"] = camera.viewProjectionMatrix; - cmd.uniforms["uCamPos"] = camera.cameraPosition; + cmd.uniforms["uCamPos"] = camera.cameraPosition; const components::RenderContext::GridParams &gridParams = renderContext.gridParams; - cmd.uniforms["uGridSize"] = gridParams.gridSize; - cmd.uniforms["uGridCellSize"] = gridParams.cellSize; - cmd.uniforms["uGridMinPixelsBetweenCells"] = gridParams.minPixelsBetweenCells; - constexpr glm::vec4 gridColorThin = {0.5f, 0.55f, 0.7f, 0.6f}; - constexpr glm::vec4 gridColorThick = {0.7f, 0.75f, 0.9f, 0.8f}; - cmd.uniforms["uGridColorThin"] = gridColorThin; - cmd.uniforms["uGridColorThick"] = gridColorThick; - - - const glm::vec2 globalMousePos = event::getMousePosition(); - glm::vec3 mouseWorldPos = camera.cameraPosition; // Default position (camera position) + cmd.uniforms["uGridSize"] = gridParams.gridSize; + cmd.uniforms["uGridCellSize"] = gridParams.cellSize; + cmd.uniforms["uGridMinPixelsBetweenCells"] = gridParams.minPixelsBetweenCells; + constexpr glm::vec4 gridColorThin = {0.5f, 0.55f, 0.7f, 0.6f}; + constexpr glm::vec4 gridColorThick = {0.7f, 0.75f, 0.9f, 0.8f}; + cmd.uniforms["uGridColorThin"] = gridColorThin; + cmd.uniforms["uGridColorThick"] = gridColorThick; + + const glm::vec2 globalMousePos = event::getMousePosition(); + glm::vec3 mouseWorldPos = camera.cameraPosition; // Default position (camera position) const glm::vec2 renderTargetSize = camera.renderTarget->getSize(); if (renderContext.isChildWindow) { // viewportBounds[0] is min (top-left), viewportBounds[1] is max (bottom-right) - const glm::vec2& viewportMin = renderContext.viewportBounds[0]; - const glm::vec2& viewportMax = renderContext.viewportBounds[1]; + const glm::vec2 &viewportMin = renderContext.viewportBounds[0]; + const glm::vec2 &viewportMax = renderContext.viewportBounds[1]; const glm::vec2 viewportSize(viewportMax.x - viewportMin.x, viewportMax.y - viewportMin.y); // Check if mouse is within the viewport bounds if (math::isPosInBounds(globalMousePos, viewportMin, viewportMax)) { - // Calculate relative mouse position within the viewport - glm::vec2 relativeMousePos( - globalMousePos.x - viewportMin.x, - globalMousePos.y - viewportMin.y - ); + glm::vec2 relativeMousePos(globalMousePos.x - viewportMin.x, globalMousePos.y - viewportMin.y); // Convert to normalized coordinates [0,1] - glm::vec2 normalizedPos( - relativeMousePos.x / viewportSize.x, - relativeMousePos.y / viewportSize.y - ); + glm::vec2 normalizedPos(relativeMousePos.x / viewportSize.x, relativeMousePos.y / viewportSize.y); // Convert to framebuffer coordinates - glm::vec2 framebufferPos( - normalizedPos.x * renderTargetSize.x, - normalizedPos.y * renderTargetSize.y - ); + glm::vec2 framebufferPos(normalizedPos.x * renderTargetSize.x, normalizedPos.y * renderTargetSize.y); // Project ray const glm::vec3 rayDir = math::projectRayToWorld( - framebufferPos.x, framebufferPos.y, - camera.viewProjectionMatrix, camera.cameraPosition, - static_cast(renderTargetSize.x), static_cast(renderTargetSize.y) - ); + framebufferPos.x, framebufferPos.y, camera.viewProjectionMatrix, camera.cameraPosition, + static_cast(renderTargetSize.x), static_cast(renderTargetSize.y)); // Calculate intersection with y=0 plane (grid plane) if (rayDir.y != 0.0f) { @@ -181,10 +168,8 @@ namespace nexo::system { } } else { const glm::vec3 rayDir = math::projectRayToWorld( - globalMousePos.x, globalMousePos.y, - camera.viewProjectionMatrix, camera.cameraPosition, - static_cast(renderTargetSize.x), static_cast(renderTargetSize.y) - ); + globalMousePos.x, globalMousePos.y, camera.viewProjectionMatrix, camera.cameraPosition, + static_cast(renderTargetSize.x), static_cast(renderTargetSize.y)); if (rayDir.y != 0.0f) { float t = -camera.cameraPosition.y / rayDir.y; @@ -195,64 +180,80 @@ namespace nexo::system { } cmd.uniforms["uMouseWorldPos"] = mouseWorldPos; - cmd.uniforms["uTime"] = static_cast(glfwGetTime()); + cmd.uniforms["uTime"] = static_cast(glfwGetTime()); return cmd; } - static renderer::DrawCommand createSelectedDrawCommand( - const components::StaticMeshComponent &mesh, - const std::shared_ptr &materialAsset, - const components::TransformComponent &transform) + static renderer::DrawCommand createSelectedDrawCommand(const components::StaticMeshComponent &mesh, + const std::shared_ptr &materialAsset, + const components::TransformComponent &transform) { renderer::DrawCommand cmd; - cmd.vao = mesh.vao; + cmd.vao = mesh.vao; const bool isOpaque = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->isOpaque : true; if (isOpaque) cmd.shader = renderer::ShaderLibrary::getInstance().get("Flat color"); else { cmd.shader = renderer::ShaderLibrary::getInstance().get("Albedo unshaded transparent"); - cmd.uniforms["uMaterial.albedoColor"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoColor : glm::vec4(0.0f); - const auto albedoTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoTexture.lock() : nullptr; - const auto albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.albedoColor"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoColor : glm::vec4(0.0f); + const auto albedoTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoTexture.lock() : nullptr; + const auto albedoTexture = + albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr; cmd.uniforms["uMaterial.albedoTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(albedoTexture); } cmd.uniforms["uMatModel"] = transform.worldMatrix; - cmd.filterMask = 0; - cmd.filterMask = renderer::F_OUTLINE_MASK; + cmd.filterMask = 0; + cmd.filterMask = renderer::F_OUTLINE_MASK; return cmd; } - static renderer::DrawCommand createDrawCommand( - const ecs::Entity entity, - const std::shared_ptr &shader, - const components::StaticMeshComponent &mesh, - const std::shared_ptr &materialAsset, - const components::TransformComponent &transform) + static renderer::DrawCommand createDrawCommand(const ecs::Entity entity, + const std::shared_ptr &shader, + const components::StaticMeshComponent &mesh, + const std::shared_ptr &materialAsset, + const components::TransformComponent &transform) { renderer::DrawCommand cmd; - cmd.vao = mesh.vao; - cmd.shader = shader; + cmd.vao = mesh.vao; + cmd.shader = shader; cmd.uniforms["uMatModel"] = transform.worldMatrix; cmd.uniforms["uEntityId"] = static_cast(entity); - cmd.uniforms["uMaterial.albedoColor"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoColor : glm::vec4(0.0f); - const auto albedoTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoTexture.lock() : nullptr; - const auto albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.albedoColor"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoColor : glm::vec4(0.0f); + const auto albedoTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->albedoTexture.lock() : nullptr; + const auto albedoTexture = + albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr; cmd.uniforms["uMaterial.albedoTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(albedoTexture); - cmd.uniforms["uMaterial.specularColor"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->specularColor : glm::vec4(0.0f); - const auto specularTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->metallicMap.lock() : nullptr; - const auto specularTexture = specularTextureAsset && specularTextureAsset->isLoaded() ? specularTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.specularColor"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->specularColor : glm::vec4(0.0f); + const auto specularTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->metallicMap.lock() : nullptr; + const auto specularTexture = specularTextureAsset && specularTextureAsset->isLoaded() ? + specularTextureAsset->getData()->texture : + nullptr; cmd.uniforms["uMaterial.specularTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(specularTexture); - cmd.uniforms["uMaterial.emissiveColor"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->emissiveColor : glm::vec3(0.0f); - const auto emissiveTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->emissiveMap.lock() : nullptr; - const auto emissiveTexture = emissiveTextureAsset && emissiveTextureAsset->isLoaded() ? emissiveTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.emissiveColor"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->emissiveColor : glm::vec3(0.0f); + const auto emissiveTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->emissiveMap.lock() : nullptr; + const auto emissiveTexture = emissiveTextureAsset && emissiveTextureAsset->isLoaded() ? + emissiveTextureAsset->getData()->texture : + nullptr; cmd.uniforms["uMaterial.emissiveTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(emissiveTexture); - cmd.uniforms["uMaterial.roughness"] = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->roughness : 1.0f; - const auto roughnessTextureAsset = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->roughnessMap.lock() : nullptr; - const auto roughnessTexture = roughnessTextureAsset && roughnessTextureAsset->isLoaded() ? roughnessTextureAsset->getData()->texture : nullptr; + cmd.uniforms["uMaterial.roughness"] = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->roughness : 1.0f; + const auto roughnessTextureAsset = + materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->roughnessMap.lock() : nullptr; + const auto roughnessTexture = roughnessTextureAsset && roughnessTextureAsset->isLoaded() ? + roughnessTextureAsset->getData()->texture : + nullptr; cmd.uniforms["uMaterial.roughnessTexIndex"] = renderer::NxRenderer3D::get().getTextureIndex(roughnessTexture); cmd.filterMask = 0; @@ -260,67 +261,57 @@ namespace nexo::system { return cmd; } - void RenderCommandSystem::update() - { - auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + void RenderCommandSystem::update() + { + auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; - const auto sceneRendered = static_cast(renderContext.sceneRendered); - const SceneType sceneType = renderContext.sceneType; + const auto sceneRendered = static_cast(renderContext.sceneRendered); + const SceneType sceneType = renderContext.sceneType; - const auto scenePartition = m_group->getPartitionView( - [](const components::SceneTag& tag) { return tag.id; } - ); - const auto *partition = scenePartition.getPartition(sceneRendered); - auto &app = Application::getInstance(); + const auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag &tag) { return tag.id; }); + const auto *partition = scenePartition.getPartition(sceneRendered); + auto &app = Application::getInstance(); const std::string &sceneName = app.getSceneManager().getScene(sceneRendered).getName(); - if (!partition) { + if (!partition) { LOG_ONCE(NEXO_WARN, "Nothing to render in scene {}, skipping", sceneName); return; - } + } Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneName)); - const auto transformSpan = get(); - const auto meshSpan = get(); - const auto materialSpan = get(); - const std::span entitySpan = m_group->entities(); + const auto transformSpan = get(); + const auto meshSpan = get(); + const auto materialSpan = get(); + const std::span entitySpan = m_group->entities(); std::vector drawCommands; - for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { - const ecs::Entity entity = entitySpan[i]; + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { + const ecs::Entity entity = entitySpan[i]; if (coord->entityHasComponent(entity) && sceneType != SceneType::EDITOR) continue; - const auto &transform = transformSpan[i]; + const auto &transform = transformSpan[i]; const auto &materialAsset = materialSpan[i].material.lock(); std::string shaderStr = materialAsset && materialAsset->isLoaded() ? materialAsset->getData()->shader : ""; - const auto &mesh = meshSpan[i]; - auto shader = renderer::ShaderLibrary::getInstance().get(shaderStr); - if (!shader) - continue; - drawCommands.push_back(createDrawCommand( - entity, - shader, - mesh, - materialAsset, - transform) - ); + const auto &mesh = meshSpan[i]; + auto shader = renderer::ShaderLibrary::getInstance().get(shaderStr); + if (!shader) continue; + drawCommands.push_back(createDrawCommand(entity, shader, mesh, materialAsset, transform)); if (coord->entityHasComponent(entity)) drawCommands.push_back(createSelectedDrawCommand(mesh, materialAsset, transform)); - } + } - for (auto &camera : renderContext.cameras) { + for (auto &camera : renderContext.cameras) { for (auto &cmd : drawCommands) { cmd.uniforms["uViewProjection"] = camera.viewProjectionMatrix; - cmd.uniforms["uCamPos"] = camera.cameraPosition; + cmd.uniforms["uCamPos"] = camera.cameraPosition; setupLights(cmd, renderContext.sceneLights); } camera.pipeline.addDrawCommands(drawCommands); if (sceneType == SceneType::EDITOR && renderContext.gridParams.enabled) camera.pipeline.addDrawCommand(createGridDrawCommand(camera, renderContext)); - if (sceneType == SceneType::EDITOR) - camera.pipeline.addDrawCommand(createOutlineDrawCommand(camera)); - } - } -} + if (sceneType == SceneType::EDITOR) camera.pipeline.addDrawCommand(createOutlineDrawCommand(camera)); + } + } +} // namespace nexo::system diff --git a/engine/src/systems/RenderCommandSystem.hpp b/engine/src/systems/RenderCommandSystem.hpp index 266968044..d833188b7 100644 --- a/engine/src/systems/RenderCommandSystem.hpp +++ b/engine/src/systems/RenderCommandSystem.hpp @@ -16,43 +16,48 @@ #include "Access.hpp" #include "DrawCommand.hpp" #include "GroupSystem.hpp" +#include "components/MaterialComponent.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" -#include "components/MaterialComponent.hpp" #include "components/StaticMesh.hpp" #include "components/Transform.hpp" namespace nexo::system { - /** - * @brief System responsible for rendering the scene. - * - * The RenderSystem iterates over the active cameras stored in the RenderContext singleton, - * sets up lighting uniforms using the sceneLights data, and then renders entities that have - * a valid RenderComponent. The system binds each camera's render target, clears the buffers, - * and then draws each renderable entity. - * - * @note Component Access Rights: - * - READ access to components::TransformComponent (owned) - * - READ access to components::RenderComponent (owned) - * - READ access to components::SceneTag (non-owned) - * - WRITE access to components::RenderContext (singleton) - * - * @note The system uses scene partitioning to only render entities belonging to the - * currently active scene (identified by RenderContext.sceneRendered). - */ - class RenderCommandSystem final : public ecs::GroupSystem< - ecs::Owned< - ecs::Read, - ecs::Read, - ecs::Read>, - ecs::NonOwned< - ecs::Read>, - ecs::WriteSingleton> { - public: - void update(); + /** + * @brief System responsible for rendering the scene. + * + * The RenderSystem iterates over the active cameras stored in the RenderContext singleton, + * sets up lighting uniforms using the sceneLights data, and then renders entities that have + * a valid RenderComponent. The system binds each camera's render target, clears the buffers, + * and then draws each renderable entity. + * + * @note Component Access Rights: + * - READ access to components::TransformComponent (owned) + * - READ access to components::RenderComponent (owned) + * - READ access to components::SceneTag (non-owned) + * - WRITE access to components::RenderContext (singleton) + * + * @note The system uses scene partitioning to only render entities belonging to the + * currently active scene (identified by RenderContext.sceneRendered). + */ + class RenderCommandSystem final + : public ecs::GroupSystem< + ecs::Owned, ecs::Read, + ecs::Read>, + ecs::NonOwned>, ecs::WriteSingleton> { + public: + /** @brief Updates the rendering of the active scene. + * This method retrieves the currently active scene from the RenderContext singleton. + * It then partitions entities by their SceneTag to find those belonging to the active scene. + * For each renderable entity found, it creates draw commands using the entity's + * TransformComponent, StaticMeshComponent, and MaterialComponent. + * The draw commands are then added to the camera pipelines in the RenderContext. + * If no renderable entities are found for the active scene, a warning is logged. + */ + void update(); - private: - static void setupLights(renderer::DrawCommand &cmd, const components::LightContext& lightContext); - }; -} + private: + static void setupLights(renderer::DrawCommand& cmd, const components::LightContext& lightContext); + }; +} // namespace nexo::system diff --git a/engine/src/systems/RenderVideoSystem.cpp b/engine/src/systems/RenderVideoSystem.cpp new file mode 100644 index 000000000..52259dc92 --- /dev/null +++ b/engine/src/systems/RenderVideoSystem.cpp @@ -0,0 +1,76 @@ +//// RenderVideoSystem.cpp //////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzz zzzzz +// zzz zzz zzz zzzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzz zzzz zzzz zzzz +// zzz zzz zzz zzzz zzzz zzzz zzzz zzzz +// zzz zzzzz zzzzzzzzzzzzz zzzz zzzz zzzzz zzzzz +// +// Author: Marie GIACOMEL +// Date: 03/09/2025 +// Description: Source file for the Video System functions +// +/////////////////////////////////////////////////////////////////////////////// + +#include "RenderVideoSystem.hpp" +#include "Application.hpp" +#include "components/Video.hpp" + +namespace nexo::system { + int RenderVideoSystem::updateVideoComponent(components::VideoComponent &videoComponent, ecs::Entity entity) + { + if (!coord->entityHasComponent(entity) || videoComponent.path.empty()) { + return 1; + } + if (!videoComponent.isLoaded) { + if (!videoComponent.loadVideoFrames(videoComponent.path)) { + LOG_ONCE(NEXO_ERROR, "Failed to load video frames from path: {}", videoComponent.path); + return 1; + } + return 0; + } + + videoComponent.updateFrame(); + + // update material to current frame + auto &materialComponent = coord->getComponent(entity); + auto ¤tFrameMaterial = videoComponent.frames[videoComponent.currentFrameIndex]; + materialComponent.material = currentFrameMaterial; + + return 0; + } + + void RenderVideoSystem::update() + { + auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; + + const auto sceneRendered = static_cast(renderContext.sceneRendered); + + const auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag &tag) { return tag.id; }); + const auto *partition = scenePartition.getPartition(sceneRendered); + auto &app = Application::getInstance(); + const std::string &sceneName = app.getSceneManager().getScene(sceneRendered).getName(); + if (!partition) { + LOG_ONCE(NEXO_WARN, "Nothing to render in scene {}, skipping", sceneName); + return; + } + Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneName)); + + auto videoSpan = get(); + const std::span entitySpan = m_group->entities(); + + for (size_t i = 0; i < videoSpan.size(); ++i) { + updateVideoComponent(videoSpan[i], entitySpan[i]); + } + } + + void RenderVideoSystem::reset() + { + auto videoSpan = get(); + for (auto & i : videoSpan) { + i.restartVideo(); + } + } +} // namespace nexo::system diff --git a/engine/src/systems/RenderVideoSystem.hpp b/engine/src/systems/RenderVideoSystem.hpp new file mode 100644 index 000000000..15050bfee --- /dev/null +++ b/engine/src/systems/RenderVideoSystem.hpp @@ -0,0 +1,50 @@ +//// RenderVideoSystem.cpp //////////////////////////////////////////////////////////// +// +// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzz zzzzz +// zzz zzz zzz zzzz zzzz zzzz zzzz zzzz +// zzz zzz zzz zzzzzzzzz zzzz zzzz zzzz +// zzz zzz zzz zzzz zzzz zzzz zzzz zzzz +// zzz zzzzz zzzzzzzzzzzzz zzzz zzzz zzzzz zzzzz +// +// Author: Marie GIACOMEL +// Date: 03/09/2025 +// Description: Header file for the Video System functions +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "components/MaterialComponent.hpp" +#include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" +#include "components/Video.hpp" +#include "ecs/GroupSystem.hpp" + +namespace nexo::system { + class RenderVideoSystem final + : public ecs::GroupSystem< + ecs::Owned>, + ecs::NonOwned, ecs::Read>, + ecs::WriteSingleton> { + public: + /** + * @brief Updates the rendering of videos in the active scene. + * This method retrieves the currently active scene from the RenderContext singleton. + * It then partitions entities by their SceneTag to find those belonging to the active scene. + * For each video entity found, it updates the VideoComponent to advance frames and updates + * the associated MaterialComponent with the current video frame. + * If no video entities are found for the active scene, a warning is logged. + */ + static int updateVideoComponent(components::VideoComponent& videoComponent, ecs::Entity entity); + + /** + * @brief Updates the rendering of videos in the active scene. + * This method retrieves the currently active scene from the RenderContext singleton. + * It then partitions entities by their SceneTag to find those belonging to the active scene. + * For each video entity found, it updates the VideoComponent to advance frames and updates + * the associated MaterialComponent with the current video frame. + * If no video entities are found for the active scene, a warning is logged. + */ + void update(); + void reset(); + }; +} // namespace nexo::system diff --git a/engine/src/systems/ScriptingSystem.cpp b/engine/src/systems/ScriptingSystem.cpp index da42d7aaa..1905e285e 100644 --- a/engine/src/systems/ScriptingSystem.cpp +++ b/engine/src/systems/ScriptingSystem.cpp @@ -21,12 +21,12 @@ namespace nexo::system { ScriptingSystem::ScriptingSystem() { scripting::HostHandler::Parameters params; - params.errorCallback = [this](const scripting::HostString& message) { + params.errorCallback = [this](const scripting::HostString &message) { LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); m_latestScriptingError = message.to_utf8(); }; - scripting::HostHandler& host = scripting::HostHandler::getInstance(); + scripting::HostHandler &host = scripting::HostHandler::getInstance(); // Initialize the host if (host.initialize(params) != scripting::HostHandler::SUCCESS) { @@ -37,7 +37,6 @@ namespace nexo::system { int ScriptingSystem::init() { - const auto &scriptHost = scripting::HostHandler::getInstance(); updateWorldState(); @@ -46,7 +45,8 @@ namespace nexo::system { return ret; } LOG(NEXO_INFO, "Scripting components initialized successfully"); - if (auto ret = scriptHost.getManagedApi().SystemBase.InitializeSystems(&m_worldState, sizeof(m_worldState)); ret != 0) { + if (auto ret = scriptHost.getManagedApi().SystemBase.InitializeSystems(&m_worldState, sizeof(m_worldState)); + ret != 0) { LOG(NEXO_ERROR, "Failed to initialize scripting systems, returned: {}", ret); return ret; } @@ -64,7 +64,7 @@ namespace nexo::system { int ScriptingSystem::update() { const auto &scriptHost = scripting::HostHandler::getInstance(); - const auto &api = scriptHost.getManagedApi(); + const auto &api = scriptHost.getManagedApi(); updateWorldState(); if (const auto ret = api.SystemBase.UpdateSystems(&m_worldState, sizeof(m_worldState)); ret != 0) { @@ -78,7 +78,7 @@ namespace nexo::system { int ScriptingSystem::shutdown() { const auto &scriptHost = scripting::HostHandler::getInstance(); - const auto &api = scriptHost.getManagedApi(); + const auto &api = scriptHost.getManagedApi(); updateWorldState(); if (auto ret = api.SystemBase.ShutdownSystems(&m_worldState, sizeof(m_worldState)); ret != 0) { @@ -91,8 +91,8 @@ namespace nexo::system { void ScriptingSystem::updateWorldState() { - Application& app = Application::getInstance(); + Application &app = Application::getInstance(); m_worldState.update(app.getWorldState()); } -} // namespace nexo::system \ No newline at end of file +} // namespace nexo::system diff --git a/engine/src/systems/ScriptingSystem.hpp b/engine/src/systems/ScriptingSystem.hpp index 4c12109d7..49485f419 100644 --- a/engine/src/systems/ScriptingSystem.hpp +++ b/engine/src/systems/ScriptingSystem.hpp @@ -21,21 +21,57 @@ namespace nexo::system { class ScriptingSystem { - public: - ScriptingSystem(); - ~ScriptingSystem() = default; + public: + ScriptingSystem(); + ~ScriptingSystem() = default; - int init(); + /** + * @brief Initializes the scripting system by setting up the world state and initializing components and + * systems. + * + * This method retrieves the singleton instance of the scripting host handler, updates the world state, + * and initializes the scripting components and systems. If any step fails, it logs an error message + * and returns a non-zero error code. + * + * @return int Returns 0 on success, or a non-zero error code on failure. + */ + int init(); - int update(); + /** @brief Updates the scripting system by refreshing the world state and invoking the update method on managed + * systems. + * + * This method first updates the internal world state to reflect any changes in the game environment. + * It then calls the Update method on the managed systems, passing in the updated world state. + * If the update fails, it logs an error message and returns a non-zero error code. + * + * @return int Returns 0 on success, or a non-zero error code on failure. + */ + int update(); - int shutdown(); + /** + * @brief Shuts down the scripting system by invoking the shutdown method on managed systems and terminating the + * host. + * + * This method calls the Shutdown method on the managed systems to perform any necessary cleanup. + * It then terminates the scripting host handler instance. If any step fails, it logs an error message + * and returns a non-zero error code. + * + * @return int Returns 0 on success, or a non-zero error code on failure. + */ + int shutdown(); - private: - void updateWorldState(); + private: + /** + * @brief Updates the internal world state used by the scripting system. + * + * This method refreshes the m_worldState member variable to ensure it accurately reflects + * the current state of the game world. This may involve gathering data from various game + * systems and components to populate the ManagedWorldState structure. + */ + void updateWorldState(); - std::string m_latestScriptingError; - scripting::ManagedWorldState m_worldState = {}; + std::string m_latestScriptingError; + scripting::ManagedWorldState m_worldState = {}; }; -} \ No newline at end of file +} // namespace nexo::system diff --git a/engine/src/systems/TransformHierarchySystem.cpp b/engine/src/systems/TransformHierarchySystem.cpp index 08a9b883c..9aac5e74d 100644 --- a/engine/src/systems/TransformHierarchySystem.cpp +++ b/engine/src/systems/TransformHierarchySystem.cpp @@ -14,9 +14,9 @@ #include "TransformHierarchySystem.hpp" #include "components/Parent.hpp" -#include "components/Transform.hpp" -#include "components/SceneComponents.hpp" #include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" +#include "components/Transform.hpp" #define GLM_ENABLE_EXPERIMENTAL #include @@ -24,30 +24,27 @@ namespace nexo::system { void TransformHierarchySystem::update() { - const auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + const auto& renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); const auto scenePartition = m_group->getPartitionView( - [](const components::SceneTag& tag) { return tag.id; } - ); - const auto *partition = scenePartition.getPartition(sceneRendered); + [](const components::SceneTag& tag) { return tag.id; }); + const auto* partition = scenePartition.getPartition(sceneRendered); if (!partition) { return; } const std::span entitySpan = m_group->entities(); - const auto &transformComponentArray = get(); + const auto& transformComponentArray = get(); // Process all root entities in the current scene for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { const ecs::Entity rootEntity = entitySpan[i]; - if (!transformComponentArray->hasComponent(rootEntity)) - continue; + if (!transformComponentArray->hasComponent(rootEntity)) continue; - auto& rootTransform = transformComponentArray->get(rootEntity); + auto& rootTransform = transformComponentArray->get(rootEntity); glm::mat4 rootWorldMatrix = calculateLocalMatrix(rootTransform); rootTransform.worldMatrix = rootWorldMatrix; updateChildTransforms(transformComponentArray, rootTransform.children, rootWorldMatrix); @@ -55,13 +52,11 @@ namespace nexo::system { } void TransformHierarchySystem::updateChildTransforms( - const std::shared_ptr> &transformComponentArray, - const std::vector& children, - const glm::mat4& parentWorldMatrix) + const std::shared_ptr>& transformComponentArray, + const std::vector& children, const glm::mat4& parentWorldMatrix) { for (const auto& childEntity : children) { - if (!transformComponentArray->hasComponent(childEntity)) - continue; + if (!transformComponentArray->hasComponent(childEntity)) continue; auto& transform = transformComponentArray->get(childEntity); @@ -73,10 +68,9 @@ namespace nexo::system { } } - glm::mat4 TransformHierarchySystem::calculateLocalMatrix(const components::TransformComponent& transform) const + glm::mat4 TransformHierarchySystem::calculateLocalMatrix(const components::TransformComponent& transform) { - return glm::translate(glm::mat4(1.0f), transform.pos) * - glm::toMat4(transform.quat) * + return glm::translate(glm::mat4(1.0f), transform.pos) * glm::toMat4(transform.quat) * glm::scale(glm::mat4(1.0f), transform.size); } -} +} // namespace nexo::system diff --git a/engine/src/systems/TransformHierarchySystem.hpp b/engine/src/systems/TransformHierarchySystem.hpp index 1d5b9621c..491ee0027 100644 --- a/engine/src/systems/TransformHierarchySystem.hpp +++ b/engine/src/systems/TransformHierarchySystem.hpp @@ -13,12 +13,11 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "components/Model.hpp" #include "components/Parent.hpp" -#include "ecs/GroupSystem.hpp" -#include "components/Transform.hpp" -#include "components/SceneComponents.hpp" #include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" +#include "components/Transform.hpp" +#include "ecs/GroupSystem.hpp" namespace nexo::system { /** @@ -28,26 +27,32 @@ namespace nexo::system { * This system updates the transforms of entities with parent relationships, * ensuring child entities inherit the transformations of their parents. */ - class TransformHierarchySystem final : public ecs::GroupSystem< - ecs::Owned< - ecs::Read>, - ecs::NonOwned< - ecs::Write, - ecs::Read>, - ecs::WriteSingleton> { - public: - void update(); - private: - /** - * @brief Recursively updates transforms of child entities based on parent transform - * - * @param children Vector of child indices to process - * @param parentTransform The parent's transform component - */ - void updateChildTransforms( - const std::shared_ptr> &transformComponentArray, - const std::vector& children, - const glm::mat4& parentWorldMatrix); - [[nodiscard]] glm::mat4 calculateLocalMatrix(const components::TransformComponent& transform) const; - }; -} + class TransformHierarchySystem final + : public ecs::GroupSystem< + ecs::Owned>, + ecs::NonOwned, ecs::Read>, + ecs::WriteSingleton> { + public: + void update(); + + private: + /** + * @brief Recursively updates transforms of child entities based on parent transform + * + * @param transformComponentArray Shared pointer to the TransformComponent array + * @param children Vector of child indices to process + * @param parentWorldMatrix The world matrix of the parent entity + */ + static void updateChildTransforms( + const std::shared_ptr>& transformComponentArray, + const std::vector& children, const glm::mat4& parentWorldMatrix); + + /** + * @brief Calculates the local transformation matrix for a given TransformComponent + * + * @param transform The TransformComponent to calculate the local matrix for + * @return glm::mat4 The calculated local transformation matrix + */ + [[nodiscard]] static glm::mat4 calculateLocalMatrix(const components::TransformComponent& transform); + }; +} // namespace nexo::system diff --git a/engine/src/systems/TransformMatrixSystem.cpp b/engine/src/systems/TransformMatrixSystem.cpp index 2b0e62969..53585c2c7 100644 --- a/engine/src/systems/TransformMatrixSystem.cpp +++ b/engine/src/systems/TransformMatrixSystem.cpp @@ -22,16 +22,14 @@ namespace nexo::system { void TransformMatrixSystem::update() { const auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); for (const ecs::Entity entity : entities) { auto &sceneTag = getComponent(entity); - if (sceneTag.id != sceneRendered) - continue; - auto &transform = getComponent(entity); + if (sceneTag.id != sceneRendered) continue; + auto &transform = getComponent(entity); transform.localMatrix = createTransformMatrix(transform); transform.worldMatrix = transform.localMatrix; } @@ -39,8 +37,7 @@ namespace nexo::system { glm::mat4 TransformMatrixSystem::createTransformMatrix(const components::TransformComponent &transform) { - return glm::translate(glm::mat4(1.0f), transform.pos) * - glm::toMat4(transform.quat) * + return glm::translate(glm::mat4(1.0f), transform.pos) * glm::toMat4(transform.quat) * glm::scale(glm::mat4(1.0f), transform.size); } -} +} // namespace nexo::system diff --git a/engine/src/systems/TransformMatrixSystem.hpp b/engine/src/systems/TransformMatrixSystem.hpp index 41f6443b0..d246f2852 100644 --- a/engine/src/systems/TransformMatrixSystem.hpp +++ b/engine/src/systems/TransformMatrixSystem.hpp @@ -13,19 +13,33 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "ecs/QuerySystem.hpp" -#include "components/Transform.hpp" -#include "components/SceneComponents.hpp" #include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" +#include "components/Transform.hpp" +#include "ecs/QuerySystem.hpp" namespace nexo::system { - class TransformMatrixSystem final : public ecs::QuerySystem< - ecs::Write, - ecs::Read, - ecs::WriteSingleton> { - public: - void update(); - private: - static glm::mat4 createTransformMatrix(const components::TransformComponent &transform); - }; -} + class TransformMatrixSystem final + : public ecs::QuerySystem, ecs::Read, + ecs::WriteSingleton> { + public: + /** + * @brief Updates the world matrices of entities based on their TransformComponents. + * This method iterates over all entities in the system, checking if their SceneTag matches + * the currently active scene from the RenderContext singleton. For matching entities, + * it computes the local transformation matrix from the TransformComponent's position, + * rotation (quaternion), and scale, and sets both the localMatrix and worldMatrix + * of the TransformComponent accordingly. + */ + void update(); + + private: + /** @brief Creates a transformation matrix from a TransformComponent. + * This static helper function constructs a transformation matrix by combining translation, + * rotation (from quaternion), and scaling transformations in that order. + * @param transform The TransformComponent containing position, rotation (as quaternion), and scale. + * @return glm::mat4 The resulting transformation matrix. + */ + static glm::mat4 createTransformMatrix(const components::TransformComponent &transform); + }; +} // namespace nexo::system diff --git a/engine/src/systems/lights/AmbientLightSystem.cpp b/engine/src/systems/lights/AmbientLightSystem.cpp index 921190d7a..1e29234e7 100644 --- a/engine/src/systems/lights/AmbientLightSystem.cpp +++ b/engine/src/systems/lights/AmbientLightSystem.cpp @@ -14,38 +14,39 @@ #include "AmbientLightSystem.hpp" -#include "Logger.hpp" #include "Application.hpp" +#include "Logger.hpp" #include "components/Light.hpp" namespace nexo::system { - void AmbientLightSystem::update() - { - auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + void AmbientLightSystem::update() + { + auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; - const auto sceneRendered = static_cast(renderContext.sceneRendered); + const auto sceneRendered = static_cast(renderContext.sceneRendered); - const auto scenePartition = m_group->getPartitionView( - [](const components::SceneTag& tag) { return tag.id; } - ); + const auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag &tag) { return tag.id; }); - const auto *partition = scenePartition.getPartition(sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); - auto &app = Application::getInstance(); + auto &app = Application::getInstance(); const std::string &sceneName = app.getSceneManager().getScene(sceneRendered).getName(); - if (!partition) { + if (!partition) { LOG_ONCE(NEXO_WARN, "No ambient light found in scene {}, skipping", sceneName); return; } Logger::resetOnce(NEXO_LOG_ONCE_KEY("No ambient light found in scene {}, skipping", sceneName)); if (partition->count != 1) - LOG_ONCE(NEXO_WARN, "For scene {}, found {} ambient lights, only one is supported, picking the first one", sceneName, partition->count); + LOG_ONCE(NEXO_WARN, "For scene {}, found {} ambient lights, only one is supported, picking the first one", + sceneName, partition->count); else - Logger::resetOnce(NEXO_LOG_ONCE_KEY("For scene {}, found {} ambient lights, only one is supported, picking the first one", sceneName, partition->count)); + Logger::resetOnce( + NEXO_LOG_ONCE_KEY("For scene {}, found {} ambient lights, only one is supported, picking the first one", + sceneName, partition->count)); renderContext.sceneLights.ambientLight = get()[0].color; - } -} + } +} // namespace nexo::system diff --git a/engine/src/systems/lights/AmbientLightSystem.hpp b/engine/src/systems/lights/AmbientLightSystem.hpp index 414fbcad5..2a48a5728 100644 --- a/engine/src/systems/lights/AmbientLightSystem.hpp +++ b/engine/src/systems/lights/AmbientLightSystem.hpp @@ -19,27 +19,35 @@ namespace nexo::system { - /** - * @brief System responsible for updating ambient light data in the scene. - * - * This system identifies the first ambient light entity in the active scene and updates - * the RenderContext's global ambient light value with its ambient light component. - * - * @note Component Access Rights: - * - READ access to components::AmbientLightComponent (owned) - * - READ access to components::SceneTag (non-owned) - * - WRITE access to components::RenderContext (singleton) - * - * @note The system uses scene partitioning to only process ambient light entities - * belonging to the currently active scene (identified by RenderContext.sceneRendered). - */ - class AmbientLightSystem final : public ecs::GroupSystem< - ecs::Owned< - ecs::Read>, - ecs::NonOwned< - ecs::Read>, - ecs::WriteSingleton> { - public: - void update(); - }; -} + /** + * @brief System responsible for updating ambient light data in the scene. + * + * This system identifies the first ambient light entity in the active scene and updates + * the RenderContext's global ambient light value with its ambient light component. + * + * @note Component Access Rights: + * - READ access to components::AmbientLightComponent (owned) + * - READ access to components::SceneTag (non-owned) + * - WRITE access to components::RenderContext (singleton) + * + * @note The system uses scene partitioning to only process ambient light entities + * belonging to the currently active scene (identified by RenderContext.sceneRendered). + */ + class AmbientLightSystem final : public ecs::GroupSystem>, + ecs::NonOwned>, + ecs::WriteSingleton> { + public: + /** + * @brief Updates the RenderContext with the ambient light from the active scene. + * + * This method retrieves the currently active scene from the RenderContext singleton. + * It then partitions entities by their SceneTag to find those belonging to the active scene. + * If an ambient light entity is found, its color is used to update the RenderContext's + * ambient light value. If multiple ambient lights are found, a warning is logged and + * only the first one is used. + * + * If no ambient light is found for the active scene, a warning is logged. + */ + void update(); + }; +} // namespace nexo::system diff --git a/engine/src/systems/lights/DirectionalLightsSystem.cpp b/engine/src/systems/lights/DirectionalLightsSystem.cpp index 0a209458e..296fbbe12 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.cpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.cpp @@ -13,41 +13,43 @@ /////////////////////////////////////////////////////////////////////////////// #include "DirectionalLightsSystem.hpp" +#include "Application.hpp" #include "components/Light.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" -#include "Application.hpp" namespace nexo::system { - void DirectionalLightsSystem::update() - { - auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + void DirectionalLightsSystem::update() + { + auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; - const auto sceneRendered = static_cast(renderContext.sceneRendered); + const auto sceneRendered = static_cast(renderContext.sceneRendered); - const auto scenePartition = m_group->getPartitionView( - [](const components::SceneTag& tag) { return tag.id; } - ); + const auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag &tag) { return tag.id; }); - const auto *partition = scenePartition.getPartition(sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); - auto &app = Application::getInstance(); + auto &app = Application::getInstance(); const std::string &sceneName = app.getSceneManager().getScene(sceneRendered).getName(); - if (!partition) { + if (!partition) { LOG_ONCE(NEXO_WARN, "No directional light found in scene {}, skipping", sceneName); return; } nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No directional light found in scene {}, skipping", sceneName)); if (partition->count != 1) - LOG_ONCE(NEXO_WARN, "For scene {}, found {} directional lights, only one is supported, picking the first one", sceneName, partition->count); + LOG_ONCE(NEXO_WARN, + "For scene {}, found {} directional lights, only one is supported, picking the first one", + sceneName, partition->count); else - nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("For scene {}, found {} directional lights, only one is supported, picking the first one", sceneName, partition->count)); + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY( + "For scene {}, found {} directional lights, only one is supported, picking the first one", sceneName, + partition->count)); - const auto &lights = get(); // now 'lights' names the container + const auto &lights = get(); // now 'lights' names the container const auto &dirLight = lights[0]; - renderContext.sceneLights.dirLight = dirLight; - } -} + renderContext.sceneLights.dirLight = dirLight; + } +} // namespace nexo::system diff --git a/engine/src/systems/lights/DirectionalLightsSystem.hpp b/engine/src/systems/lights/DirectionalLightsSystem.hpp index 287bc5cd3..a7d27149b 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.hpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.hpp @@ -13,36 +13,45 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "ecs/GroupSystem.hpp" #include "components/Light.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" +#include "ecs/GroupSystem.hpp" namespace nexo::system { - /** - * @brief System responsible for updating directional lights in the scene. - * - * This system iterates over all directional light entities in the active scene and updates - * the RenderContext's sceneLights collection with their directional light components. - * - * @note Component Access Rights: - * - READ access to components::DirectionalLightComponent (owned) - * - READ access to components::SceneTag (non-owned) - * - WRITE access to components::RenderContext (singleton) - * - * @note The system uses scene partitioning to only process directional light entities - * belonging to the currently active scene (identified by RenderContext.sceneRendered). - * - * @throws TooManyDirectionalLightsException if the count of directional light entities exceeds MAX_DIRECTIONAL_LIGHTS. - */ - class DirectionalLightsSystem final : public ecs::GroupSystem< - ecs::Owned< - ecs::Read>, - ecs::NonOwned< - ecs::Read>, - ecs::WriteSingleton> { - public: - void update(); - }; -} + /** + * @brief System responsible for updating directional lights in the scene. + * + * This system iterates over all directional light entities in the active scene and updates + * the RenderContext's sceneLights collection with their directional light components. + * + * @note Component Access Rights: + * - READ access to components::DirectionalLightComponent (owned) + * - READ access to components::SceneTag (non-owned) + * - WRITE access to components::RenderContext (singleton) + * + * @note The system uses scene partitioning to only process directional light entities + * belonging to the currently active scene (identified by RenderContext.sceneRendered). + * + * @throws TooManyDirectionalLightsException if the count of directional light entities exceeds + * MAX_DIRECTIONAL_LIGHTS. + */ + class DirectionalLightsSystem final + : public ecs::GroupSystem>, + ecs::NonOwned>, + ecs::WriteSingleton> { + public: + /** + * @brief Updates the RenderContext with directional lights from the active scene. + * + * This method retrieves the currently active scene from the RenderContext singleton. + * It then partitions entities by their SceneTag to find those belonging to the active scene. + * If directional light entities are found, their components are collected and used to update + * the RenderContext's sceneLights collection. If the number of directional lights exceeds + * MAX_DIRECTIONAL_LIGHTS, a TooManyDirectionalLightsException is thrown. + * If no directional lights are found for the active scene, a warning is logged. + */ + void update(); + }; +} // namespace nexo::system diff --git a/engine/src/systems/lights/PointLightsSystem.cpp b/engine/src/systems/lights/PointLightsSystem.cpp index ee7a0415c..9a919b607 100644 --- a/engine/src/systems/lights/PointLightsSystem.cpp +++ b/engine/src/systems/lights/PointLightsSystem.cpp @@ -13,45 +13,42 @@ /////////////////////////////////////////////////////////////////////////////// #include "PointLightsSystem.hpp" +#include "Application.hpp" #include "Exception.hpp" #include "components/Light.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" #include "core/exceptions/Exceptions.hpp" -#include "Application.hpp" namespace nexo::system { - void PointLightsSystem::update() - { - auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + void PointLightsSystem::update() + { + auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; - const auto sceneRendered = static_cast(renderContext.sceneRendered); + const auto sceneRendered = static_cast(renderContext.sceneRendered); - const auto scenePartition = m_group->getPartitionView( - [](const components::SceneTag& tag) { return tag.id; } - ); + const auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag &tag) { return tag.id; }); - const auto *partition = scenePartition.getPartition(sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); - auto &app = Application::getInstance(); + auto &app = Application::getInstance(); const std::string &sceneName = app.getSceneManager().getScene(sceneRendered).getName(); - if (!partition) { + if (!partition) { LOG_ONCE(NEXO_WARN, "No point light found in scene {}, skipping", sceneName); return; } nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No point light found in scene {}, skipping", sceneName)); - if (partition->count > MAX_POINT_LIGHTS) - THROW_EXCEPTION(core::TooManyPointLightsException, sceneRendered, partition->count); + if (partition->count > MAX_POINT_LIGHTS) + THROW_EXCEPTION(core::TooManyPointLightsException, sceneRendered, partition->count); - const std::span entitySpan = m_group->entities(); + const std::span entitySpan = m_group->entities(); - for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) - { - renderContext.sceneLights.pointLights[renderContext.sceneLights.pointLightCount] = entitySpan[i]; - renderContext.sceneLights.pointLightCount++; - } - } -} + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { + renderContext.sceneLights.pointLights[renderContext.sceneLights.pointLightCount] = entitySpan[i]; + renderContext.sceneLights.pointLightCount++; + } + } +} // namespace nexo::system diff --git a/engine/src/systems/lights/PointLightsSystem.hpp b/engine/src/systems/lights/PointLightsSystem.hpp index a3a783873..c2c064c36 100644 --- a/engine/src/systems/lights/PointLightsSystem.hpp +++ b/engine/src/systems/lights/PointLightsSystem.hpp @@ -20,29 +20,36 @@ namespace nexo::system { - /** - * @brief System responsible for updating point lights in the scene. - * - * This system iterates over all point light entities in the active scene and updates - * the RenderContext's sceneLights collection with their point light components. - * - * @note Component Access Rights: - * - READ access to components::PointLightComponent (owned) - * - READ access to components::SceneTag (non-owned) - * - WRITE access to components::RenderContext (singleton) - * - * @note The system uses scene partitioning to only process point light entities - * belonging to the currently active scene (identified by RenderContext.sceneRendered). - * - * @throws TooManyPointLightsException if the count of point light entities exceeds MAX_POINT_LIGHTS. - */ - class PointLightsSystem final : public ecs::GroupSystem< - ecs::Owned< - ecs::Read>, - ecs::NonOwned< - ecs::Read>, - ecs::WriteSingleton> { - public: - void update(); - }; -} + /** + * @brief System responsible for updating point lights in the scene. + * + * This system iterates over all point light entities in the active scene and updates + * the RenderContext's sceneLights collection with their point light components. + * + * @note Component Access Rights: + * - READ access to components::PointLightComponent (owned) + * - READ access to components::SceneTag (non-owned) + * - WRITE access to components::RenderContext (singleton) + * + * @note The system uses scene partitioning to only process point light entities + * belonging to the currently active scene (identified by RenderContext.sceneRendered). + * + * @throws TooManyPointLightsException if the count of point light entities exceeds MAX_POINT_LIGHTS. + */ + class PointLightsSystem final : public ecs::GroupSystem>, + ecs::NonOwned>, + ecs::WriteSingleton> { + public: + /** + * @brief Updates the RenderContext with point lights from the active scene. + * + * This method retrieves the currently active scene from the RenderContext singleton. + * It then partitions entities by their SceneTag to find those belonging to the active scene. + * If point light entities are found, their components are collected and used to update + * the RenderContext's sceneLights collection. If the number of point lights exceeds + * MAX_POINT_LIGHTS, a TooManyPointLightsException is thrown. + * If no point lights are found for the active scene, a warning is logged. + */ + void update(); + }; +} // namespace nexo::system diff --git a/engine/src/systems/lights/SpotLightsSystem.cpp b/engine/src/systems/lights/SpotLightsSystem.cpp index aa5319825..3bb2e30db 100644 --- a/engine/src/systems/lights/SpotLightsSystem.cpp +++ b/engine/src/systems/lights/SpotLightsSystem.cpp @@ -13,44 +13,41 @@ /////////////////////////////////////////////////////////////////////////////// #include "SpotLightsSystem.hpp" +#include "Application.hpp" #include "components/Light.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" #include "core/exceptions/Exceptions.hpp" -#include "Application.hpp" namespace nexo::system { - void SpotLightsSystem::update() - { - auto &renderContext = getSingleton(); - if (renderContext.sceneRendered == -1) - return; + void SpotLightsSystem::update() + { + auto &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) return; - const auto sceneRendered = static_cast(renderContext.sceneRendered); + const auto sceneRendered = static_cast(renderContext.sceneRendered); - const auto scenePartition = m_group->getPartitionView( - [](const components::SceneTag& tag) { return tag.id; } - ); + const auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag &tag) { return tag.id; }); - const auto *partition = scenePartition.getPartition(sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); - auto &app = Application::getInstance(); + auto &app = Application::getInstance(); const std::string &sceneName = app.getSceneManager().getScene(sceneRendered).getName(); - if (!partition) { + if (!partition) { LOG_ONCE(NEXO_WARN, "No spot light found in scene {}, skipping", sceneName); return; } nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No spot light found in scene {}, skipping", sceneName)); - if (partition->count > MAX_SPOT_LIGHTS) - THROW_EXCEPTION(core::TooManySpotLightsException, sceneRendered, partition->count); + if (partition->count > MAX_SPOT_LIGHTS) + THROW_EXCEPTION(core::TooManySpotLightsException, sceneRendered, partition->count); - const std::span entitySpan = m_group->entities(); + const std::span entitySpan = m_group->entities(); - for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) - { - renderContext.sceneLights.spotLights[renderContext.sceneLights.spotLightCount] = entitySpan[i]; - renderContext.sceneLights.spotLightCount++; - } - } -} + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { + renderContext.sceneLights.spotLights[renderContext.sceneLights.spotLightCount] = entitySpan[i]; + renderContext.sceneLights.spotLightCount++; + } + } +} // namespace nexo::system diff --git a/engine/src/systems/lights/SpotLightsSystem.hpp b/engine/src/systems/lights/SpotLightsSystem.hpp index 7b3a17d79..c67f99633 100644 --- a/engine/src/systems/lights/SpotLightsSystem.hpp +++ b/engine/src/systems/lights/SpotLightsSystem.hpp @@ -20,29 +20,35 @@ namespace nexo::system { - /** - * @brief System responsible for updating spot lights in the scene. - * - * This system iterates over all spot light entities in the active scene and updates - * the RenderContext's sceneLights collection with their spot light components. - * - * @note Component Access Rights: - * - READ access to components::SpotLightComponent (owned) - * - READ access to components::SceneTag (non-owned) - * - WRITE access to components::RenderContext (singleton) - * - * @note The system uses scene partitioning to only process spot light entities - * belonging to the currently active scene (identified by RenderContext.sceneRendered). - * - * @throws TooManySpotLightsException if the count of spot light entities exceeds MAX_SPOT_LIGHTS. - */ - class SpotLightsSystem final : public ecs::GroupSystem< - ecs::Owned< - ecs::Read>, - ecs::NonOwned< - ecs::Read>, - ecs::WriteSingleton> { - public: - void update(); - }; -} + /** + * @brief System responsible for updating spot lights in the scene. + * + * This system iterates over all spot light entities in the active scene and updates + * the RenderContext's sceneLights collection with their spot light components. + * + * @note Component Access Rights: + * - READ access to components::SpotLightComponent (owned) + * - READ access to components::SceneTag (non-owned) + * - WRITE access to components::RenderContext (singleton) + * + * @note The system uses scene partitioning to only process spot light entities + * belonging to the currently active scene (identified by RenderContext.sceneRendered). + * + * @throws TooManySpotLightsException if the count of spot light entities exceeds MAX_SPOT_LIGHTS. + */ + class SpotLightsSystem final : public ecs::GroupSystem>, + ecs::NonOwned>, + ecs::WriteSingleton> { + public: + /** + * @brief Updates the RenderContext with spotlight from the active scene. + * This method retrieves the currently active scene from the RenderContext singleton. + * It then partitions entities by their SceneTag to find those belonging to the active scene. + * If spotlight entities are found, their components are collected and used to update + * the RenderContext's sceneLights collection. If the number of spotlight exceeds + * MAX_SPOT_LIGHTS, a TooManySpotLightsException is thrown. + * If no spotlight are found for the active scene, a warning is logged. + */ + void update(); + }; +} // namespace nexo::system diff --git a/examples/ecs/CMakeLists.txt b/examples/ecs/CMakeLists.txt index 49648f6a6..8a94cab49 100644 --- a/examples/ecs/CMakeLists.txt +++ b/examples/ecs/CMakeLists.txt @@ -38,10 +38,10 @@ include_directories("../../common") # include_directories("${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/x64-osx/include") #endif() -if(APPLE) +if (APPLE) include_directories(/usr/local/include) link_directories(/usr/local/lib) -endif() +endif () include_directories(include) diff --git a/examples/ecs/exampleBasic.cpp b/examples/ecs/exampleBasic.cpp index d55ab7555..b8b3fc3c5 100644 --- a/examples/ecs/exampleBasic.cpp +++ b/examples/ecs/exampleBasic.cpp @@ -1,12 +1,12 @@ -#include #include +#include +#include +#include #include #include -#include -#include #include "ecs/Coordinator.hpp" -#include "ecs/QuerySystem.hpp" #include "ecs/GroupSystem.hpp" +#include "ecs/QuerySystem.hpp" // Component Definitions struct Position { @@ -23,19 +23,20 @@ struct Velocity { // EVERY singleton components should have their copy constructor deleted to enforce singleton semantics // This is statically checked when registering it struct GameConfig { - int maxEntities = 1000; + int maxEntities = 1000; float worldSize = 100.0f; // Default constructor and other constructors as needed GameConfig() = default; - GameConfig(int entities, float size) : maxEntities(entities), worldSize(size) {} + GameConfig(const int entities, const float size) : maxEntities(entities), worldSize(size) + {} // Delete copy constructor to enforce singleton semantics - GameConfig(const GameConfig&) = delete; + GameConfig(const GameConfig&) = delete; GameConfig& operator=(const GameConfig&) = delete; // Move operations can be allowed if needed - GameConfig(GameConfig&&) = default; + GameConfig(GameConfig&&) = default; GameConfig& operator=(GameConfig&&) = default; }; @@ -45,14 +46,15 @@ struct GameState { // Default constructor and other constructors GameState() = default; - GameState(bool paused, float time) : isPaused(paused), gameTime(time) {} + GameState(bool paused, float time) : isPaused(paused), gameTime(time) + {} // Delete copy constructor to enforce singleton semantics - GameState(const GameState&) = delete; + GameState(const GameState&) = delete; GameState& operator=(const GameState&) = delete; // Move operations can be allowed if needed - GameState(GameState&&) = default; + GameState(GameState&&) = default; GameState& operator=(GameState&&) = default; }; @@ -68,56 +70,52 @@ void log(const std::string& message) // The query system induces a small performance overhead because of the indirection required to access the components // (because the entities we are iterating on does not necessarily have contiguous components in memory) // This should be used when you want to create a group system that does not own any components -class QueryBenchmarkSystem : public nexo::ecs::QuerySystem< - nexo::ecs::Write, - nexo::ecs::Read, - nexo::ecs::ReadSingleton, - nexo::ecs::WriteSingleton -> { - public: - void runBenchmark() - { - log("Running query system benchmarks with " + std::to_string(entities.size()) + " entities"); - constexpr int NUM_ITERATIONS = 100; - auto queryTime = benchmarkQuery(NUM_ITERATIONS); - log("Query System: " + std::to_string(queryTime) + " milliseconds per iteration"); - } - - private: - double benchmarkQuery(int numIterations) - { - auto start = std::chrono::high_resolution_clock::now(); - - auto &gameConfig = getSingleton(); - log ("Max entities " + std::to_string(gameConfig.maxEntities)); - log("World size " + std::to_string(gameConfig.worldSize)); - // This does not compile because GameConfig is read-only - //gameConfig.worldSize += 1; - - auto &gameState = getSingleton(); - log("Game state: " + std::to_string(gameState.isPaused)); - log("Game time: " + std::to_string(gameState.gameTime)); - - for (int i = 0; i < numIterations; i++) { - // We can safely update the game state here - gameState.gameTime += 10; - for (nexo::ecs::Entity entity : entities) { - auto &position = getComponent(entity); - auto &velocity = getComponent(entity); - - - // This triggers a compiler error since Velocity is marked as read-only - //velocity.x += 1; - - position.x += velocity.x; - position.y += velocity.y; - } - } - - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration duration = end - start; - return duration.count() / numIterations; - } +class QueryBenchmarkSystem final + : public nexo::ecs::QuerySystem, nexo::ecs::Read, + nexo::ecs::ReadSingleton, nexo::ecs::WriteSingleton > { + public: + void runBenchmark() + { + log("Running query system benchmarks with " + std::to_string(entities.size()) + " entities"); + constexpr int NUM_ITERATIONS = 100; + auto queryTime = benchmarkQuery(NUM_ITERATIONS); + log("Query System: " + std::to_string(queryTime) + " milliseconds per iteration"); + } + + private: + double benchmarkQuery(int numIterations) + { + const auto start = std::chrono::high_resolution_clock::now(); + + auto& gameConfig = getSingleton(); + log("Max entities " + std::to_string(gameConfig.maxEntities)); + log("World size " + std::to_string(gameConfig.worldSize)); + // This does not compile because GameConfig is read-only + // gameConfig.worldSize += 1; + + auto& gameState = getSingleton(); + log("Game state: " + std::to_string(gameState.isPaused)); + log("Game time: " + std::to_string(gameState.gameTime)); + + for (int i = 0; i < numIterations; i++) { + // We can safely update the game state here + gameState.gameTime += 10; + for (const nexo::ecs::Entity entity : entities) { + auto& position = getComponent(entity); + auto& velocity = getComponent(entity); + + // This triggers a compiler error since Velocity is marked as read-only + // velocity.x += 1; + + position.x += velocity.x; + position.y += velocity.y; + } + } + + const auto end = std::chrono::high_resolution_clock::now(); + const std::chrono::duration duration = end - start; + return duration.count() / numIterations; + } }; // This a basic full-owning group system @@ -125,142 +123,137 @@ class QueryBenchmarkSystem : public nexo::ecs::QuerySystem< // Then we can safely iterate over the group and update the position of each entity // Those system induces a huge overhead when you are adding/removing components or destroying entities often // So make sure to use them wisely and avoid unnecessary operations. -// Also here we get the singleton components game config as write and game state as read +// Also, here we get the singleton components game config as write and game state as read // But in most case, those are blazingly fast // If unsure, you can try both a query system and a group system to test out what is best for your use case ! -class GroupBenchmarkSystem : public nexo::ecs::GroupSystem< - nexo::ecs::Owned< - nexo::ecs::Write, - nexo::ecs::Read - >, - nexo::ecs::NonOwned<>, - nexo::ecs::WriteSingleton, - nexo::ecs::ReadSingleton -> { - public: - void runBenchmark() - { - log("Running benchmarks with " + std::to_string(m_group->size()) + " entities"); - - constexpr int NUM_ITERATIONS = 100; - - // Benchmark each approach - auto eachTime = benchmarkEach(NUM_ITERATIONS); - log("Each method: " + std::to_string(eachTime) + " milliseconds per iteration"); - - // Benchmark spans approach - auto spansTime = benchmarkSpans(NUM_ITERATIONS); - log("Spans method: " + std::to_string(spansTime) + " milliseconds per iteration"); - - // Benchmark iterator approach - auto iteratorTime = benchmarkIterator(NUM_ITERATIONS); - log("Iterator method: " + std::to_string(iteratorTime) + " milliseconds per iteration"); - } - - private: - double benchmarkIterator(int numIterations) - { - // Method 1: Using the iterator, slowest solution of the three but more verbose - // Also not fully recommend since it does not enforce the constness for read-only components - auto start = std::chrono::high_resolution_clock::now(); - - for (int i = 0; i < numIterations; i++) { - for (const auto &[entity, position, velocity] : *m_group) { - position.x += velocity.x; - position.y += velocity.y; - - // This is highly dangerous since velocity is read-only and this is not enforced at compile time, - // we advise to use it at user's discretion - // velocity.x += 1; - } - } - - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration duration = end - start; - return duration.count() / numIterations; - } - - double benchmarkEach(int numIterations) - { - // Method 2: Using the each method, faster than the iterator, but does not necessarly enforce constness if not mentionned - auto start = std::chrono::high_resolution_clock::now(); - - for (int i = 0; i < numIterations; i++) { - m_group->each([]([[maybe_unused]] nexo::ecs::Entity entity, Position& position, const Velocity& velocity) { - position.x += velocity.x; - position.y += velocity.y; - - // This triggers a compilation error since we are using a const reference to velocity in the lambda function - // velocity.x += 1; - }); - - // But here, this would compile even though the user forgot to mention the const in the lambda function, - // which can be problematic in multihreaded systems - // m_group->each([](nexo::ecs::Entity entity, Position& position, Velocity& velocity) { - // position.x += velocity.x; - // position.y += velocity.y; - - // velocity.x += 1; - // }); - } - - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration duration = end - start; - return duration.count() / numIterations; - } - - double benchmarkSpans(int numIterations) - { - // Method 3: Using the spans directly, this is the fasted method of the three - // Also, it automatically enforces constness on read-only components - // This solution should be your preferred one - auto start = std::chrono::high_resolution_clock::now(); - - auto &gameConfig = getSingleton(); - log ("Max entities " + std::to_string(gameConfig.maxEntities)); - log("World size " + std::to_string(gameConfig.worldSize)); - - auto &gameState = getSingleton(); - log("Game state: " + std::to_string(gameState.isPaused)); - log("Game time: " + std::to_string(gameState.gameTime)); - // This does not compile because GameState is read-only - // gameState.isPaused = false; - - - - for (int i = 0; i < numIterations; i++) { - auto positionSpan = get(); - // Constness is not enforced on the span itself since it is basically a non-owning view of the underlying data. - // But we consider it to be good practice to make more explicit that we are using read-only components - const auto velocitySpan = get(); - // We can safely update the game config - gameConfig.maxEntities += 1000; - - size_t size = positionSpan.size(); - for (size_t j = 0; j < size; ++j) { - auto& position = positionSpan[j]; - auto& velocity = velocitySpan[j]; - - // Of course for more clarity you should write: - // const auto& velocity = velocitySpan[j]; - // but here we are demonstrating that even if the user forgets to mark velocity as const, - // the compiler will raise an error for read-only components - - // This triggers a compilation error since velocity is read-only even though we did not explicitly mark it as const - // velocity.x += 1; - - position.x += velocity.x; - position.y += velocity.y; - } - } - - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration duration = end - start; - return duration.count() / numIterations; - } +class GroupBenchmarkSystem + : public nexo::ecs::GroupSystem, nexo::ecs::Read >, + nexo::ecs::NonOwned<>, nexo::ecs::WriteSingleton, + nexo::ecs::ReadSingleton > { + public: + void runBenchmark() + { + log("Running benchmarks with " + std::to_string(m_group->size()) + " entities"); + + constexpr int NUM_ITERATIONS = 100; + + // Benchmark each approach + const auto eachTime = benchmarkEach(NUM_ITERATIONS); + log("Each method: " + std::to_string(eachTime) + " milliseconds per iteration"); + + // Benchmark spans approach + const auto spansTime = benchmarkSpans(NUM_ITERATIONS); + log("Spans method: " + std::to_string(spansTime) + " milliseconds per iteration"); + + // Benchmark iterator approach + const auto iteratorTime = benchmarkIterator(NUM_ITERATIONS); + log("Iterator method: " + std::to_string(iteratorTime) + " milliseconds per iteration"); + } + + private: + [[nodiscard]] double benchmarkIterator(int numIterations) const + { + // Method 1: Using the iterator, slowest solution of the three but more verbose + // Also not fully recommend since it does not enforce the constness for read-only components + const auto start = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < numIterations; i++) { + for (const auto& [entity, position, velocity] : *m_group) { + position.x += velocity.x; + position.y += velocity.y; + + // This is highly dangerous since velocity is read-only and this is not enforced at compile time, + // we advise to use it at user's discretion + // velocity.x += 1; + } + } + + const auto end = std::chrono::high_resolution_clock::now(); + const std::chrono::duration duration = end - start; + return duration.count() / numIterations; + } + + [[nodiscard]] double benchmarkEach(int numIterations) const + { + // Method 2: Using the "each" method, faster than the iterator, but does not necessary enforce constness if not + // mentioned + const auto start = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < numIterations; i++) { + m_group->each([]([[maybe_unused]] nexo::ecs::Entity entity, Position& position, const Velocity& velocity) { + position.x += velocity.x; + position.y += velocity.y; + + // This triggers a compilation error since we are using a const reference to velocity in the lambda + // function velocity.x += 1; + }); + + // But here, this would compile even though the user forgot to mention the const in the lambda function, + // which can be problematic in multihreaded systems + // m_group->each([](nexo::ecs::Entity entity, Position& position, Velocity& velocity) { + // position.x += velocity.x; + // position.y += velocity.y; + + // velocity.x += 1; + // }); + } + + const auto end = std::chrono::high_resolution_clock::now(); + const std::chrono::duration duration = end - start; + return duration.count() / numIterations; + } + + double benchmarkSpans(int numIterations) + { + // Method 3: Using the spans directly, this is the fasted method of the three + // Also, it automatically enforces constness on read-only components + // This solution should be your preferred one + const auto start = std::chrono::high_resolution_clock::now(); + + auto& gameConfig = getSingleton(); + log("Max entities " + std::to_string(gameConfig.maxEntities)); + log("World size " + std::to_string(gameConfig.worldSize)); + + auto& gameState = getSingleton(); + log("Game state: " + std::to_string(gameState.isPaused)); + log("Game time: " + std::to_string(gameState.gameTime)); + // This does not compile because GameState is read-only + // gameState.isPaused = false; + + for (int i = 0; i < numIterations; i++) { + auto positionSpan = get(); + // Constness is not enforced on the span itself since it is basically a non-owning view of the underlying + // data. But we consider it to be good practice to make more explicit that we are using read-only components + const auto velocitySpan = get(); + // We can safely update the game config + gameConfig.maxEntities += 1000; + + const size_t size = positionSpan.size(); + for (size_t j = 0; j < size; ++j) { + auto& position = positionSpan[j]; + auto& velocity = velocitySpan[j]; + + // Of course for more clarity you should write: + // const auto& velocity = velocitySpan[j]; + // but here we are demonstrating that even if the user forgets to mark velocity as const, + // the compiler will raise an error for read-only components + + // This triggers a compilation error since velocity is read-only even though we did not explicitly mark + // it as const velocity.x += 1; + + position.x += velocity.x; + position.y += velocity.y; + } + } + + const auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration duration = end - start; + return duration.count() / numIterations; + } }; -int main() { +int main() +{ // Initialize ECS Coordinator nexo::ecs::Coordinator coordinator; coordinator.init(); @@ -294,8 +287,8 @@ int main() { for (int i = 0; i < 5000; ++i) { nexo::ecs::Entity entity = coordinator.createEntity(); - float velX = static_cast(velocityDist(gen)); - float velY = static_cast(velocityDist(gen)); + const auto velX = static_cast(velocityDist(gen)); + const auto velY = static_cast(velocityDist(gen)); coordinator.addComponent(entity, Position{0.0f, 0.0f}); coordinator.addComponent(entity, Velocity{velX, velY}); @@ -315,12 +308,11 @@ int main() { log("=== GroupSystem Benchmark Complete ==="); // We make sure to check if the singleton component has been updated - auto &gameState = coordinator.getSingletonComponent(); + const auto& gameState = coordinator.getSingletonComponent(); log("Game time: " + std::to_string(gameState.gameTime)); - auto &gameConfig = coordinator.getSingletonComponent(); + const auto& gameConfig = coordinator.getSingletonComponent(); log("Max entities: " + std::to_string(gameConfig.maxEntities)); - return 0; } diff --git a/resources/models/Avocado/Avocado.bin b/resources/models/Avocado/Avocado.bin index b98140c9d..69ddd8a03 100644 Binary files a/resources/models/Avocado/Avocado.bin and b/resources/models/Avocado/Avocado.bin differ diff --git a/resources/models/Avocado/Avocado.gltf b/resources/models/Avocado/Avocado.gltf index 55fcd9551..cd2762cd3 100644 --- a/resources/models/Avocado/Avocado.gltf +++ b/resources/models/Avocado/Avocado.gltf @@ -1,155 +1,169 @@ { - "accessors": [ - { - "bufferView": 0, - "componentType": 5126, - "count": 406, - "type": "VEC2" - }, - { - "bufferView": 1, - "componentType": 5126, - "count": 406, - "type": "VEC3" - }, - { - "bufferView": 2, - "componentType": 5126, - "count": 406, - "type": "VEC4" - }, - { - "bufferView": 3, - "componentType": 5126, - "count": 406, - "type": "VEC3", - "max": [ - 0.02128091, - 0.06284806, - 0.0138090011 - ], - "min": [ - -0.02128091, - -4.773855E-05, - -0.013809 - ] - }, - { - "bufferView": 4, - "componentType": 5123, - "count": 2046, - "type": "SCALAR" - } - ], - "asset": { - "generator": "glTF Tools for Unity", - "version": "2.0" - }, - "bufferViews": [ - { - "buffer": 0, - "byteLength": 3248 - }, - { - "buffer": 0, - "byteOffset": 3248, - "byteLength": 4872 - }, - { - "buffer": 0, - "byteOffset": 8120, - "byteLength": 6496 - }, - { - "buffer": 0, - "byteOffset": 14616, - "byteLength": 4872 - }, - { - "buffer": 0, - "byteOffset": 19488, - "byteLength": 4092 - } - ], - "buffers": [ - { - "uri": "Avocado.bin", - "byteLength": 23580 - } - ], - "images": [ - { - "uri": "Avocado_baseColor.png" - }, - { - "uri": "Avocado_roughnessMetallic.png" - }, - { - "uri": "Avocado_normal.png" - } - ], - "meshes": [ - { - "primitives": [ - { - "attributes": { - "TEXCOORD_0": 0, - "NORMAL": 1, - "TANGENT": 2, - "POSITION": 3 - }, - "indices": 4, - "material": 0 - } - ], - "name": "Avocado" - } - ], - "materials": [ - { - "pbrMetallicRoughness": { - "baseColorTexture": { - "index": 0 - }, - "metallicRoughnessTexture": { - "index": 1 - } - }, - "normalTexture": { - "index": 2 - }, - "name": "2256_Avocado_d" - } - ], - "nodes": [ - { - "mesh": 0, - "rotation": [ - 0.0, - 1.0, - 0.0, - 0.0 - ], - "name": "Avocado" - } - ], - "scene": 0, - "scenes": [ - { - "nodes": [ - 0 - ] - } - ], - "textures": [ - { - "source": 0 - }, - { - "source": 1 - }, - { - "source": 2 - } - ] -} \ No newline at end of file + "asset":{ + "generator":"Khronos glTF Blender I/O v4.5.48", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Avocado", + "rotation":[ + 0, + 1, + 0, + 0 + ], + "scale":[ + 70, + 70, + 70 + ] + } + ], + "materials":[ + { + "name":"2256_Avocado_d", + "normalTexture":{ + "index":0 + }, + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "index":1 + }, + "metallicRoughnessTexture":{ + "index":2 + } + } + } + ], + "meshes":[ + { + "name":"Avocado", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "textures":[ + { + "sampler":0, + "source":0 + }, + { + "sampler":0, + "source":1 + }, + { + "sampler":0, + "source":2 + } + ], + "images":[ + { + "mimeType":"image/png", + "name":"Avocado_normal", + "uri":"Avocado_normal.png" + }, + { + "mimeType":"image/png", + "name":"Avocado_baseColor", + "uri":"Avocado_baseColor.png" + }, + { + "mimeType":"image/png", + "name":"Avocado_roughnessMetallic", + "uri":"Avocado_roughnessMetallic.png" + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":406, + "max":[ + 0.021280910819768906, + 0.0628480613231659, + 0.013809001073241234 + ], + "min":[ + -0.021280910819768906, + -4.7738551074871793e-05, + -0.01380900014191866 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":406, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":406, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2046, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":4872, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":4872, + "byteOffset":4872, + "target":34962 + }, + { + "buffer":0, + "byteLength":3248, + "byteOffset":9744, + "target":34962 + }, + { + "buffer":0, + "byteLength":4092, + "byteOffset":12992, + "target":34963 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":17084, + "uri":"Avocado.bin" + } + ] +} diff --git a/resources/models/DiscoBall/discoball.fbx b/resources/models/DiscoBall/discoball.fbx deleted file mode 100644 index 2624efa31..000000000 Binary files a/resources/models/DiscoBall/discoball.fbx and /dev/null differ diff --git a/resources/models/sword/scene.bin b/resources/models/Sword/scene.bin similarity index 100% rename from resources/models/sword/scene.bin rename to resources/models/Sword/scene.bin diff --git a/resources/models/sword/scene.gltf b/resources/models/Sword/scene.gltf similarity index 100% rename from resources/models/sword/scene.gltf rename to resources/models/Sword/scene.gltf diff --git a/resources/models/sword/textures/DefaultMaterial_baseColor.jpeg b/resources/models/Sword/textures/DefaultMaterial_baseColor.jpeg similarity index 100% rename from resources/models/sword/textures/DefaultMaterial_baseColor.jpeg rename to resources/models/Sword/textures/DefaultMaterial_baseColor.jpeg diff --git a/resources/models/sword/textures/DefaultMaterial_metallicRoughness.png b/resources/models/Sword/textures/DefaultMaterial_metallicRoughness.png similarity index 100% rename from resources/models/sword/textures/DefaultMaterial_metallicRoughness.png rename to resources/models/Sword/textures/DefaultMaterial_metallicRoughness.png diff --git a/resources/models/bench.glb b/resources/models/bench.glb new file mode 100644 index 000000000..2059ec624 Binary files /dev/null and b/resources/models/bench.glb differ diff --git a/resources/models/bomb.glb b/resources/models/bomb.glb new file mode 100644 index 000000000..1df8e0e21 Binary files /dev/null and b/resources/models/bomb.glb differ diff --git a/resources/models/cup.glb b/resources/models/cup.glb new file mode 100644 index 000000000..4a14b5e03 Binary files /dev/null and b/resources/models/cup.glb differ diff --git a/resources/models/earth.glb b/resources/models/earth.glb new file mode 100644 index 000000000..098323d42 Binary files /dev/null and b/resources/models/earth.glb differ diff --git a/resources/models/frog.glb b/resources/models/frog.glb new file mode 100644 index 000000000..5ad12f413 Binary files /dev/null and b/resources/models/frog.glb differ diff --git a/resources/models/log.glb b/resources/models/log.glb new file mode 100644 index 000000000..a859df376 Binary files /dev/null and b/resources/models/log.glb differ diff --git a/resources/models/plane.glb b/resources/models/plane.glb new file mode 100644 index 000000000..46f22a713 Binary files /dev/null and b/resources/models/plane.glb differ diff --git a/resources/models/plant.glb b/resources/models/plant.glb new file mode 100644 index 000000000..af23a92e1 Binary files /dev/null and b/resources/models/plant.glb differ diff --git a/resources/models/rocks.glb b/resources/models/rocks.glb new file mode 100644 index 000000000..68f80d1dc Binary files /dev/null and b/resources/models/rocks.glb differ diff --git a/resources/models/room.glb b/resources/models/room.glb new file mode 100644 index 000000000..143446084 Binary files /dev/null and b/resources/models/room.glb differ diff --git a/resources/models/rubixCube.glb b/resources/models/rubixCube.glb new file mode 100644 index 000000000..e10b6af19 Binary files /dev/null and b/resources/models/rubixCube.glb differ diff --git a/resources/models/tree.glb b/resources/models/tree.glb new file mode 100644 index 000000000..4d0930574 Binary files /dev/null and b/resources/models/tree.glb differ diff --git a/resources/shaders/box_shader.glsl b/resources/shaders/box_shader.glsl new file mode 100644 index 000000000..2301a54bd --- /dev/null +++ b/resources/shaders/box_shader.glsl @@ -0,0 +1,28 @@ +#type vertex +#version 430 core + +layout(location=0) in vec3 aPos; + +uniform mat4 uViewProjection; +uniform mat4 uMatModel; +uniform vec3 uColor; + +out vec3 vColor; + +void main() +{ + vColor = uColor; + gl_Position = uViewProjection * uMatModel * vec4(aPos, 1.0); +} + +#type fragment +#version 430 core + +in vec3 vColor; + +out vec4 FragColor; + +void main() +{ + FragColor = vec4(vColor, 1.0); +} diff --git a/resources/textures/9mn/GunGs_baseColor.png b/resources/textures/9mn/GunGs_baseColor.png deleted file mode 100644 index d868693d6..000000000 Binary files a/resources/textures/9mn/GunGs_baseColor.png and /dev/null differ diff --git a/resources/textures/9mn/GunGs_emissive.png b/resources/textures/9mn/GunGs_emissive.png deleted file mode 100644 index 5e8fce536..000000000 Binary files a/resources/textures/9mn/GunGs_emissive.png and /dev/null differ diff --git a/resources/textures/9mn/GunGs_metallicRoughness.png b/resources/textures/9mn/GunGs_metallicRoughness.png deleted file mode 100644 index a7063bb11..000000000 Binary files a/resources/textures/9mn/GunGs_metallicRoughness.png and /dev/null differ diff --git a/resources/textures/9mn/GunGs_normal.png b/resources/textures/9mn/GunGs_normal.png deleted file mode 100644 index 7026f3849..000000000 Binary files a/resources/textures/9mn/GunGs_normal.png and /dev/null differ diff --git a/resources/textures/9mn/GunGs_specularf0.png b/resources/textures/9mn/GunGs_specularf0.png deleted file mode 100644 index 6d2566818..000000000 Binary files a/resources/textures/9mn/GunGs_specularf0.png and /dev/null differ diff --git a/resources/textures/9mn/Plane_baseColor.png b/resources/textures/9mn/Plane_baseColor.png deleted file mode 100644 index c88358f6e..000000000 Binary files a/resources/textures/9mn/Plane_baseColor.png and /dev/null differ diff --git a/resources/textures/dirt.jpg b/resources/textures/dirt.jpg new file mode 100644 index 000000000..d2db68923 Binary files /dev/null and b/resources/textures/dirt.jpg differ diff --git a/resources/textures/grass.jpg b/resources/textures/grass.jpg new file mode 100644 index 000000000..2418932b0 Binary files /dev/null and b/resources/textures/grass.jpg differ diff --git a/resources/textures/logo_nexo.png b/resources/textures/logoNexo.png similarity index 100% rename from resources/textures/logo_nexo.png rename to resources/textures/logoNexo.png diff --git a/resources/textures/minecraftGrass.png b/resources/textures/minecraftGrass.png new file mode 100644 index 000000000..77b235b5b Binary files /dev/null and b/resources/textures/minecraftGrass.png differ diff --git a/resources/textures/minecraftStone.png b/resources/textures/minecraftStone.png new file mode 100644 index 000000000..5e42f81da Binary files /dev/null and b/resources/textures/minecraftStone.png differ diff --git a/resources/textures/rock.jpg b/resources/textures/rock.jpg new file mode 100644 index 000000000..3583cddf9 Binary files /dev/null and b/resources/textures/rock.jpg differ diff --git a/resources/textures/wood.jpg b/resources/textures/wood.jpg new file mode 100644 index 000000000..01a53e3f4 Binary files /dev/null and b/resources/textures/wood.jpg differ diff --git a/resources/videos/test.mp4 b/resources/videos/test.mp4 new file mode 100644 index 000000000..da308a402 Binary files /dev/null and b/resources/videos/test.mp4 differ diff --git a/tests/renderer/CMakeLists.txt b/tests/renderer/CMakeLists.txt index de2bb675f..9438215f3 100644 --- a/tests/renderer/CMakeLists.txt +++ b/tests/renderer/CMakeLists.txt @@ -25,6 +25,7 @@ include_directories("./engine/src/renderer") set(COMMON_SOURCES common/Exception.cpp common/math/Matrix.cpp + common/math/Bounds.cpp common/Path.cpp ) @@ -56,6 +57,7 @@ set(RENDERER_SOURCES engine/src/renderer/primitives/Pyramid.cpp engine/src/renderer/primitives/Cylinder.cpp engine/src/renderer/primitives/Sphere.cpp + engine/src/renderer/primitives/Box.cpp ) add_executable(renderer_tests diff --git a/vcpkg.json b/vcpkg.json index d14d5b078..3caca5328 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -33,8 +33,9 @@ { "name": "tinyfiledialogs", "version>=": "3.19.1#0" }, { "name": "gtest", "version>=": "1.15.2#0" }, { "name": "assimp", "version>=": "5.4.3#0" }, + { "name": "opencv4", "version>=": "4.11.0"}, { "name": "nlohmann-json", "version>=": "3.11.3#1"}, { "name": "nethost", "version>=": "8.0.3#0"}, { "name": "utfcpp", "version>=": "4.0.6#0"} - ] + ] }