From f71db272b5a99daf0e41aaffcaa2b2b7e8f7f001 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Feb 2025 18:04:25 +0000 Subject: [PATCH 001/450] chore(deps): bump langroodi/doxygenize from 1.6.1 to 1.7.0 Bumps [langroodi/doxygenize](https://github.com/langroodi/doxygenize) from 1.6.1 to 1.7.0. - [Release notes](https://github.com/langroodi/doxygenize/releases) - [Commits](https://github.com/langroodi/doxygenize/compare/v1.6.1...v1.7.0) --- updated-dependencies: - dependency-name: langroodi/doxygenize dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/doxygen.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index c420a3352..b14ee3f30 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -12,6 +12,6 @@ jobs: steps: - uses: actions/checkout@v2 - run: git submodule add https://github.com/jothepro/doxygen-awesome-css.git && cd doxygen-awesome-css && git checkout v2.2.0 - - uses: langroodi/doxygenize@v1.6.1 + - uses: langroodi/doxygenize@v1.7.0 with: htmloutput: './html/' From 4a333215a9522c033b775485b713375d285af09b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 16:36:20 +0900 Subject: [PATCH 002/450] refactor(ecs-opti): component array now using a true sparse set setup --- engine/src/ecs/Components.cpp | 12 +- engine/src/ecs/Components.hpp | 660 +++++++++++++++++++--------------- 2 files changed, 381 insertions(+), 291 deletions(-) diff --git a/engine/src/ecs/Components.cpp b/engine/src/ecs/Components.cpp index 738a7334e..81ab7b97c 100644 --- a/engine/src/ecs/Components.cpp +++ b/engine/src/ecs/Components.cpp @@ -16,13 +16,13 @@ namespace nexo::ecs { - void ComponentManager::entityDestroyed(const Entity entity) const + void ComponentManager::entityDestroyed(const Entity entity) { - for (const auto &[fst, snd]: m_componentArrays) - { - auto const &component = snd; - component->entityDestroyed(entity); + for (auto& componentArray : m_componentArrays) { + if (componentArray) { + componentArray->entityDestroyed(entity); + } } } -} \ No newline at end of file +} diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index f182cff5c..58bfe54d8 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -1,4 +1,4 @@ -//// Components.hpp /////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -14,304 +14,394 @@ #pragma once -#include #include #include -#include -#include +#include +#include #include #include +#include +#include +#include -#include "Logger.hpp" #include "ECSExceptions.hpp" +#include "Exception.hpp" +#include "Logger.hpp" -namespace nexo::ecs -{ - using Entity = std::uint32_t; +namespace nexo::ecs { - // Maximum entity count, given that 13 bits are used for ID - constexpr Entity MAX_ENTITIES = 8191; - using ComponentType = std::uint8_t; + // Entity definitions + using Entity = std::uint32_t; + constexpr Entity MAX_ENTITIES = 80191; + constexpr Entity INVALID_ENTITY = std::numeric_limits::max(); - constexpr ComponentType MAX_COMPONENT_TYPE = 32; + // Component type definitions + using ComponentType = std::uint8_t; + constexpr ComponentType MAX_COMPONENT_TYPE = 32; - /** - * @class IComponentArray - * - * @brief Interface for a component array in the ECS framework. - * - * This class defines the interface for component arrays, which are used to store - * components of entities in the ECS system. It provides the foundation for managing - * the lifecycle of components associated with entities. - */ - class IComponentArray + // Global counter shared across all component types. + inline ComponentType globalComponentCounter = 0; + + template + ComponentType getUniqueComponentTypeID() { - public: - virtual ~IComponentArray() = default; + // This static variable is instantiated once per type T, + // but it will be assigned a unique value from the shared global counter. + static const ComponentType id = []() { + assert(globalComponentCounter < MAX_COMPONENT_TYPE && "Maximum number of component types exceeded"); + return globalComponentCounter++; + }(); + return id; + } + + template + ComponentType getComponentTypeID() + { + return getUniqueComponentTypeID>(); + } - virtual void entityDestroyed(Entity entity) = 0; - }; + /** + * @class IComponentArray + * + * @brief Interface for a component array in the ECS framework. + */ + class IComponentArray { + public: + virtual ~IComponentArray() = default; + virtual void entityDestroyed(Entity entity) = 0; + }; /** - * @class ComponentArray - * - * @brief Templated class that manages a specific type of component for all entities. - * - * This class manages the storage, retrieval, and deletion of components of a specific type. - * It ensures efficient access and modification of components associated with entities. - * - * @tparam T - The type of the component this array will manage. - */ - template - class ComponentArray final : public IComponentArray - { - public: - /** - * @brief Inserts a component for a specific entity. - * - * @param entity - The entity to which the component will be added. - * @param component - The component to add. - */ - void insertData(const Entity entity, T component) - { - if (m_size == MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, MAX_ENTITIES); - if (m_entityToIndexMap.contains(entity)) - { - LOG(NEXO_WARN, "ECS::ComponentArray::insertData: Component already added to entity {}", entity); - return; - } - - size_t newIndex = m_size; - m_entityToIndexMap[entity] = newIndex; - m_indexToEntityMap[newIndex] = entity; - m_componentArray[newIndex] = component; - ++m_size; - } - - /** - * @brief Removes a component from a specific entity. - * - * @param entity - The entity from which the component will be removed. - */ - void removeData(const Entity entity) - { - if (!m_entityToIndexMap.contains(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - - size_t indexOfRemovedEntity = m_entityToIndexMap[entity]; - size_t indexOfLastElement = m_size - 1; - m_componentArray[indexOfRemovedEntity] = m_componentArray[indexOfLastElement]; - - const Entity entityOfLastElement = m_indexToEntityMap[indexOfLastElement]; - m_entityToIndexMap[entityOfLastElement] = indexOfRemovedEntity; - m_indexToEntityMap[indexOfRemovedEntity] = entityOfLastElement; - - m_entityToIndexMap.erase(entity); - m_indexToEntityMap.erase(indexOfLastElement); - - --m_size; - } - - /** - * @brief Retrieves a reference to a component associated with a specific entity. - * - * @param entity - The entity whose component is to be retrieved. - * @return T& - A reference to the requested component. - */ - T& getData(const Entity entity) - { - if (!m_entityToIndexMap.contains(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - - return m_componentArray[m_entityToIndexMap[entity]]; - } - - /** - * @brief Cleans up components associated with a destroyed entity. - * - * @param entity - The destroyed entity. - */ - void entityDestroyed(const Entity entity) override - { - if (m_entityToIndexMap.contains(entity)) - removeData(entity); - } - - bool hasComponent(const Entity entity) const - { - return m_entityToIndexMap.contains(entity); - } - - private: - std::array m_componentArray; - - std::unordered_map m_entityToIndexMap; - - std::unordered_map m_indexToEntityMap; - - size_t m_size = 0; - }; + * @class ComponentArray + * + * @brief Component storage using sparse set pattern for O(1) lookup. + * + * Dense storage is maintained in dynamically growing vectors. The initial capacity is reserved + * at 1024 elements by default and then the vectors grow as needed. When many components are removed, + * the dense storage is shrunk to 125% of the current size. + * + * @tparam T - The type of component to store. + */ + template + class alignas(64) ComponentArray final : public IComponentArray { + public: + ComponentArray() + { + m_sparse.resize(capacity, INVALID_ENTITY); + m_dense.reserve(capacity); + m_componentArray.reserve(capacity); + } + + void insertData(Entity entity, T component) + { + if (entity >= MAX_ENTITIES) + THROW_EXCEPTION(OutOfRange, entity); + + // Ensure m_sparse can hold this entity index. + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "ECS::ComponentArray::insertData: Component already added to entity {}", entity); + return; + } + + const size_t newIndex = m_size; + m_sparse[entity] = newIndex; + m_dense.push_back(entity); + m_componentArray.push_back(std::move(component)); + ++m_size; + } + + void removeData(Entity entity) + { + if (entity >= m_sparse.size() || !hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + const size_t indexToRemove = m_sparse[entity]; + const size_t lastIndex = m_size - 1; + const Entity lastEntity = m_dense[lastIndex]; + + // Swap and pop. + m_componentArray[indexToRemove] = std::move(m_componentArray[lastIndex]); + m_dense[indexToRemove] = lastEntity; + m_sparse[lastEntity] = indexToRemove; + + m_sparse[entity] = INVALID_ENTITY; + m_componentArray.pop_back(); + m_dense.pop_back(); + --m_size; + + shrinkIfNeeded(); + } + + [[nodiscard]] T& getData(Entity entity) + { + if (entity >= m_sparse.size() || !hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + return m_componentArray[m_sparse[entity]]; + } + + [[nodiscard]] const T& getData(Entity entity) const + { + if (entity >= m_sparse.size() || !hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + return m_componentArray[m_sparse[entity]]; + } + + [[nodiscard]] bool hasComponent(Entity entity) const + { + return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); + } + + void entityDestroyed(Entity entity) override + { + if (hasComponent(entity)) + removeData(entity); + } + + [[nodiscard]] size_t size() const + { + return m_size; + } + + [[nodiscard]] Entity getEntityAtIndex(size_t index) const + { + if (index >= m_size) + THROW_EXCEPTION(OutOfRange, index); + return m_dense[index]; + } + + [[nodiscard]] std::span rawData() + { + return std::span(m_componentArray.data(), m_size); + } + + [[nodiscard]] std::span rawData() const + { + return std::span(m_componentArray.data(), m_size); + } + + [[nodiscard]] std::span entities() const + { + return std::span(m_dense.data(), m_size); + } + + private: + // Dense storage for components. + std::vector m_componentArray; + // Sparse mapping: maps entity ID to index in the dense arrays. + std::vector m_sparse; + // Dense storage for entity IDs. + std::vector m_dense; + // Current number of active components. + size_t m_size = 0; + + // Ensures m_sparse is large enough to index 'entity'. + void ensureSparseCapacity(Entity entity) + { + if (entity >= m_sparse.size()) { + size_t newSize = m_sparse.size(); + if (newSize == 0) + newSize = capacity; + while (entity >= newSize) + newSize *= 2; + m_sparse.resize(newSize, INVALID_ENTITY); + } + } + + // Shrinks the dense vectors if m_size is less than half of their capacity. + // New capacity is set to 125% of m_size, with a minimum of the capacity given. + void shrinkIfNeeded() + { + auto shrinkVector = [this](auto& vec) { + size_t currentCapacity = vec.capacity(); + if (m_size < currentCapacity / 2) { + size_t newCapacity = static_cast(m_size * 1.25); + if (newCapacity < capacity) + newCapacity = capacity; + std::vector::type::value_type> newVec; + newVec.reserve(newCapacity); + newVec.assign(vec.begin(), vec.begin() + m_size); + vec.swap(newVec); + } + }; + + shrinkVector(m_componentArray); + shrinkVector(m_dense); + } + }; /** - * @class ComponentManager - * - * @brief Manages the registration and handling of components in an ECS architecture. - * - * The ComponentManager is responsible for managing different types of components in the ECS framework. - * It allows the registration of component types, adding and removing components to entities, and - * accessing components of entities. - */ - class ComponentManager - { - public: - /** - * @brief Registers a new component type in the system. - * - * Each component type is associated with a unique ComponentType ID and a ComponentArray - * to manage instances of the component. - */ - template - void registerComponent() - { - std::type_index typeName(typeid(T)); - if (m_componentTypes.contains(typeName)) - { - LOG(NEXO_WARN, "ECS::ComponentManager::registerComponent: Component already registered"); - return; - } - - m_componentTypes.insert({typeName, _nextComponentType}); - - m_componentArrays.insert({typeName, std::make_shared>()}); - - ++_nextComponentType; - } - - /** - * @brief Retrieves the ComponentType ID for a specific component type. - * - * @return ComponentType - The unique ID associated with the component type. - */ - template - ComponentType getComponentType() - { - const std::type_index typeName(typeid(T)); - if (!m_componentTypes.contains(typeName)) - THROW_EXCEPTION(ComponentNotRegistered); - - return m_componentTypes[typeName]; - } - - /** - * @brief Adds a component of a specific type to an entity. - * - * @param entity - The entity to which the component will be added. - * @param component - The component to add to the entity. - */ - template - void addComponent(Entity entity, T component) - { - getComponentArray()->insertData(entity, component); - } - - /** - * @brief Removes a component of a specific type from an entity. - * - * @param entity - The entity from which the component will be removed. - */ - template - void removeComponent(Entity entity) - { - getComponentArray()->removeData(entity); - } - - template - bool tryRemoveComponent(Entity entity) - { - if (!getComponentArray()->hasComponent(entity)) - return false; - getComponentArray()->removeData(entity); - return true; - } - - /** - * @brief Retrieves a reference to a component of a specific type from an entity. - * - * @param entity - The entity whose component is to be retrieved. - * @return T& - A reference to the requested component. - */ - template - T& getComponent(Entity entity) - { - return getComponentArray()->getData(entity); - } - - template - std::optional> tryGetComponent(Entity entity) - { - if (!getComponentArray()->hasComponent(entity)) - return std::nullopt; - return getComponent(entity); - } - - /** - * @brief Retrieves a reference to all components of a specific type from an entity. - * - * @param entity - The entity whose component is to be retrieved. - */ - std::vector> getAllComponents(Entity entity) - { - std::vector> components; - - for (const auto& [type, array] : m_componentArrays) - { - auto componentArray = std::dynamic_pointer_cast>>(array); - if (componentArray && componentArray->hasComponent(entity)) - { - components.push_back(componentArray->getData(entity)); - } - } - return components; - } - - std::optional>> tryGetAllComponents(const Entity entity) - { - std::vector> components = getAllComponents(entity); - if (components.empty()) - { - return std::nullopt; - } - return components; - } - - /** - * @brief Handles the destruction of an entity by ensuring all associated components are removed. - * - * @param entity - The entity that has been destroyed. - */ - void entityDestroyed(Entity entity) const; - - private: - std::unordered_map m_componentTypes{}; - std::unordered_map> m_componentArrays; - - ComponentType _nextComponentType{}; - - /** - * @brief Retrieves the ComponentArray associated with a specific component type. - * - * @return std::shared_ptr> - Shared pointer to the component array of the specified type. - */ - template - std::shared_ptr> getComponentArray() - { - const std::type_index typeName(typeid(T)); - - if (!m_componentArrays.contains(typeName)) - THROW_EXCEPTION(ComponentNotRegistered); - - return std::static_pointer_cast>(m_componentArrays[typeName]); - } - }; + * @class ComponentManager + * + * @brief Manages all component arrays and provides access to them. + */ + class ComponentManager { + public: + ComponentManager() = default; + + // Non-copyable + ComponentManager(const ComponentManager&) = delete; + ComponentManager& operator=(const ComponentManager&) = delete; + + // Movable + ComponentManager(ComponentManager&&) noexcept = default; + ComponentManager& operator=(ComponentManager&&) noexcept = default; + + /** + * @brief Registers a component type in the system + * + * @tparam T The component type to register + */ + template + void registerComponent() + { + const ComponentType typeID = getComponentTypeID(); + + if (m_componentArrays[typeID] != nullptr) { + LOG(NEXO_WARN, "ECS::ComponentManager::registerComponent: Component already registered"); + return; + } + + m_componentArrays[typeID] = std::make_shared>(); + } + + /** + * @brief Gets the component type ID + * + * @tparam T The component type + * @return The component type ID + */ + 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 + * + * @tparam T The component type + * @param entity The entity to add the component to + * @param component The component to add + */ + template + void addComponent(Entity entity, T component) + { + getComponentArray()->insertData(entity, std::move(component)); + } + + /** + * @brief Removes a component from an entity + * + * @tparam T The component type + * @param entity The entity to remove the component from + */ + template + void removeComponent(Entity entity) + { + getComponentArray()->removeData(entity); + } + + /** + * @brief Tries to remove a component from an entity + * + * @tparam T The component type + * @param entity The entity to remove the component from + * @return true if the component was removed, false if it didn't exist + */ + template + bool tryRemoveComponent(Entity entity) + { + auto componentArray = getComponentArray(); + if (!componentArray->hasComponent(entity)) + return false; + + componentArray->removeData(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 + */ + template + [[nodiscard]] T& getComponent(Entity entity) + { + return getComponentArray()->getData(entity); + } + + /** + * @brief Gets a component array + * + * @tparam T The component type + * @return Shared pointer to the component array + */ + template + [[nodiscard]] std::shared_ptr> getComponentArray() + { + const ComponentType typeID = getComponentTypeID(); + + auto& componentArray = m_componentArrays[typeID]; + if (componentArray == nullptr) + THROW_EXCEPTION(ComponentNotRegistered); + + return std::static_pointer_cast>(componentArray); + } + + /** + * @brief Gets a component array (const version) + * + * @tparam T The component type + * @return Shared pointer to the component array + */ + template + [[nodiscard]] std::shared_ptr> getComponentArray() const + { + const ComponentType typeID = getComponentTypeID(); + + const auto& componentArray = m_componentArrays[typeID]; + if (componentArray == nullptr) + THROW_EXCEPTION(ComponentNotRegistered); + + return std::static_pointer_cast>(componentArray); + } + + /** + * @brief Tries 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 + */ + template + [[nodiscard]] std::optional> tryGetComponent(Entity entity) + { + auto componentArray = getComponentArray(); + if (!componentArray->hasComponent(entity)) + return std::nullopt; + + return componentArray->getData(entity); + } + + /** + * @brief Notifies all component arrays that an entity has been destroyed + * + * @param entity The destroyed entity + */ + void entityDestroyed(Entity entity); + + private: + // Use fixed-size array for O(1) component type lookup instead of std::unordered_map + std::array, MAX_COMPONENT_TYPE> m_componentArrays{}; + }; } From d70b8fc8017823d948cd476aa66b8ec2a811edb4 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 16:37:17 +0900 Subject: [PATCH 003/450] refactor(ecs-opti): instead of checking if entity is contained in the system, compare its old signature and delete it if corresponding --- engine/src/ecs/Coordinator.cpp | 5 +++-- engine/src/ecs/Coordinator.hpp | 33 ++++++++++++++++++++++++--------- engine/src/ecs/System.cpp | 28 ++++++++++++++++++---------- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index bb365c963..4bf2b9dea 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -37,9 +37,10 @@ namespace nexo::ecs { void Coordinator::destroyEntity(const Entity entity) const { + auto signature = m_entityManager->getSignature(entity); m_entityManager->destroyEntity(entity); m_componentManager->entityDestroyed(entity); - m_systemManager->entityDestroyed(entity); + m_systemManager->entityDestroyed(entity, signature); } std::vector Coordinator::getAllComponentTypes(const Entity entity) const @@ -67,4 +68,4 @@ namespace nexo::ecs { return components; } -} \ No newline at end of file +} diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index c1a371652..86602df5a 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -16,6 +16,7 @@ #include #include +#include #include "Components.hpp" #include "System.hpp" @@ -60,7 +61,8 @@ namespace nexo::ecs { * @brief Registers a new component type within the ComponentManager. */ template - void registerComponent() { + void registerComponent() + { m_componentManager->registerComponent(); m_hasComponentFunctions[typeid(T)] = [this](Entity entity) -> bool { return this->entityHasComponent(entity); @@ -78,7 +80,8 @@ namespace nexo::ecs { * @param component */ template - void registerSingletonComponent(Args&&... args) { + void registerSingletonComponent(Args&&... args) + { m_singletonComponentManager->registerSingletonComponent(std::forward(args)...); } @@ -89,14 +92,16 @@ namespace nexo::ecs { * @param component - The component to add to the entity. */ template - void addComponent(const Entity entity, T component) { + void addComponent(const Entity entity, T component) + { m_componentManager->addComponent(entity, component); auto signature = m_entityManager->getSignature(entity); + auto oldSignature = signature; signature.set(m_componentManager->getComponentType(), true); m_entityManager->setSignature(entity, signature); - m_systemManager->entitySignatureChanged(entity, signature); + m_systemManager->entitySignatureChanged(entity, oldSignature, signature); } /** @@ -110,10 +115,11 @@ namespace nexo::ecs { m_componentManager->removeComponent(entity); auto signature = m_entityManager->getSignature(entity); + auto oldSignature = signature; signature.set(m_componentManager->getComponentType(), false); m_entityManager->setSignature(entity, signature); - m_systemManager->entitySignatureChanged(entity, signature); + m_systemManager->entitySignatureChanged(entity, oldSignature, signature); } /** @@ -130,10 +136,11 @@ namespace nexo::ecs { if (m_componentManager->tryRemoveComponent(entity)) { auto signature = m_entityManager->getSignature(entity); + auto oldSignature = signature; signature.set(m_componentManager->getComponentType(), false); m_entityManager->setSignature(entity, signature); - m_systemManager->entitySignatureChanged(entity, signature); + m_systemManager->entitySignatureChanged(entity, oldSignature, signature); } } @@ -155,10 +162,17 @@ namespace nexo::ecs { * @return T& - Reference to the requested component. */ template - T &getComponent(const Entity entity) { + T &getComponent(const Entity entity) + { return m_componentManager->getComponent(entity); } + template + std::shared_ptr> getComponentArray() + { + return m_componentManager->getComponentArray(); + } + /** * @brief Attempts to retrieve a component from an entity. * @@ -179,7 +193,8 @@ namespace nexo::ecs { * @return T& The instance of the desired singleton component */ template - T &getSingletonComponent() { + T &getSingletonComponent() + { return m_singletonComponentManager->getSingletonComponent(); } @@ -275,10 +290,10 @@ namespace nexo::ecs { const ComponentType componentType = m_componentManager->getComponentType(); return signature.test(componentType); } + void updateSystemEntities() const; private: - std::shared_ptr m_componentManager; std::shared_ptr m_entityManager; std::shared_ptr m_systemManager; diff --git a/engine/src/ecs/System.cpp b/engine/src/ecs/System.cpp index 806aa2fb3..958284cf7 100644 --- a/engine/src/ecs/System.cpp +++ b/engine/src/ecs/System.cpp @@ -16,24 +16,32 @@ namespace nexo::ecs { - void SystemManager::entityDestroyed(const Entity entity) const + void SystemManager::entityDestroyed(const Entity entity, const Signature signature) const { for (const auto &[fst, snd] : m_systems) { + auto const &type = fst; auto const &system = snd; - system->entities.erase(entity); + if (auto const &systemSignature = m_signatures.at(type); (signature & systemSignature) == systemSignature) + system->entities.erase(entity); } } - void SystemManager::entitySignatureChanged(const Entity entity, const Signature entitySignature) + void SystemManager::entitySignatureChanged(const Entity entity, + const Signature oldSignature, + const Signature newSignature) { - for (const auto &[fst, snd] : m_systems) { - auto const &type = fst; - auto const &system = snd; - - if (auto const &systemSignature = m_signatures[type]; (entitySignature & systemSignature) == systemSignature) + for (auto& [type, system] : m_systems) { + const auto systemSignature = m_signatures.at(type); + // Check if entity qualifies now but did not qualify before. + if (((oldSignature & systemSignature) != systemSignature) && + ((newSignature & systemSignature) == systemSignature)) { system->entities.insert(entity); - else + } + // Otherwise, if the entity no longer qualifies but did before, remove it. + else if (((oldSignature & systemSignature) == systemSignature) && + ((newSignature & systemSignature) != systemSignature)) { system->entities.erase(entity); + } } } -} \ No newline at end of file +} From ae7aedfdd74c53789b7168b83892d302549eb845 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 16:37:46 +0900 Subject: [PATCH 004/450] refactor(ecs-opti): now using a sparse set too for handling entities inside the system --- engine/src/ecs/System.hpp | 63 ++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/engine/src/ecs/System.hpp b/engine/src/ecs/System.hpp index 2fb0ccbee..609e40252 100644 --- a/engine/src/ecs/System.hpp +++ b/engine/src/ecs/System.hpp @@ -14,7 +14,6 @@ #pragma once -#include #include #include #include @@ -26,6 +25,52 @@ namespace nexo::ecs { } namespace nexo::ecs { + + class SparseSetUnordered { + public: + void insert(Entity entity) + { + if (contains(entity)) + { + LOG(NEXO_WARN, "Entity {} already added to the sparse set", entity); + return; + } + + sparse[entity] = dense.size(); + dense.push_back(entity); + } + + void erase(Entity entity) + { + if (!contains(entity)) + { + LOG(NEXO_WARN, "Entity {} does not exist in the sparse set", entity); + return; + } + + size_t index = sparse[entity]; + size_t lastIndex = dense.size() - 1; + Entity lastEntity = dense[lastIndex]; + + dense[index] = lastEntity; + sparse[lastEntity] = index; + dense.pop_back(); + sparse.erase(entity); + } + + const std::vector& getDense() const { return dense; } + auto begin() const { return dense.begin(); } + auto end() const { return dense.end(); } + + private: + std::vector dense; + std::unordered_map sparse; + + bool contains(Entity entity) const + { + return sparse.find(entity) != sparse.end(); + } + }; /** * @class System * @@ -36,7 +81,7 @@ namespace nexo::ecs { */ class System { public: - std::set entities; + SparseSetUnordered entities; static std::shared_ptr coord; }; @@ -57,7 +102,8 @@ namespace nexo::ecs { * @return std::shared_ptr - Shared pointer to the newly registered system. */ template - std::shared_ptr registerSystem() { + std::shared_ptr registerSystem() + { std::type_index typeName(typeid(T)); if (m_systems.contains(typeName)) @@ -79,7 +125,8 @@ namespace nexo::ecs { * @param signature - The signature to associate with the system. */ template - void setSignature(Signature signature) { + void setSignature(Signature signature) + { std::type_index typeName(typeid(T)); if (!m_systems.contains(typeName)) @@ -92,17 +139,19 @@ namespace nexo::ecs { * @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) const; + 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 entitySignature - The new signature of the entity. + * @param oldSignature - The old signature of the entity. + * @param newSignature - The new signature of the entity. */ - void entitySignatureChanged(Entity entity, Signature entitySignature); + void entitySignatureChanged(Entity entity, Signature oldSignature, Signature newSignature); private: std::unordered_map m_signatures{}; std::unordered_map> m_systems{}; From 561d94fea0321fa10a3e3f87e493e1c475919a73 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 16:38:27 +0900 Subject: [PATCH 005/450] refactor(ecs-opti): update the ecs example + make it into a benchmark --- examples/ecs/CMakeLists.txt | 12 ++-- examples/ecs/exampleBasic.cpp | 127 ++++++++++++++++++++++++++-------- 2 files changed, 108 insertions(+), 31 deletions(-) diff --git a/examples/ecs/CMakeLists.txt b/examples/ecs/CMakeLists.txt index 3e2292ce1..36d8ed948 100644 --- a/examples/ecs/CMakeLists.txt +++ b/examples/ecs/CMakeLists.txt @@ -8,7 +8,12 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) set(SRCS - examples/ecs/exampleBasic.cpp + exampleBasic.cpp + ../../common/Exception.cpp + ../../engine/src/ecs/Components.cpp + ../../engine/src/ecs/Entity.cpp + ../../engine/src/ecs/Coordinator.cpp + ../../engine/src/ecs/System.cpp ) add_executable(ecsExample ${SRCS}) @@ -20,8 +25,8 @@ set_target_properties(ecsExample RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/" ) -include_directories("./engine/src") -include_directories("./common") +include_directories("../../engine/src") +include_directories("../../common") #if(WIN32) # include_directories("${CMAKE_CURRENT_SOURCE_DIR}/vcpkg_installed/x64-windows/include") @@ -39,7 +44,6 @@ if(APPLE) endif() include_directories(include) -target_link_libraries(ecsExample PRIVATE nexoRenderer) # Set the output directory for the executable (prevents generator from creating Debug/Release folders) set_target_properties(ecsExample PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<0:>) diff --git a/examples/ecs/exampleBasic.cpp b/examples/ecs/exampleBasic.cpp index 8e38a37b9..69ced3b26 100644 --- a/examples/ecs/exampleBasic.cpp +++ b/examples/ecs/exampleBasic.cpp @@ -12,8 +12,14 @@ // /////////////////////////////////////////////////////////////////////////////// #include +#include #include "ecs/Coordinator.hpp" +#include +#include +#include +#include + struct Position { float x; float y; @@ -26,12 +32,21 @@ struct Velocity { float z; }; + class MovementSystem : public nexo::ecs::System { - public: - void update(float deltaTime) { + public: + + MovementSystem() + { + positionArray = coord->getComponentArray(); + velocityArray = coord->getComponentArray(); + } + + void update(float deltaTime) + { for (auto entity : entities) { - auto& position = coord->getComponent(entity); - auto& velocity = coord->getComponent(entity); + auto& position = positionArray->getData(entity); + auto& velocity = velocityArray->getData(entity); // Update position using velocity position.x += velocity.x * deltaTime; @@ -39,49 +54,107 @@ class MovementSystem : public nexo::ecs::System { position.z += velocity.z * deltaTime; } } + + private: + std::shared_ptr> positionArray; + std::shared_ptr> velocityArray; }; -int main() { - // Initialize the ECS Coordinator +int main(int argc, char** argv) { + // Check if the correct number of arguments is provided. + if (argc != 4) { + std::cerr << "Usage: " << argv[0] + << " \n"; + return 1; + } + + // Parse command-line arguments. + int numEntities = std::stoi(argv[1]); + int numFrames = std::stoi(argv[2]); + int repeatCount = std::stoi(argv[3]); + + std::cout << "Running with " << numEntities << " entities, " + << numFrames << " frames per run, repeated " << repeatCount << " times.\n"; + + // Initialize the ECS Coordinator. nexo::ecs::Coordinator coordinator; coordinator.init(); - // Register components + // Register components. coordinator.registerComponent(); coordinator.registerComponent(); - // Register and set up the MovementSystem + // Register and set up the MovementSystem. auto movementSystem = coordinator.registerSystem(); nexo::ecs::Signature movementSignature; movementSignature.set(coordinator.getComponentType(), true); movementSignature.set(coordinator.getComponentType(), true); coordinator.setSystemSignature(movementSignature); - // Create entities and assign components - auto entity1 = coordinator.createEntity(); - coordinator.addComponent(entity1, Position{0.0f, 0.0f, 0.0f}); - coordinator.addComponent(entity1, Velocity{1.0f, 0.0f, 0.0f}); + auto setupStart = std::chrono::steady_clock::now(); + // Create entities and assign components. + std::vector createdEntities; + createdEntities.reserve(numEntities); + for (int i = 0; i < numEntities; i++) { + nexo::ecs::Entity newEntity = coordinator.createEntity(); + coordinator.addComponent(newEntity, Position{0.0f, 0.0f, 0.0f}); + coordinator.addComponent(newEntity, Velocity{1.0f, 0.0f, 0.0f}); + createdEntities.push_back(newEntity); + } + auto setupEnd = std::chrono::steady_clock::now(); + auto setupDurationMicro = std::chrono::duration_cast(setupEnd - setupStart).count(); + + std::cout << "Setup duration: " << setupDurationMicro << " microseconds" << std::endl; - auto entity2 = coordinator.createEntity(); - coordinator.addComponent(entity2, Position{5.0f, 5.0f, 5.0f}); - coordinator.addComponent(entity2, Velocity{0.0f, -1.0f, 0.0f}); + // Remove 25% of the entities randomly. + int numToRemove = numEntities / 4; + { + // Create a random device and engine. + std::random_device rd; + std::mt19937 engine(rd()); + std::shuffle(createdEntities.begin(), createdEntities.end(), engine); + } + for (int i = 0; i < numToRemove; ++i) { + nexo::ecs::Entity entityToRemove = createdEntities[i]; + coordinator.destroyEntity(entityToRemove); + } + std::cout << "Removed " << numToRemove << " entities randomly after setup.\n"; - // Simulate a game loop - for (int frame = 0; frame < 10; ++frame) { - std::cout << "Frame " << frame << ":\n"; + // Run the update loop as specified by the command-line arguments. + // We'll measure frame times in microseconds. + long long overallDurationMicro = 0; + for (int repeat = 0; repeat < repeatCount; ++repeat) { + long long totalDurationMicro = 0; + for (int frame = 0; frame < numFrames; ++frame) { + auto frameStart = std::chrono::steady_clock::now(); - // Update the MovementSystem - movementSystem->update(0.016f); // Assuming 60 FPS, so ~16ms per frame + // Update the MovementSystem (assume a delta time of ~16ms per frame). + movementSystem->update(0.016f); - // Output entity positions - for (auto entity : movementSystem->entities) { - auto& position = coordinator.getComponent(entity); - std::cout << "Entity " << entity << " Position: (" - << position.x << ", " << position.y << ", " << position.z << ")\n"; + auto frameEnd = std::chrono::steady_clock::now(); + auto frameDurationMicro = std::chrono::duration_cast(frameEnd - frameStart).count(); + // Convert microseconds to milliseconds for display. + double frameDurationMilli = frameDurationMicro / 1000.0; + totalDurationMicro += frameDurationMicro; } - - std::cout << "---------------------\n"; + double totalDurationMilli = totalDurationMicro / 1000.0; + double averageDurationMilli = (totalDurationMicro / static_cast(numFrames)) / 1000.0; + std::cout << "Iteration " << repeat << ": total time to render " << numFrames + << " frames: " << totalDurationMilli << " milliseconds.\n"; + std::cout << "Iteration " << repeat << ": average frame duration: " + << averageDurationMilli << " milliseconds.\n\n"; + overallDurationMicro += totalDurationMicro; } + // Overall per-frame average (in milliseconds). + double overallAverageFrameMilli = (overallDurationMicro / static_cast(repeatCount * numFrames)) / 1000.0; + std::cout << "Overall average frame duration over all repeats: " + << overallAverageFrameMilli << " milliseconds.\n"; + + // Average total time to render numFrames frames across all repeats. + double averageTotalTimeMilli = (overallDurationMicro / static_cast(repeatCount)) / 1000.0; + std::cout << "Average time to render " << numFrames << " frames: " + << averageTotalTimeMilli << " milliseconds.\n"; + return 0; } From d015d8f6a46a402cf91a70efef5e8d3bdcb56322 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 16:44:08 +0900 Subject: [PATCH 006/450] fix(ecs-opti): fix cmake for git action --- examples/ecs/CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/ecs/CMakeLists.txt b/examples/ecs/CMakeLists.txt index 36d8ed948..49648f6a6 100644 --- a/examples/ecs/CMakeLists.txt +++ b/examples/ecs/CMakeLists.txt @@ -8,12 +8,12 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) set(SRCS - exampleBasic.cpp - ../../common/Exception.cpp - ../../engine/src/ecs/Components.cpp - ../../engine/src/ecs/Entity.cpp - ../../engine/src/ecs/Coordinator.cpp - ../../engine/src/ecs/System.cpp + examples/ecs/exampleBasic.cpp + common/Exception.cpp + engine/src/ecs/Components.cpp + engine/src/ecs/Entity.cpp + engine/src/ecs/Coordinator.cpp + engine/src/ecs/System.cpp ) add_executable(ecsExample ${SRCS}) From e9561a30da7817941e91d52ceb6de57754e1b825 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 17:59:44 +0900 Subject: [PATCH 007/450] tests(ecs-opti): fix test to comply with new refactor --- tests/ecs/Components.test.cpp | 10 +++++----- tests/ecs/System.test.cpp | 30 +++++++++++++++++++----------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/tests/ecs/Components.test.cpp b/tests/ecs/Components.test.cpp index 86fe62652..10039c2b1 100644 --- a/tests/ecs/Components.test.cpp +++ b/tests/ecs/Components.test.cpp @@ -58,7 +58,7 @@ namespace nexo::ecs { array.insertData(entity, {42}); EXPECT_NO_THROW(array.removeData(entity)); - EXPECT_THROW(array.getData(entity), ComponentNotFound); + EXPECT_THROW(static_cast(array.getData(entity)), ComponentNotFound); } TEST(ComponentArrayTest, HandleEntityDestruction) @@ -70,7 +70,7 @@ namespace nexo::ecs { array.insertData(entity, {42}); array.entityDestroyed(entity); - EXPECT_THROW(array.getData(entity), ComponentNotFound); + EXPECT_THROW(static_cast(array.getData(entity)), ComponentNotFound); } TEST(ComponentArrayTest, InsertDuplicateEntity) @@ -111,7 +111,7 @@ namespace nexo::ecs { componentManager->addComponent(entity, TestComponent{42}); EXPECT_NO_THROW(componentManager->removeComponent(entity)); - EXPECT_THROW(componentManager->getComponent(entity), ComponentNotFound); + EXPECT_THROW(static_cast(componentManager->getComponent(entity)), ComponentNotFound); } TEST_F(ComponentManagerTest, TryRemoveComponent) @@ -137,13 +137,13 @@ namespace nexo::ecs { componentManager->entityDestroyed(entity1); - EXPECT_THROW(componentManager->getComponent(entity1), ComponentNotFound); + EXPECT_THROW(static_cast(componentManager->getComponent(entity1)), ComponentNotFound); EXPECT_EQ(componentManager->getComponent(entity2).value, 84); } TEST_F(ComponentManagerTest, RetrieveUnregisteredComponentType) { - EXPECT_THROW(componentManager->getComponentType(), ComponentNotRegistered); + EXPECT_THROW(static_cast(componentManager->getComponentType()), ComponentNotRegistered); } TEST_F(ComponentManagerTest, AddComponentWithoutRegistering) diff --git a/tests/ecs/System.test.cpp b/tests/ecs/System.test.cpp index c5bff2c9a..015fb8bc5 100644 --- a/tests/ecs/System.test.cpp +++ b/tests/ecs/System.test.cpp @@ -79,7 +79,7 @@ namespace nexo::ecs { Entity entity = 1; mockSystem->entities.insert(entity); - systemManager->entityDestroyed(entity); + systemManager->entityDestroyed(entity, signature); EXPECT_TRUE(mockSystem->entities.empty()); } @@ -93,9 +93,10 @@ namespace nexo::ecs { Entity entity = 1; Signature entitySignature; + Signature oldSignature; entitySignature.set(1); // Matches the system's signature - systemManager->entitySignatureChanged(entity, entitySignature); + systemManager->entitySignatureChanged(entity, oldSignature, entitySignature); EXPECT_TRUE(mockSystem->entities.contains(entity)); } @@ -109,15 +110,17 @@ namespace nexo::ecs { Entity entity = 1; Signature entitySignature; + Signature oldSignature; entitySignature.set(1); // Matches the system's signature // Add the entity - systemManager->entitySignatureChanged(entity, entitySignature); + systemManager->entitySignatureChanged(entity, oldSignature, entitySignature); EXPECT_TRUE(mockSystem->entities.contains(entity)); // Change signature to no longer match + oldSignature = entitySignature; entitySignature.reset(1); // Clear the 1st bit - systemManager->entitySignatureChanged(entity, entitySignature); + systemManager->entitySignatureChanged(entity, oldSignature, entitySignature); EXPECT_FALSE(mockSystem->entities.contains(entity)); } @@ -131,9 +134,10 @@ namespace nexo::ecs { Entity entity = 1; Signature entitySignature; + Signature oldSignature; entitySignature.set(2); // Does not match the system's signature - systemManager->entitySignatureChanged(entity, entitySignature); + systemManager->entitySignatureChanged(entity, oldSignature, entitySignature); EXPECT_FALSE(mockSystem->entities.contains(entity)); } @@ -146,13 +150,15 @@ namespace nexo::ecs { systemManager->setSignature(signature); Entity entity = 1; + Signature entitySignature; + entitySignature.set(1); mockSystem->entities.insert(entity); - systemManager->entityDestroyed(entity); + systemManager->entityDestroyed(entity, entitySignature); EXPECT_TRUE(mockSystem->entities.empty()); // Destroying the same entity again should not throw an error - EXPECT_NO_THROW(systemManager->entityDestroyed(entity)); + EXPECT_NO_THROW(systemManager->entityDestroyed(entity, entitySignature)); } TEST_F(SystemManagerTest, AddAndRemoveEntitiesFromMultipleSystems) { @@ -172,13 +178,15 @@ namespace nexo::ecs { Entity entity2 = 2; Signature entitySignature1; + Signature oldSignature1; entitySignature1.set(1); // Matches system1 Signature entitySignature2; + Signature oldSignature2; entitySignature2.set(2); // Matches system2 - systemManager->entitySignatureChanged(entity1, entitySignature1); - systemManager->entitySignatureChanged(entity2, entitySignature2); + systemManager->entitySignatureChanged(entity1, oldSignature1, entitySignature1); + systemManager->entitySignatureChanged(entity2, oldSignature2, entitySignature2); EXPECT_TRUE(system1->entities.contains(entity1)); EXPECT_FALSE(system1->entities.contains(entity2)); @@ -186,8 +194,8 @@ namespace nexo::ecs { EXPECT_TRUE(system2->entities.contains(entity2)); // Change signature of entity1 to match system2 - systemManager->entitySignatureChanged(entity1, entitySignature2); + systemManager->entitySignatureChanged(entity1, entitySignature1, entitySignature2); EXPECT_FALSE(system1->entities.contains(entity1)); EXPECT_TRUE(system2->entities.contains(entity1)); } -} \ No newline at end of file +} From 4f85252dcb4531fe031e588ab00b574d6db8e10a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 18:00:32 +0900 Subject: [PATCH 008/450] fix(ecs-opti): oopsi forgot to push the contains and empty method in the SparseSet --- engine/src/ecs/System.hpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/engine/src/ecs/System.hpp b/engine/src/ecs/System.hpp index 609e40252..f62c95f3c 100644 --- a/engine/src/ecs/System.hpp +++ b/engine/src/ecs/System.hpp @@ -58,6 +58,16 @@ namespace nexo::ecs { sparse.erase(entity); } + bool empty() const + { + return dense.empty(); + } + + bool contains(Entity entity) const + { + return sparse.find(entity) != sparse.end(); + } + const std::vector& getDense() const { return dense; } auto begin() const { return dense.begin(); } auto end() const { return dense.end(); } @@ -65,11 +75,6 @@ namespace nexo::ecs { private: std::vector dense; std::unordered_map sparse; - - bool contains(Entity entity) const - { - return sparse.find(entity) != sparse.end(); - } }; /** * @class System From cd298a0554606ce51045b680107a604b69efcbbd Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 18:18:55 +0900 Subject: [PATCH 009/450] tests(ecs-opti): add more tests for the component array and component manager --- engine/src/ecs/Components.hpp | 3 +- tests/ecs/Components.test.cpp | 505 +++++++++++++++++++++++++++++++++- 2 files changed, 500 insertions(+), 8 deletions(-) diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 58bfe54d8..9f2851d2a 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -82,7 +82,8 @@ namespace nexo::ecs { * * @tparam T - The type of component to store. */ - template + template= 1)>> class alignas(64) ComponentArray final : public IComponentArray { public: ComponentArray() diff --git a/tests/ecs/Components.test.cpp b/tests/ecs/Components.test.cpp index 10039c2b1..ce34b794a 100644 --- a/tests/ecs/Components.test.cpp +++ b/tests/ecs/Components.test.cpp @@ -85,6 +85,385 @@ namespace nexo::ecs { EXPECT_EQ(array.getData(entity).value, 42); // Original value should remain } + TEST(ComponentArrayTest, InitialStateEmpty) + { + ComponentArray array; + EXPECT_EQ(array.size(), 0); + EXPECT_FALSE(array.hasComponent(0)); + } + + TEST(ComponentArrayTest, SizeTracking) + { + ComponentArray array; + + array.insertData(1, {42}); + EXPECT_EQ(array.size(), 1); + + array.insertData(2, {84}); + EXPECT_EQ(array.size(), 2); + + array.removeData(1); + EXPECT_EQ(array.size(), 1); + } + + TEST(ComponentArrayTest, GetEntityAtIndex) + { + ComponentArray array; + + array.insertData(5, {42}); + array.insertData(10, {84}); + + EXPECT_EQ(array.getEntityAtIndex(0), 5); + EXPECT_EQ(array.getEntityAtIndex(1), 10); + EXPECT_THROW(static_cast(array.getEntityAtIndex(2)), OutOfRange); + } + + TEST(ComponentArrayTest, RawDataAccess) + { + ComponentArray array; + + array.insertData(1, {42}); + array.insertData(2, {84}); + + auto data = array.rawData(); + EXPECT_EQ(data.size(), 2); + EXPECT_EQ(data[0].value, 42); + EXPECT_EQ(data[1].value, 84); + + // Test const version + const auto& constArray = array; + auto constData = constArray.rawData(); + EXPECT_EQ(constData.size(), 2); + EXPECT_EQ(constData[0].value, 42); + EXPECT_EQ(constData[1].value, 84); + } + + TEST(ComponentArrayTest, EntitiesAccess) + { + ComponentArray array; + + array.insertData(5, {42}); + array.insertData(10, {84}); + + auto entities = array.entities(); + EXPECT_EQ(entities.size(), 2); + EXPECT_EQ(entities[0], 5); + EXPECT_EQ(entities[1], 10); + } + + TEST(ComponentArrayTest, SwapAndPopRemovalMechanism) + { + ComponentArray array; + + // Insert three components + array.insertData(1, {1}); + array.insertData(2, {2}); + array.insertData(3, {3}); + + // Remove the middle one + array.removeData(2); + + // Check if the entities are correctly mapped + EXPECT_EQ(array.size(), 2); + EXPECT_TRUE(array.hasComponent(1)); + EXPECT_FALSE(array.hasComponent(2)); + EXPECT_TRUE(array.hasComponent(3)); + + // The last entity (3) should now be at index 1 + EXPECT_EQ(array.getEntityAtIndex(1), 3); + + // Data should be preserved correctly + EXPECT_EQ(array.getData(1).value, 1); + EXPECT_EQ(array.getData(3).value, 3); + } + + TEST(ComponentArrayTest, AutomaticGrowth) + { + // Create array with small initial capacity + ComponentArray array; + + // Insert component for an entity beyond initial capacity + array.insertData(10, {42}); + + EXPECT_TRUE(array.hasComponent(10)); + EXPECT_EQ(array.getData(10).value, 42); + } + + TEST(ComponentArrayTest, ConstGetData) + { + ComponentArray array; + array.insertData(1, {42}); + + const ComponentArray& constArray = array; + EXPECT_EQ(constArray.getData(1).value, 42); + EXPECT_THROW(static_cast(constArray.getData(2)), ComponentNotFound); + } + + TEST(ComponentArrayTest, LargeEntityIDs) + { + ComponentArray array; + + Entity largeEntity = MAX_ENTITIES - 1; + array.insertData(largeEntity, {42}); + + EXPECT_TRUE(array.hasComponent(largeEntity)); + EXPECT_EQ(array.getData(largeEntity).value, 42); + } + + TEST(ComponentArrayTest, MultipleInsertionsAndRemovals) + { + ComponentArray array; + + // Insert several components + for (Entity e = 0; e < 100; ++e) { + array.insertData(e, {static_cast(e)}); + } + + EXPECT_EQ(array.size(), 100); + + // Remove some components + for (Entity e = 0; e < 100; e += 2) { + array.removeData(e); + } + + EXPECT_EQ(array.size(), 50); + + // Check remaining components + for (Entity e = 1; e < 100; e += 2) { + EXPECT_TRUE(array.hasComponent(e)); + EXPECT_EQ(array.getData(e).value, e); + } + } + + TEST(ComponentArrayTest, ShrinkingBehavior) + { + ComponentArray array; + + // Insert many components to force capacity increase + for (Entity e = 0; e < 100; ++e) { + array.insertData(e, {static_cast(e)}); + } + + // Remove most components to trigger shrinking + for (Entity e = 20; e < 100; ++e) { + array.removeData(e); + } + + // Check remaining components are still accessible + for (Entity e = 0; e < 20; ++e) { + EXPECT_TRUE(array.hasComponent(e)); + EXPECT_EQ(array.getData(e).value, e); + } + } + + TEST(ComponentArrayTest, ComplexComponentType) + { + struct ComplexComponent { + std::string name; + std::vector data; + bool flag; + + bool operator==(const ComplexComponent& other) const { + return name == other.name && data == other.data && flag == other.flag; + } + }; + + ComponentArray array; + + ComplexComponent comp1{"test", {1, 2, 3}, true}; + ComplexComponent comp2{"another", {4, 5}, false}; + + array.insertData(1, comp1); + array.insertData(2, comp2); + + EXPECT_EQ(array.getData(1), comp1); + EXPECT_EQ(array.getData(2), comp2); + } + + TEST(ComponentArrayTest, InsertBeyondMaxEntities) + { + ComponentArray array; + EXPECT_THROW(array.insertData(MAX_ENTITIES, {42}), OutOfRange); + } + + TEST(ComponentArrayTest, AccessNonExistentComponent) + { + ComponentArray array; + EXPECT_THROW(static_cast(array.getData(1)), ComponentNotFound); + } + + TEST(ComponentArrayTest, RemoveNonExistentComponent) + { + ComponentArray array; + EXPECT_THROW(array.removeData(1), ComponentNotFound); + } + + TEST(ComponentArrayTest, EntityDestroyedWithComponent) + { + ComponentArray array; + + array.insertData(1, {42}); + EXPECT_TRUE(array.hasComponent(1)); + + array.entityDestroyed(1); + EXPECT_FALSE(array.hasComponent(1)); + EXPECT_EQ(array.size(), 0); + } + + TEST(ComponentArrayTest, EntityDestroyedWithoutComponent) + { + ComponentArray array; + + array.insertData(2, {42}); + EXPECT_FALSE(array.hasComponent(1)); + + // Should not throw or affect other components + EXPECT_NO_THROW(array.entityDestroyed(1)); + EXPECT_TRUE(array.hasComponent(2)); + EXPECT_EQ(array.size(), 1); + } + + TEST(ComponentArrayTest, VerySmallCapacity) + { + ComponentArray array; + + // Insert beyond initial capacity + array.insertData(0, {0}); + array.insertData(1, {1}); + array.insertData(2, {2}); + + EXPECT_EQ(array.size(), 3); + EXPECT_TRUE(array.hasComponent(0)); + EXPECT_TRUE(array.hasComponent(1)); + EXPECT_TRUE(array.hasComponent(2)); + } + + TEST(ComponentArrayTest, ConcurrentEntityRemovalAndAccess) + { + ComponentArray array; + + for (Entity e = 0; e < 10; ++e) { + array.insertData(e, {static_cast(e)}); + } + + // Remove entities and check that others remain accessible + for (Entity e = 0; e < 10; e += 2) { + array.removeData(e); + + // Check remaining entities are still correctly accessible + for (Entity remaining = 1; remaining < 10; remaining += 2) { + EXPECT_TRUE(array.hasComponent(remaining)); + EXPECT_EQ(array.getData(remaining).value, remaining); + } + } + } + + TEST(ComponentArrayTest, ReinsertAfterRemoval) + { + ComponentArray array; + + array.insertData(1, {42}); + array.removeData(1); + EXPECT_FALSE(array.hasComponent(1)); + + // Reinsert with new value + array.insertData(1, {100}); + EXPECT_TRUE(array.hasComponent(1)); + EXPECT_EQ(array.getData(1).value, 100); + } + + TEST(ComponentArrayTest, MoveSemanticRespect) + { + // Instead of using a true move-only type, use a class that tracks moves + struct MoveTrackingComponent { + std::shared_ptr ptr; + int moves = 0; + + MoveTrackingComponent(int val) : ptr(std::make_shared(val)) {} + + MoveTrackingComponent(const MoveTrackingComponent& other) : ptr(other.ptr), moves(other.moves) {} + + MoveTrackingComponent(MoveTrackingComponent&& other) noexcept + : ptr(std::move(other.ptr)), moves(other.moves + 1) {} + + MoveTrackingComponent& operator=(const MoveTrackingComponent& other) { + ptr = other.ptr; + moves = other.moves; + return *this; + } + + MoveTrackingComponent& operator=(MoveTrackingComponent&& other) noexcept { + ptr = std::move(other.ptr); + moves = other.moves + 1; + return *this; + } + }; + + ComponentArray array; + + array.insertData(1, MoveTrackingComponent(42)); + EXPECT_EQ(*array.getData(1).ptr, 42); + + // Don't remove enough components to trigger shrinking + // Just verify the basic functionality works + } + + TEST(ComponentArrayTest, SmallOperationsWithoutShrinking) + { + // Using a component with expensive move operations + struct ExpensiveComponent { + std::vector data; + + ExpensiveComponent(std::initializer_list values) : data(values) {} + }; + + ComponentArray array; + + // Add just a few components + array.insertData(1, {1, 2, 3}); + array.insertData(2, {4, 5, 6}); + array.insertData(3, {7, 8, 9}); + + // Verify data access + EXPECT_EQ(array.getData(1).data, std::vector({1, 2, 3})); + EXPECT_EQ(array.getData(2).data, std::vector({4, 5, 6})); + EXPECT_EQ(array.getData(3).data, std::vector({7, 8, 9})); + + // Remove one (not enough to trigger shrinking) + array.removeData(2); + + // Verify remaining data + EXPECT_EQ(array.getData(1).data, std::vector({1, 2, 3})); + EXPECT_EQ(array.getData(3).data, std::vector({7, 8, 9})); + } + + TEST(ComponentArrayTest, EdgeCaseEmptyRemoval) + { + ComponentArray array; + + array.insertData(1, {42}); + array.removeData(1); + + // Array should be empty now + EXPECT_EQ(array.size(), 0); + EXPECT_FALSE(array.hasComponent(1)); + + // Removing again should throw + EXPECT_THROW(array.removeData(1), ComponentNotFound); + } + + TEST(ComponentArrayTest, HandleOverflow) + { + ComponentArray array; + + for (Entity entity = 0; entity < MAX_ENTITIES; ++entity) + { + EXPECT_NO_THROW(array.insertData(entity, {static_cast(entity)})); + } + + EXPECT_THROW(array.insertData(MAX_ENTITIES, {999}), OutOfRange); + } + TEST_F(ComponentManagerTest, RegisterAndRetrieveComponentType) { componentManager->registerComponent(); @@ -166,16 +545,128 @@ namespace nexo::ecs { EXPECT_EQ(result->get().value, 42); } - TEST(ComponentArrayTest, HandleOverflow) + TEST_F(ComponentManagerTest, GetComponentArray) { - ComponentArray array; + componentManager->registerComponent(); - for (Entity entity = 0; entity < MAX_ENTITIES; ++entity) - { - EXPECT_NO_THROW(array.insertData(entity, {static_cast(entity)})); - } + // Get component array + auto array = componentManager->getComponentArray(); + EXPECT_NE(array, nullptr); - EXPECT_THROW(array.insertData(MAX_ENTITIES, {999}), OutOfRange); + // Get const component array + const auto& constManager = *componentManager; + auto constArray = constManager.getComponentArray(); + EXPECT_NE(constArray, nullptr); + } + + TEST_F(ComponentManagerTest, GetComponentArrayUnregistered) + { + // Get component array for unregistered component + EXPECT_THROW(static_cast(componentManager->getComponentArray()), ComponentNotRegistered); + + // Get const component array for unregistered component + const auto& constManager = *componentManager; + EXPECT_THROW(static_cast(constManager.getComponentArray()), ComponentNotRegistered); + } + + TEST_F(ComponentManagerTest, MoveConstructor) + { + // Setup original manager + componentManager->registerComponent(); + Entity entity = 1; + componentManager->addComponent(entity, TestComponent{42}); + + // Move construct new manager + ComponentManager movedManager(std::move(*componentManager)); + + // Should be able to access component through moved manager + EXPECT_EQ(movedManager.getComponent(entity).value, 42); + } + + TEST_F(ComponentManagerTest, MoveAssignment) + { + // Setup original manager + componentManager->registerComponent(); + Entity entity = 1; + componentManager->addComponent(entity, TestComponent{42}); + + // Create second manager and move-assign + ComponentManager secondManager; + secondManager = std::move(*componentManager); + + // Should be able to access component through second manager + EXPECT_EQ(secondManager.getComponent(entity).value, 42); + } + + TEST_F(ComponentManagerTest, ComponentTypeIDs) + { + componentManager->registerComponent(); + componentManager->registerComponent(); + + // Different component types should have different IDs + ComponentType testType = componentManager->getComponentType(); + ComponentType anotherType = componentManager->getComponentType(); + + EXPECT_NE(testType, anotherType); + } + + TEST_F(ComponentManagerTest, EntityDestroyedNoComponents) + { + // EntityDestroyed with no components registered should not crash + EXPECT_NO_THROW(componentManager->entityDestroyed(1)); + } + + TEST_F(ComponentManagerTest, EntityDestroyedEmptyComponentArray) + { + componentManager->registerComponent(); + + // EntityDestroyed with registered but empty component array should not crash + EXPECT_NO_THROW(componentManager->entityDestroyed(1)); + } + + TEST_F(ComponentManagerTest, ComplexComponentStorage) + { + struct ComplexComponent { + std::string name; + std::vector data; + }; + + componentManager->registerComponent(); + Entity entity = 1; + + ComplexComponent complex{"test", {1, 2, 3}}; + componentManager->addComponent(entity, complex); + + ComplexComponent& retrieved = componentManager->getComponent(entity); + EXPECT_EQ(retrieved.name, "test"); + EXPECT_EQ(retrieved.data, std::vector({1, 2, 3})); + } + + TEST_F(ComponentManagerTest, GetComponentArrayDirect) + { + componentManager->registerComponent(); + Entity entity = 1; + + componentManager->addComponent(entity, TestComponent{42}); + + // Get component array directly and use it + auto array = componentManager->getComponentArray(); + TestComponent& component = array->getData(entity); + EXPECT_EQ(component.value, 42); + } + + TEST_F(ComponentManagerTest, ComponentModification) + { + componentManager->registerComponent(); + Entity entity = 1; + + componentManager->addComponent(entity, TestComponent{42}); + + // Modify component directly + componentManager->getComponent(entity).value = 100; + + // Verify modification took effect + EXPECT_EQ(componentManager->getComponent(entity).value, 100); } TEST_F(ComponentManagerTest, RegisterDuplicateComponent) From 75620d9d116f7d2c9f760d7e58c56ed934cbe364 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 18:22:18 +0900 Subject: [PATCH 010/450] style(ecs-opti): rename SparSetUnordered to SparseSet --- engine/src/ecs/System.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/ecs/System.hpp b/engine/src/ecs/System.hpp index f62c95f3c..3b39497c6 100644 --- a/engine/src/ecs/System.hpp +++ b/engine/src/ecs/System.hpp @@ -26,7 +26,7 @@ namespace nexo::ecs { namespace nexo::ecs { - class SparseSetUnordered { + class SparseSet { public: void insert(Entity entity) { @@ -86,7 +86,7 @@ namespace nexo::ecs { */ class System { public: - SparseSetUnordered entities; + SparseSet entities; static std::shared_ptr coord; }; From ed0227c16cc0a853c1362ff2cc76088031e2d916 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 18:22:32 +0900 Subject: [PATCH 011/450] tests(ecs-opti): add tests for the sparse set --- tests/ecs/CMakeLists.txt | 1 + tests/ecs/SparseSet.test.cpp | 272 +++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 tests/ecs/SparseSet.test.cpp diff --git a/tests/ecs/CMakeLists.txt b/tests/ecs/CMakeLists.txt index 195dedfd8..44bef01a5 100644 --- a/tests/ecs/CMakeLists.txt +++ b/tests/ecs/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(ecs_tests ${BASEDIR}/System.test.cpp ${BASEDIR}/Coordinator.test.cpp ${BASEDIR}/Exceptions.test.cpp + ${BASEDIR}/SparseSet.test.cpp ) # Find glm and add its include directories diff --git a/tests/ecs/SparseSet.test.cpp b/tests/ecs/SparseSet.test.cpp new file mode 100644 index 000000000..f87f7fb8b --- /dev/null +++ b/tests/ecs/SparseSet.test.cpp @@ -0,0 +1,272 @@ +//// SparseSet.test.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: 28/03/2025 +// Description: Test file for the sparse set class +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "ecs/System.hpp" + +namespace nexo::ecs { + class SparseSetTest : public ::testing::Test { + protected: + SparseSet sparseSet; + }; + + TEST_F(SparseSetTest, InitiallyEmpty) { + EXPECT_TRUE(sparseSet.empty()); + EXPECT_TRUE(sparseSet.getDense().empty()); + EXPECT_EQ(sparseSet.begin(), sparseSet.end()); + } + + TEST_F(SparseSetTest, InsertSingleEntity) { + Entity entity = 42; + sparseSet.insert(entity); + + EXPECT_FALSE(sparseSet.empty()); + EXPECT_TRUE(sparseSet.contains(entity)); + EXPECT_EQ(sparseSet.getDense().size(), 1); + EXPECT_EQ(sparseSet.getDense()[0], entity); + } + + TEST_F(SparseSetTest, InsertMultipleEntities) { + Entity entity1 = 42; + Entity entity2 = 100; + Entity entity3 = 255; + + sparseSet.insert(entity1); + sparseSet.insert(entity2); + sparseSet.insert(entity3); + + EXPECT_FALSE(sparseSet.empty()); + EXPECT_TRUE(sparseSet.contains(entity1)); + EXPECT_TRUE(sparseSet.contains(entity2)); + EXPECT_TRUE(sparseSet.contains(entity3)); + EXPECT_EQ(sparseSet.getDense().size(), 3); + + // Check that all entities are in the dense array + auto& dense = sparseSet.getDense(); + EXPECT_THAT(dense, ::testing::UnorderedElementsAre(entity1, entity2, entity3)); + } + + TEST_F(SparseSetTest, EraseSingleEntity) { + Entity entity = 42; + sparseSet.insert(entity); + EXPECT_TRUE(sparseSet.contains(entity)); + + sparseSet.erase(entity); + EXPECT_FALSE(sparseSet.contains(entity)); + EXPECT_TRUE(sparseSet.empty()); + } + + TEST_F(SparseSetTest, EraseMultipleEntities) { + // Insert multiple entities + Entity entity1 = 42; + Entity entity2 = 100; + Entity entity3 = 255; + + sparseSet.insert(entity1); + sparseSet.insert(entity2); + sparseSet.insert(entity3); + + // Erase middle entity + sparseSet.erase(entity2); + EXPECT_FALSE(sparseSet.contains(entity2)); + EXPECT_TRUE(sparseSet.contains(entity1)); + EXPECT_TRUE(sparseSet.contains(entity3)); + EXPECT_EQ(sparseSet.getDense().size(), 2); + + // Erase first entity + sparseSet.erase(entity1); + EXPECT_FALSE(sparseSet.contains(entity1)); + EXPECT_TRUE(sparseSet.contains(entity3)); + EXPECT_EQ(sparseSet.getDense().size(), 1); + + // Erase last entity + sparseSet.erase(entity3); + EXPECT_FALSE(sparseSet.contains(entity3)); + EXPECT_TRUE(sparseSet.empty()); + } + + TEST_F(SparseSetTest, SwapAndPopMechanism) { + // Insert entities in order to test the swap-and-pop mechanism + Entity entity1 = 1; + Entity entity2 = 2; + Entity entity3 = 3; + + sparseSet.insert(entity1); + sparseSet.insert(entity2); + sparseSet.insert(entity3); + + // Erase the first entity + sparseSet.erase(entity1); + + // The last entity should now be in the first entity's position + auto& dense = sparseSet.getDense(); + EXPECT_EQ(dense.size(), 2); + + // Since order isn't guaranteed with unordered_map, we just check that both + // remaining entities are in the dense array + EXPECT_THAT(dense, ::testing::UnorderedElementsAre(entity2, entity3)); + EXPECT_TRUE(sparseSet.contains(entity2)); + EXPECT_TRUE(sparseSet.contains(entity3)); + } + + TEST_F(SparseSetTest, InsertDuplicateEntity) { + Entity entity = 42; + + sparseSet.insert(entity); + // Try to insert the same entity again + sparseSet.insert(entity); + + // Should only be one entity in the set + EXPECT_EQ(sparseSet.getDense().size(), 1); + EXPECT_TRUE(sparseSet.contains(entity)); + } + + TEST_F(SparseSetTest, EraseNonExistentEntity) { + Entity entity = 42; + + // Try to erase an entity that doesn't exist + sparseSet.erase(entity); + + // Should still be empty + EXPECT_TRUE(sparseSet.empty()); + + // Insert and then erase + sparseSet.insert(entity); + sparseSet.erase(entity); + + // Try to erase again + sparseSet.erase(entity); + + // Should still be empty + EXPECT_TRUE(sparseSet.empty()); + } + + TEST_F(SparseSetTest, IteratorFunctionality) { + Entity entity1 = 42; + Entity entity2 = 100; + + sparseSet.insert(entity1); + sparseSet.insert(entity2); + + // Test begin/end iteration + std::vector entities; + for (auto it = sparseSet.begin(); it != sparseSet.end(); ++it) { + entities.push_back(*it); + } + + EXPECT_EQ(entities.size(), 2); + EXPECT_THAT(entities, ::testing::UnorderedElementsAre(entity1, entity2)); + + // Test range-based for loop + entities.clear(); + for (Entity e : sparseSet) { + entities.push_back(e); + } + + EXPECT_EQ(entities.size(), 2); + EXPECT_THAT(entities, ::testing::UnorderedElementsAre(entity1, entity2)); + } + + TEST_F(SparseSetTest, GetDenseArray) { + Entity entity1 = 42; + Entity entity2 = 100; + + sparseSet.insert(entity1); + sparseSet.insert(entity2); + + const auto& dense = sparseSet.getDense(); + EXPECT_EQ(dense.size(), 2); + EXPECT_THAT(dense, ::testing::UnorderedElementsAre(entity1, entity2)); + } + + TEST_F(SparseSetTest, LargeNumberOfEntities) { + // Insert a large number of entities + const size_t numEntities = 1000; + + for (Entity i = 0; i < numEntities; ++i) { + sparseSet.insert(i); + } + + EXPECT_EQ(sparseSet.getDense().size(), numEntities); + + // Check each entity is contained + for (Entity i = 0; i < numEntities; ++i) { + EXPECT_TRUE(sparseSet.contains(i)); + } + + // Remove all entities in reverse order + for (Entity i = numEntities - 1; i != static_cast(-1); --i) { + sparseSet.erase(i); + } + + EXPECT_TRUE(sparseSet.empty()); + } + + TEST_F(SparseSetTest, MixedOperations) { + // Perform a mix of insert and erase operations + sparseSet.insert(1); + sparseSet.insert(2); + sparseSet.insert(3); + sparseSet.erase(2); + sparseSet.insert(4); + sparseSet.erase(1); + sparseSet.insert(5); + sparseSet.erase(3); + sparseSet.insert(6); + + EXPECT_EQ(sparseSet.getDense().size(), 3); + EXPECT_TRUE(sparseSet.contains(4)); + EXPECT_TRUE(sparseSet.contains(5)); + EXPECT_TRUE(sparseSet.contains(6)); + EXPECT_FALSE(sparseSet.contains(1)); + EXPECT_FALSE(sparseSet.contains(2)); + EXPECT_FALSE(sparseSet.contains(3)); + } + + TEST_F(SparseSetTest, EmptyAfterEraseAll) { + // Insert and then erase all + sparseSet.insert(1); + sparseSet.insert(2); + sparseSet.insert(3); + + sparseSet.erase(1); + sparseSet.erase(2); + sparseSet.erase(3); + + EXPECT_TRUE(sparseSet.empty()); + EXPECT_EQ(sparseSet.begin(), sparseSet.end()); + } + + TEST_F(SparseSetTest, NonSequentialEntities) { + // Test with large, scattered entity IDs + Entity entity1 = 1000000; + Entity entity2 = 2000000; + Entity entity3 = 3000000; + + sparseSet.insert(entity1); + sparseSet.insert(entity2); + sparseSet.insert(entity3); + + EXPECT_TRUE(sparseSet.contains(entity1)); + EXPECT_TRUE(sparseSet.contains(entity2)); + EXPECT_TRUE(sparseSet.contains(entity3)); + + sparseSet.erase(entity2); + + EXPECT_TRUE(sparseSet.contains(entity1)); + EXPECT_FALSE(sparseSet.contains(entity2)); + EXPECT_TRUE(sparseSet.contains(entity3)); + } +} From 83e903ddfd0ab3d1e37b7e393489d05f0191ce1c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 18:29:26 +0900 Subject: [PATCH 012/450] tests(ecs-opti): add more tests to the coordinator --- tests/ecs/Coordinator.test.cpp | 379 +++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) diff --git a/tests/ecs/Coordinator.test.cpp b/tests/ecs/Coordinator.test.cpp index 8d5bdafe7..6c695c871 100644 --- a/tests/ecs/Coordinator.test.cpp +++ b/tests/ecs/Coordinator.test.cpp @@ -15,6 +15,7 @@ #include #include #include "ecs/Coordinator.hpp" +#include "Components.hpp" #include "ecs/SingletonComponent.hpp" #include "ecs/Signature.hpp" #include "ecs/System.hpp" @@ -296,4 +297,382 @@ namespace nexo::ecs { EXPECT_EQ(retrieved.value, 200); } } + + TEST_F(CoordinatorTest, GetComponentArray) { + // Register a component type + coordinator->registerComponent(); + + // Get the component array + auto componentArray = coordinator->getComponentArray(); + ASSERT_NE(componentArray, nullptr); + + // Test basic functionality of the component array + Entity entity = coordinator->createEntity(); + componentArray->insertData(entity, TestComponent{42}); + EXPECT_EQ(componentArray->getData(entity).data, 42); + + // Verify the component was actually added to the entity + EXPECT_EQ(coordinator->getComponent(entity).data, 42); + } + + TEST_F(CoordinatorTest, GetAllComponentTypes) { + // Register multiple component types + coordinator->registerComponent(); + + Entity entity = coordinator->createEntity(); + + // Initially, the entity has no components + auto types = coordinator->getAllComponentTypes(entity); + EXPECT_TRUE(types.empty()); + + // Add components and check types + coordinator->addComponent(entity, TestComponent{42}); + coordinator->addComponent(entity, ComponentA{10}); + coordinator->addComponent(entity, ComponentB{3.14f}); + + types = coordinator->getAllComponentTypes(entity); + EXPECT_EQ(types.size(), 3); + + // Verify correct types are returned (specific order may vary) + bool hasTestComponent = false; + bool hasComponentA = false; + bool hasComponentB = false; + + for (const auto& typeIndex : types) { + if (typeIndex == std::type_index(typeid(TestComponent))) hasTestComponent = true; + else if (typeIndex == std::type_index(typeid(ComponentA))) hasComponentA = true; + else if (typeIndex == std::type_index(typeid(ComponentB))) hasComponentB = true; + } + + EXPECT_TRUE(hasTestComponent); + EXPECT_TRUE(hasComponentA); + EXPECT_TRUE(hasComponentB); + + // Remove a component and verify types are updated + coordinator->removeComponent(entity); + types = coordinator->getAllComponentTypes(entity); + EXPECT_EQ(types.size(), 2); + + hasTestComponent = false; + hasComponentA = false; + hasComponentB = false; + + for (const auto& typeIndex : types) { + if (typeIndex == std::type_index(typeid(TestComponent))) hasTestComponent = true; + else if (typeIndex == std::type_index(typeid(ComponentA))) hasComponentA = true; + else if (typeIndex == std::type_index(typeid(ComponentB))) hasComponentB = true; + } + + EXPECT_FALSE(hasTestComponent); + EXPECT_TRUE(hasComponentA); + EXPECT_TRUE(hasComponentB); + } + + TEST_F(CoordinatorTest, EntityHasComponent) { + coordinator->registerComponent(); + coordinator->registerComponent(); + + Entity entity = coordinator->createEntity(); + + // Initially, the entity has no components + EXPECT_FALSE(coordinator->entityHasComponent(entity)); + EXPECT_FALSE(coordinator->entityHasComponent(entity)); + + // Add a component + coordinator->addComponent(entity, TestComponent{42}); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + EXPECT_FALSE(coordinator->entityHasComponent(entity)); + + // Add another component + coordinator->addComponent(entity, ComponentA{10}); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + + // Remove a component + coordinator->removeComponent(entity); + EXPECT_FALSE(coordinator->entityHasComponent(entity)); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + } + + TEST_F(CoordinatorTest, GetAllComponentsComprehensive) { + coordinator->registerComponent(); + coordinator->registerComponent(); + coordinator->registerComponent(); + + Entity entity = coordinator->createEntity(); + + // Initially, the entity has no components + auto components = coordinator->getAllComponents(entity); + EXPECT_TRUE(components.empty()); + + // Add components + coordinator->addComponent(entity, TestComponent{42}); + coordinator->addComponent(entity, ComponentA{10}); + coordinator->addComponent(entity, ComponentB{3.14f}); + + components = coordinator->getAllComponents(entity); + EXPECT_EQ(components.size(), 3); + + // Verify components are correctly returned + bool hasTestComponent = false; + bool hasComponentA = false; + bool hasComponentB = false; + + for (const auto& [type, value] : components) { + if (type == std::type_index(typeid(TestComponent))) { + hasTestComponent = true; + EXPECT_EQ(std::any_cast(value).data, 42); + } else if (type == std::type_index(typeid(ComponentA))) { + hasComponentA = true; + EXPECT_EQ(std::any_cast(value).value, 10); + } else if (type == std::type_index(typeid(ComponentB))) { + hasComponentB = true; + EXPECT_FLOAT_EQ(std::any_cast(value).data, 3.14f); + } + } + + EXPECT_TRUE(hasTestComponent); + EXPECT_TRUE(hasComponentA); + EXPECT_TRUE(hasComponentB); + } + + TEST_F(CoordinatorTest, ModifyComponent) { + coordinator->registerComponent(); + + Entity entity = coordinator->createEntity(); + coordinator->addComponent(entity, TestComponent{42}); + + // Modify component through getComponent + coordinator->getComponent(entity).data = 100; + EXPECT_EQ(coordinator->getComponent(entity).data, 100); + + // Modify component through a reference + TestComponent& component = coordinator->getComponent(entity); + component.data = 200; + EXPECT_EQ(coordinator->getComponent(entity).data, 200); + } + + // This test verifies the updateSystemEntities functionality indirectly + TEST_F(CoordinatorTest, SystemEntityUpdatesWhenComponentsChange) { + // Setup system with a specific signature + auto system = coordinator->registerSystem(); + coordinator->registerComponent(); + coordinator->registerComponent(); + + Signature signature; + signature.set(coordinator->getComponentType()); + signature.set(coordinator->getComponentType()); + coordinator->setSystemSignature(signature); + + // Create entity and add only TestComponent - should not be in system + Entity entity = coordinator->createEntity(); + coordinator->addComponent(entity, TestComponent{42}); + EXPECT_FALSE(system->entities.contains(entity)); + + // Add ComponentA - now it should match the system signature + coordinator->addComponent(entity, ComponentA{10}); + EXPECT_TRUE(system->entities.contains(entity)); + + // Remove ComponentA - should no longer match + coordinator->removeComponent(entity); + EXPECT_FALSE(system->entities.contains(entity)); + } + + TEST_F(CoordinatorTest, MultipleSystemsWithDifferentSignatures) { + // Create two different systems with different signatures + auto systemA = coordinator->registerSystem(); + + class AnotherMockSystem : public System { + public: + MOCK_METHOD(void, update, (), (const)); + }; + + auto systemB = coordinator->registerSystem(); + + coordinator->registerComponent(); + coordinator->registerComponent(); + coordinator->registerComponent(); + + // Set different signatures for each system + Signature signatureA; + signatureA.set(coordinator->getComponentType()); + signatureA.set(coordinator->getComponentType()); + coordinator->setSystemSignature(signatureA); + + Signature signatureB; + signatureB.set(coordinator->getComponentType()); + coordinator->setSystemSignature(signatureB); + + // Create entities with different combinations of components + Entity entity1 = coordinator->createEntity(); + Entity entity2 = coordinator->createEntity(); + Entity entity3 = coordinator->createEntity(); + + // Entity1 has TestComponent and ComponentA - should only be in systemA + coordinator->addComponent(entity1, TestComponent{1}); + coordinator->addComponent(entity1, ComponentA{1}); + + // Entity2 has ComponentB - should only be in systemB + coordinator->addComponent(entity2, ComponentB{2.0f}); + + // Entity3 has all components - should be in both systems + coordinator->addComponent(entity3, TestComponent{3}); + coordinator->addComponent(entity3, ComponentA{3}); + coordinator->addComponent(entity3, ComponentB{3.0f}); + + // Check each system has the expected entities + EXPECT_TRUE(systemA->entities.contains(entity1)); + EXPECT_FALSE(systemA->entities.contains(entity2)); + EXPECT_TRUE(systemA->entities.contains(entity3)); + + EXPECT_FALSE(systemB->entities.contains(entity1)); + EXPECT_TRUE(systemB->entities.contains(entity2)); + EXPECT_TRUE(systemB->entities.contains(entity3)); + } + + TEST_F(CoordinatorTest, EntityCreationMaximumLimit) { + // This test should be adjusted based on your MAX_ENTITIES value + // For simplicity, let's create a reasonable number of entities + const int numEntities = MAX_ENTITIES; // Adjust based on your MAX_ENTITIES + + std::vector entities; + coordinator->registerComponent(); + + // Create entities up to the limit + for (int i = 0; i < numEntities; ++i) { + EXPECT_NO_THROW({ + Entity entity = coordinator->createEntity(); + entities.push_back(entity); + }); + } + + // Verify all entities were created correctly + for (const auto& entity : entities) { + EXPECT_NO_THROW(coordinator->addComponent(entity, TestComponent{1})); + } + + // Delete half the entities + for (size_t i = 0; i < entities.size() / 2; ++i) { + EXPECT_NO_THROW(coordinator->destroyEntity(entities[i])); + } + + // Create more entities to replace the deleted ones + for (size_t i = 0; i < entities.size() / 2; ++i) { + EXPECT_NO_THROW({ + Entity entity = coordinator->createEntity(); + // Could store these new entities if needed + }); + } + } + + TEST_F(CoordinatorTest, ComponentArrayIntegration) { + coordinator->registerComponent(); + + // Get component array directly + auto componentArray = coordinator->getComponentArray(); + + // Create entity through coordinator + Entity entity = coordinator->createEntity(); + + // Add component directly through component array + componentArray->insertData(entity, TestComponent{42}); + + // Verify we can access it through the coordinator + EXPECT_EQ(coordinator->getComponent(entity).data, 42); + + // Modify through component array + componentArray->getData(entity).data = 100; + + // Verify change is visible through coordinator + EXPECT_EQ(coordinator->getComponent(entity).data, 100); + + // Remove through component array + componentArray->removeData(entity); + + // Verify it's gone + EXPECT_THROW(coordinator->getComponent(entity), ComponentNotFound); + } + + TEST_F(CoordinatorTest, SingletonComponentEdgeCases) { + // Try getting a non-existent singleton + EXPECT_THROW(coordinator->getSingletonComponent(), SingletonComponentNotRegistered); + + // Register, remove, and try to get + coordinator->registerSingletonComponent(42); + coordinator->removeSingletonComponent(); + EXPECT_THROW(coordinator->getSingletonComponent(), SingletonComponentNotRegistered); + + // Remove a non-existent singleton + EXPECT_THROW(coordinator->removeSingletonComponent(), SingletonComponentNotRegistered); + + // Register multiple singleton types + struct AnotherSingletonComponent { + float value; + }; + + coordinator->registerSingletonComponent(42); + coordinator->registerSingletonComponent(3.14f); + + EXPECT_EQ(coordinator->getSingletonComponent().value, 42); + EXPECT_FLOAT_EQ(coordinator->getSingletonComponent().value, 3.14f); + } + + TEST_F(CoordinatorTest, ComplexEntityComponentInteractions) { + // Register multiple components + struct Position { float x, y; }; + struct Velocity { float dx, dy; }; + struct Renderable { int spriteId; }; + struct Health { int current, max; }; + + coordinator->registerComponent(); + coordinator->registerComponent(); + coordinator->registerComponent(); + coordinator->registerComponent(); + + // Create a complex entity with multiple components + Entity entity = coordinator->createEntity(); + coordinator->addComponent(entity, Position{10.0f, 20.0f}); + coordinator->addComponent(entity, Velocity{1.0f, 2.0f}); + coordinator->addComponent(entity, Renderable{5}); + coordinator->addComponent(entity, Health{100, 100}); + + // Verify all components are accessible + EXPECT_FLOAT_EQ(coordinator->getComponent(entity).x, 10.0f); + EXPECT_FLOAT_EQ(coordinator->getComponent(entity).y, 20.0f); + EXPECT_FLOAT_EQ(coordinator->getComponent(entity).dx, 1.0f); + EXPECT_FLOAT_EQ(coordinator->getComponent(entity).dy, 2.0f); + EXPECT_EQ(coordinator->getComponent(entity).spriteId, 5); + EXPECT_EQ(coordinator->getComponent(entity).current, 100); + EXPECT_EQ(coordinator->getComponent(entity).max, 100); + + // Test entityHasComponent for all components + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + + // Get all component types and verify + auto types = coordinator->getAllComponentTypes(entity); + EXPECT_EQ(types.size(), 4); + + // Modify components + coordinator->getComponent(entity).x += coordinator->getComponent(entity).dx; + coordinator->getComponent(entity).y += coordinator->getComponent(entity).dy; + coordinator->getComponent(entity).current -= 10; + + // Verify modifications + EXPECT_FLOAT_EQ(coordinator->getComponent(entity).x, 11.0f); + EXPECT_FLOAT_EQ(coordinator->getComponent(entity).y, 22.0f); + EXPECT_EQ(coordinator->getComponent(entity).current, 90); + + // Remove components and verify + coordinator->removeComponent(entity); + EXPECT_FALSE(coordinator->entityHasComponent(entity)); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + EXPECT_TRUE(coordinator->entityHasComponent(entity)); + + types = coordinator->getAllComponentTypes(entity); + EXPECT_EQ(types.size(), 3); + } } From a8ddcf1955fc84cb58635f697ceed62ca612db2e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 18:32:17 +0900 Subject: [PATCH 013/450] tests(ecs-opti): add tests for the singleton component class and manager --- tests/ecs/CMakeLists.txt | 1 + tests/ecs/SingletonComponent.test.cpp | 277 ++++++++++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 tests/ecs/SingletonComponent.test.cpp diff --git a/tests/ecs/CMakeLists.txt b/tests/ecs/CMakeLists.txt index 44bef01a5..bfd103288 100644 --- a/tests/ecs/CMakeLists.txt +++ b/tests/ecs/CMakeLists.txt @@ -44,6 +44,7 @@ add_executable(ecs_tests ${BASEDIR}/Coordinator.test.cpp ${BASEDIR}/Exceptions.test.cpp ${BASEDIR}/SparseSet.test.cpp + ${BASEDIR}/SingletonComponent.test.cpp ) # Find glm and add its include directories diff --git a/tests/ecs/SingletonComponent.test.cpp b/tests/ecs/SingletonComponent.test.cpp new file mode 100644 index 000000000..a13ba38f6 --- /dev/null +++ b/tests/ecs/SingletonComponent.test.cpp @@ -0,0 +1,277 @@ +//// SingletonComponent.test.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: 28/03/2025 +// Description: Test file for the singleton component classes +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "ecs/SingletonComponent.hpp" + +namespace nexo::ecs { + +// Test components +struct TestComponent { + int value; + std::string name; + + TestComponent(int v) : value(v), name("default") {} + TestComponent(int v, std::string n) : value(v), name(n) {} +}; + +struct ComplexComponent { + std::vector data; + bool flag; + + ComplexComponent() : flag(false) {} + ComplexComponent(const std::vector& d, bool f) : data(d), flag(f) {} +}; + +class SingletonComponentTest : public ::testing::Test {}; + +class SingletonComponentManagerTest : public ::testing::Test { +protected: + void SetUp() override { + manager = std::make_unique(); + } + + std::unique_ptr manager; +}; + +// SingletonComponent Tests + +TEST_F(SingletonComponentTest, ConstructWithSingleArgument) { + SingletonComponent component(42); + EXPECT_EQ(component.getInstance().value, 42); + EXPECT_EQ(component.getInstance().name, "default"); +} + +TEST_F(SingletonComponentTest, ConstructWithMultipleArguments) { + SingletonComponent component(42, "test"); + EXPECT_EQ(component.getInstance().value, 42); + EXPECT_EQ(component.getInstance().name, "test"); +} + +TEST_F(SingletonComponentTest, GetInstanceReturnsReference) { + SingletonComponent component(42); + TestComponent& instance = component.getInstance(); + + // Modify through reference + instance.value = 100; + instance.name = "modified"; + + // Verify changes + EXPECT_EQ(component.getInstance().value, 100); + EXPECT_EQ(component.getInstance().name, "modified"); +} + +TEST_F(SingletonComponentTest, ComplexComponentConstruction) { + std::vector testData = {1, 2, 3, 4, 5}; + SingletonComponent component(testData, true); + + EXPECT_EQ(component.getInstance().data, testData); + EXPECT_TRUE(component.getInstance().flag); +} + +TEST_F(SingletonComponentTest, DefaultConstructible) { + SingletonComponent component; + EXPECT_TRUE(component.getInstance().data.empty()); + EXPECT_FALSE(component.getInstance().flag); +} + +// SingletonComponentManager Tests + +TEST_F(SingletonComponentManagerTest, RegisterAndGetSingletonComponent) { + manager->registerSingletonComponent(42); + + TestComponent& component = manager->getSingletonComponent(); + EXPECT_EQ(component.value, 42); + EXPECT_EQ(component.name, "default"); +} + +TEST_F(SingletonComponentManagerTest, RegisterWithMultipleArguments) { + manager->registerSingletonComponent(42, "test"); + + TestComponent& component = manager->getSingletonComponent(); + EXPECT_EQ(component.value, 42); + EXPECT_EQ(component.name, "test"); +} + +TEST_F(SingletonComponentManagerTest, GetNonexistentComponent) { + EXPECT_THROW(manager->getSingletonComponent(), SingletonComponentNotRegistered); +} + +TEST_F(SingletonComponentManagerTest, UnregisterComponent) { + // Register + manager->registerSingletonComponent(42); + EXPECT_NO_THROW(manager->getSingletonComponent()); + + // Unregister + manager->unregisterSingletonComponent(); + + // Should now throw + EXPECT_THROW(manager->getSingletonComponent(), SingletonComponentNotRegistered); +} + +TEST_F(SingletonComponentManagerTest, UnregisterNonexistentComponent) { + EXPECT_THROW(manager->unregisterSingletonComponent(), SingletonComponentNotRegistered); +} + +TEST_F(SingletonComponentManagerTest, RegisterSameComponentTwice) { + // First registration + manager->registerSingletonComponent(42); + + // Second registration should log warning but not throw + EXPECT_NO_THROW(manager->registerSingletonComponent(100)); + + // Should still have the original value + TestComponent& component = manager->getSingletonComponent(); + EXPECT_EQ(component.value, 42); +} + +TEST_F(SingletonComponentManagerTest, RegisterMultipleComponentTypes) { + manager->registerSingletonComponent(42); + manager->registerSingletonComponent(std::vector{1, 2, 3}, true); + + // Both should be retrievable + TestComponent& comp1 = manager->getSingletonComponent(); + ComplexComponent& comp2 = manager->getSingletonComponent(); + + EXPECT_EQ(comp1.value, 42); + EXPECT_EQ(comp2.data, std::vector({1, 2, 3})); + EXPECT_TRUE(comp2.flag); +} + +TEST_F(SingletonComponentManagerTest, ModifyComponent) { + manager->registerSingletonComponent(42); + + // Get and modify + TestComponent& component = manager->getSingletonComponent(); + component.value = 100; + component.name = "modified"; + + // Should reflect changes when retrieved again + TestComponent& retrieved = manager->getSingletonComponent(); + EXPECT_EQ(retrieved.value, 100); + EXPECT_EQ(retrieved.name, "modified"); +} + +TEST_F(SingletonComponentManagerTest, RegisterAfterUnregister) { + // Register + manager->registerSingletonComponent(42); + + // Unregister + manager->unregisterSingletonComponent(); + + // Register again with different value + manager->registerSingletonComponent(100); + + // Should have new value + TestComponent& component = manager->getSingletonComponent(); + EXPECT_EQ(component.value, 100); +} + +TEST_F(SingletonComponentManagerTest, ComplexComponentCycle) { + // Register complex component + std::vector originalData = {1, 2, 3}; + manager->registerSingletonComponent(originalData, true); + + // Get and modify + ComplexComponent& comp = manager->getSingletonComponent(); + comp.data.push_back(4); + comp.flag = false; + + // Verify modifications + ComplexComponent& modified = manager->getSingletonComponent(); + std::vector expectedData = {1, 2, 3, 4}; + EXPECT_EQ(modified.data, expectedData); + EXPECT_FALSE(modified.flag); + + // Unregister + manager->unregisterSingletonComponent(); + + // Register new instance + std::vector newData = {5, 6, 7}; + manager->registerSingletonComponent(newData, true); + + // Verify new instance + ComplexComponent& newComp = manager->getSingletonComponent(); + EXPECT_EQ(newComp.data, newData); + EXPECT_TRUE(newComp.flag); +} + +// Test edge cases + +TEST_F(SingletonComponentManagerTest, EmptyComponent) { + struct EmptyComponent {}; + + manager->registerSingletonComponent(); + EXPECT_NO_THROW(manager->getSingletonComponent()); +} + +TEST_F(SingletonComponentManagerTest, NonDefaultConstructibleComponent) { + struct NonDefaultComponent { + int value; + explicit NonDefaultComponent(int v) : value(v) {} + NonDefaultComponent() = delete; + }; + + manager->registerSingletonComponent(42); + EXPECT_EQ(manager->getSingletonComponent().value, 42); +} + +TEST_F(SingletonComponentManagerTest, MoveOnlyComponent) { + struct MoveOnlyComponent { + std::unique_ptr ptr; + + explicit MoveOnlyComponent(int v) : ptr(std::make_unique(v)) {} + MoveOnlyComponent(const MoveOnlyComponent&) = delete; + MoveOnlyComponent& operator=(const MoveOnlyComponent&) = delete; + MoveOnlyComponent(MoveOnlyComponent&&) = default; + MoveOnlyComponent& operator=(MoveOnlyComponent&&) = default; + }; + + manager->registerSingletonComponent(42); + EXPECT_EQ(*manager->getSingletonComponent().ptr, 42); +} + +TEST_F(SingletonComponentManagerTest, MultipleUnregistrations) { + manager->registerSingletonComponent(42); + manager->unregisterSingletonComponent(); + + // Second unregistration should throw + EXPECT_THROW(manager->unregisterSingletonComponent(), SingletonComponentNotRegistered); +} + +class InheritanceTestBase { +public: + virtual ~InheritanceTestBase() = default; + int baseValue = 0; +}; + +class InheritanceTestDerived : public InheritanceTestBase { +public: + InheritanceTestDerived(int base, int derived) { + baseValue = base; + derivedValue = derived; + } + int derivedValue = 0; +}; + +TEST_F(SingletonComponentManagerTest, InheritedComponent) { + manager->registerSingletonComponent(10, 20); + + InheritanceTestDerived& component = manager->getSingletonComponent(); + EXPECT_EQ(component.baseValue, 10); + EXPECT_EQ(component.derivedValue, 20); +} + +} From c4b9c573ba8a60e2ebfff65cad6b31447b8f84d2 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 18:45:47 +0900 Subject: [PATCH 014/450] refactor(ecs-opti): entity manager now properly recycle the ids to keep them dense --- engine/src/ecs/Entity.cpp | 6 +++--- engine/src/ecs/Entity.hpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/engine/src/ecs/Entity.cpp b/engine/src/ecs/Entity.cpp index 136044dc2..c54c52dcf 100644 --- a/engine/src/ecs/Entity.cpp +++ b/engine/src/ecs/Entity.cpp @@ -19,7 +19,7 @@ namespace nexo::ecs { EntityManager::EntityManager() { for (Entity entity = 0; entity < MAX_ENTITIES; ++entity) - m_availableEntities.push(entity); + m_availableEntities.push_back(entity); } Entity EntityManager::createEntity() @@ -28,7 +28,7 @@ namespace nexo::ecs { THROW_EXCEPTION(TooManyEntities); const Entity id = m_availableEntities.front(); - m_availableEntities.pop(); + m_availableEntities.pop_front(); ++m_livingEntityCount; return id; @@ -41,7 +41,7 @@ namespace nexo::ecs { m_signatures[entity].reset(); - m_availableEntities.push(entity); + m_availableEntities.push_front(entity); if (m_livingEntityCount > 0) --m_livingEntityCount; } diff --git a/engine/src/ecs/Entity.hpp b/engine/src/ecs/Entity.hpp index 944c58efd..6b398e48e 100644 --- a/engine/src/ecs/Entity.hpp +++ b/engine/src/ecs/Entity.hpp @@ -14,7 +14,7 @@ #pragma once -#include +#include #include #include "Signature.hpp" @@ -74,7 +74,7 @@ namespace nexo::ecs { std::uint32_t getLivingEntityCount() const; private: - std::queue m_availableEntities{}; + std::deque m_availableEntities{}; std::array m_signatures{}; From 001eac0707ff1afe930675845f1c2ecc56593083 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 28 Mar 2025 18:46:03 +0900 Subject: [PATCH 015/450] tests(ecs-opti): add tests for the entity manager --- tests/ecs/Entity.test.cpp | 151 +++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 3 deletions(-) diff --git a/tests/ecs/Entity.test.cpp b/tests/ecs/Entity.test.cpp index ad96dcb21..65f5cb617 100644 --- a/tests/ecs/Entity.test.cpp +++ b/tests/ecs/Entity.test.cpp @@ -15,6 +15,7 @@ #include #include #include "ecs/Entity.hpp" +#include "Components.hpp" namespace nexo::ecs { class EntityManagerTest : public ::testing::Test { @@ -34,7 +35,7 @@ namespace nexo::ecs { // Recreate the entity, it should use the ID in front Entity reusedEntity = entityManager->createEntity(); - EXPECT_EQ(reusedEntity, 1); + EXPECT_EQ(reusedEntity, 0); } TEST_F(EntityManagerTest, TooManyEntities) { @@ -66,7 +67,7 @@ namespace nexo::ecs { TEST_F(EntityManagerTest, GetSignatureOutOfRange) { Entity invalidEntity = MAX_ENTITIES; // Invalid entity ID - EXPECT_THROW(entityManager->getSignature(invalidEntity), OutOfRange); + EXPECT_THROW(static_cast(entityManager->getSignature(invalidEntity)), OutOfRange); } TEST_F(EntityManagerTest, DestroyEntityResetsSignature) { @@ -103,4 +104,148 @@ namespace nexo::ecs { EXPECT_NO_THROW(entityManager->createEntity()); } -} \ No newline at end of file + + TEST_F(EntityManagerTest, GetLivingEntityCount) { + // Initially should be 0 + EXPECT_EQ(entityManager->getLivingEntityCount(), 0); + + // Create one entity + Entity entity1 = entityManager->createEntity(); + EXPECT_EQ(entityManager->getLivingEntityCount(), 1); + + // Create another entity + Entity entity2 = entityManager->createEntity(); + EXPECT_EQ(entityManager->getLivingEntityCount(), 2); + + // Destroy one entity + entityManager->destroyEntity(entity1); + EXPECT_EQ(entityManager->getLivingEntityCount(), 1); + + // Destroy the other entity + entityManager->destroyEntity(entity2); + EXPECT_EQ(entityManager->getLivingEntityCount(), 0); + } + + TEST_F(EntityManagerTest, EntityIdSequence) { + // Create multiple entities and check the ID sequence + Entity entity1 = entityManager->createEntity(); + Entity entity2 = entityManager->createEntity(); + Entity entity3 = entityManager->createEntity(); + + // IDs should be sequential starting from 0 + EXPECT_EQ(entity1, 0); + EXPECT_EQ(entity2, 1); + EXPECT_EQ(entity3, 2); + } + + TEST_F(EntityManagerTest, ComplexEntityRecycling) { + // Create several entities + std::vector entities; + for (int i = 0; i < 5; ++i) { + entities.push_back(entityManager->createEntity()); + } + + // Destroy entities in a non-sequential order + entityManager->destroyEntity(entities[2]); // Destroy entity with ID 2 + entityManager->destroyEntity(entities[0]); // Destroy entity with ID 0 + + // Create new entities and check the recycling order + // Entities should be reused in FIFO order (first destroyed first reused) + Entity newEntity1 = entityManager->createEntity(); + Entity newEntity2 = entityManager->createEntity(); + + // The first available entity should be recycled first, + // which depends on the order they were added to the availability queue + EXPECT_EQ(newEntity1, 0); // ID 2 should be recycled first (FIFO) + EXPECT_EQ(newEntity2, 2); // ID 0 should be recycled next + + // Destroy all entities and check if count reaches zero + entityManager->destroyEntity(entities[1]); + entityManager->destroyEntity(entities[3]); + entityManager->destroyEntity(entities[4]); + entityManager->destroyEntity(newEntity1); + entityManager->destroyEntity(newEntity2); + + EXPECT_EQ(entityManager->getLivingEntityCount(), 0); + } + + TEST_F(EntityManagerTest, EntityCountTracking) { + // Test edge cases in entity count tracking + + // Create MAX_ENTITIES entities + std::vector entities; + for (Entity i = 0; i < MAX_ENTITIES; ++i) { + entities.push_back(entityManager->createEntity()); + } + + EXPECT_EQ(entityManager->getLivingEntityCount(), MAX_ENTITIES); + + // Destroy half of the entities + for (Entity i = 0; i < MAX_ENTITIES / 2; ++i) { + entityManager->destroyEntity(entities[i]); + } + + if (MAX_ENTITIES % 2 == 0) { + EXPECT_EQ(entityManager->getLivingEntityCount(), MAX_ENTITIES / 2); + } else { + EXPECT_EQ(entityManager->getLivingEntityCount(), MAX_ENTITIES / 2 + 1); + } + + // Create half of them again + for (Entity i = 0; i < MAX_ENTITIES / 2; ++i) { + entityManager->createEntity(); + } + + EXPECT_EQ(entityManager->getLivingEntityCount(), MAX_ENTITIES); + } + + TEST_F(EntityManagerTest, DestroyAlreadyDestroyedEntity) { + // Create an entity + Entity entity = entityManager->createEntity(); + EXPECT_EQ(entityManager->getLivingEntityCount(), 1); + + // Destroy it once + entityManager->destroyEntity(entity); + EXPECT_EQ(entityManager->getLivingEntityCount(), 0); + + // Destroy it again - this should work without errors + // and not affect the living entity count (which should remain 0) + EXPECT_NO_THROW(entityManager->destroyEntity(entity)); + EXPECT_EQ(entityManager->getLivingEntityCount(), 0); + } + + TEST_F(EntityManagerTest, SignatureManipulation) { + // Create an entity + Entity entity = entityManager->createEntity(); + + // Initial signature should be empty + EXPECT_TRUE(entityManager->getSignature(entity).none()); + + // Set some bits + Signature signature; + signature.set(0); + signature.set(5); + signature.set(10); + entityManager->setSignature(entity, signature); + + // Check that signature was properly set + Signature retrieved = entityManager->getSignature(entity); + EXPECT_TRUE(retrieved.test(0)); + EXPECT_TRUE(retrieved.test(5)); + EXPECT_TRUE(retrieved.test(10)); + EXPECT_FALSE(retrieved.test(1)); + + // Modify signature + Signature newSignature = retrieved; + newSignature.reset(0); + newSignature.set(3); + entityManager->setSignature(entity, newSignature); + + // Check updated signature + retrieved = entityManager->getSignature(entity); + EXPECT_FALSE(retrieved.test(0)); + EXPECT_TRUE(retrieved.test(3)); + EXPECT_TRUE(retrieved.test(5)); + EXPECT_TRUE(retrieved.test(10)); + } +} From 8df705dbc8361ebdc7ffca5854e68206a3076cb5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:48:11 +0900 Subject: [PATCH 016/450] refactor(ecs-opti): move the component array to its own file + feat: add group system --- engine/src/ecs/ComponentArray.hpp | 405 ++++++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 engine/src/ecs/ComponentArray.hpp diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp new file mode 100644 index 000000000..c00c15b23 --- /dev/null +++ b/engine/src/ecs/ComponentArray.hpp @@ -0,0 +1,405 @@ +//// ComponentArray.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: Mehdy MORVAN +// Date: 01/04/2025 +// Description: Header file for the component array +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "Definitions.hpp" +#include "ECSExceptions.hpp" +#include "Exception.hpp" +#include "Logger.hpp" + +#include + +namespace nexo::ecs { + /** + * @class IComponentArray + * @brief Base interface for all component array types. + * + * Provides the common interface that all concrete component arrays must implement, + * allowing type-erased storage and manipulation of components. + */ + class IComponentArray { + public: + virtual ~IComponentArray() = default; + + /** + * @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 + */ + virtual bool hasComponent(Entity entity) const = 0; + + /** + * @brief Handles cleanup when an entity is destroyed + * @param entity The entity being destroyed + */ + virtual void entityDestroyed(Entity entity) = 0; + }; + + /** + * @class ComponentArray + * @brief Stores and manages components of a specific type T. + * + * Implements a sparse-dense storage pattern for efficient component storage and retrieval. + * Components are stored contiguously for cache-friendly access, while maintaining + * O(1) lookups via entity IDs. + * + * @tparam T The component type stored in this array + * @tparam capacity Initial capacity for the sparse array + */ + template= 1)>> + class alignas(64) ComponentArray final : public IComponentArray { + public: + /** + * @brief Type alias for the component type + */ + using component_type = T; + + /** + * @brief Constructs a new component array with initial capacity + * + * Initializes the sparse array with capacity elements, and reserves space + * for the dense arrays. + */ + ComponentArray() + { + m_sparse.resize(capacity, INVALID_ENTITY); + m_dense.reserve(capacity); + m_componentArray.reserve(capacity); + } + + /** + * @brief Inserts a new component for the given entity. + * + * If the entity already has a component, the operation is silently ignored. + * + * @param entity The entity to add the component to + * @param component The component instance to add + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + */ + void insertData(Entity entity, T component) + { + if (entity >= MAX_ENTITIES) + THROW_EXCEPTION(OutOfRange, entity); + + // Ensure m_sparse can hold this entity index. + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "Entity {} already has component: {}", entity, typeid(T).name()); + return; + } + + const size_t newIndex = m_size; + m_sparse[entity] = newIndex; + m_dense.push_back(entity); + m_componentArray.push_back(std::move(component)); + ++m_size; + } + + /** + * @brief Removes the component for the given entity. + * + * If the entity is grouped (i.e. within the first m_groupSize entries), + * then adjusts the group boundary appropriately. + * + * @param entity The entity to remove the component from + * @throws ComponentNotFound if the entity doesn't have the component + */ + void removeData(Entity entity) + { + if (entity >= m_sparse.size() || !hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t indexToRemove = m_sparse[entity]; + + // If the entity is part of the group, remove it from the group first. + if (indexToRemove < m_groupSize) { + // Swap with the last grouped element if not already at the end. + size_t groupLastIndex = m_groupSize - 1; + 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[groupLastIndex]] = groupLastIndex; + } + --m_groupSize; + // Now update indexToRemove to reflect the new position. + indexToRemove = groupLastIndex; + } + + // Standard removal from the overall array: + const size_t lastIndex = m_size - 1; + if (indexToRemove != lastIndex) { + std::swap(m_componentArray[indexToRemove], m_componentArray[lastIndex]); + std::swap(m_dense[indexToRemove], m_dense[lastIndex]); + m_sparse[m_dense[indexToRemove]] = indexToRemove; + } + m_sparse[entity] = INVALID_ENTITY; + m_componentArray.pop_back(); + m_dense.pop_back(); + --m_size; + + shrinkIfNeeded(); + } + + /** + * @brief Retrieves a component for the given entity + * + * @param entity The entity to get the component from + * @return Reference to the component + * @throws ComponentNotFound if the entity doesn't have the component + */ + [[nodiscard]] T& getData(Entity entity) + { + if (entity >= m_sparse.size() || !hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + return m_componentArray[m_sparse[entity]]; + } + + /** + * @brief Retrieves a component for the given entity (const version) + * + * @param entity The entity to get the component from + * @return Const reference to the component + * @throws ComponentNotFound if the entity doesn't have the component + */ + [[nodiscard]] const T& getData(Entity entity) const + { + if (entity >= m_sparse.size() || !hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + return m_componentArray[m_sparse[entity]]; + } + + /** + * @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 + { + return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); + } + + /** + * @brief Removes the component from an entity when it's destroyed + * + * @param entity The entity being destroyed + */ + void entityDestroyed(Entity entity) override + { + if (hasComponent(entity)) + removeData(entity); + } + + /** + * @brief Gets the total number of components in the array + * + * @return The number of active components + */ + [[nodiscard]] size_t size() const + { + return m_size; + } + + /** + * @brief Gets the entity at the given index in the dense array + * + * @param index The index to look up + * @return The entity at that index + * @throws OutOfRange if the index is invalid + */ + [[nodiscard]] Entity getEntityAtIndex(size_t index) const + { + if (index >= m_size) + THROW_EXCEPTION(OutOfRange, index); + return m_dense[index]; + } + + /** + * @brief Gets a span view of all components + * + * @return Span of component data + */ + [[nodiscard]] std::span rawData() + { + return std::span(m_componentArray.data(), m_size); + } + + /** + * @brief Gets a const span view of all components + * + * @return Const span of component data + */ + [[nodiscard]] std::span rawData() const + { + return std::span(m_componentArray.data(), m_size); + } + + /** + * @brief Gets a const span view of all entities with this component + * + * @return Const span of entity IDs + */ + [[nodiscard]] std::span entities() const + { + return std::span(m_dense.data(), m_size); + } + + /** + * @brief Moves the component for the given entity into the group region. + * + * This operation swaps the entity with the one at m_groupSize + * and then increments the group pointer. + * + * @param entity The entity to add to the group + * @throws ComponentNotFound if the entity doesn't have the component + */ + void addToGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + 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]); + // Update sparse mapping for both swapped entities. + m_sparse[m_dense[index]] = index; + m_sparse[m_dense[m_groupSize]] = m_groupSize; + } + ++m_groupSize; + } + + /** + * @brief Moves the component for the given entity out of the group region. + * + * This operation swaps the entity with the last grouped element + * and then decrements the group pointer. + * + * @param entity The entity to remove from the group + * @throws ComponentNotFound if the entity doesn't have the component + */ + void removeFromGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + if (index >= m_groupSize) + return; + // Decrement group size first; the entity to remove is at index. + --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[m_groupSize]] = m_groupSize; + } + } + + /** + * @brief Forces a component to be set at a specific index (internal use only) + * + * Used primarily during group reordering operations. + * + * @param index The index to set the component at + * @param entity The entity to associate with this component + * @param component The component data to set + * @throws OutOfRange if the index is invalid + */ + void forceSetComponentAt(size_t index, Entity entity, T component) + { + if (index >= m_size) + THROW_EXCEPTION(OutOfRange, index); + + // Update the sparse mapping + m_sparse[entity] = index; + + // Update the dense array + m_dense[index] = entity; + + // Update the component + m_componentArray[index] = std::move(component); + } + + /** + * @brief Gets the number of entities in the group region + * + * @return Number of grouped entities + */ + [[nodiscard]] size_t groupSize() const + { + return m_groupSize; + } + + private: + // Dense storage for components. + std::vector m_componentArray; + // Sparse mapping: maps entity ID to index in the dense arrays. + std::vector m_sparse; + // Dense storage for entity IDs. + std::vector m_dense; + // Current number of active components. + size_t m_size = 0; + // The first m_groupSize entries in m_dense/m_componentArray are considered "grouped". + 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) + { + if (entity >= m_sparse.size()) { + size_t newSize = m_sparse.size(); + if (newSize == 0) + newSize = capacity; + while (entity >= newSize) + newSize *= 2; + m_sparse.resize(newSize, INVALID_ENTITY); + } + } + + /** + * @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() + { + auto shrinkVector = [this](auto& vec) { + size_t currentCapacity = vec.capacity(); + if (m_size < currentCapacity / 2) { + size_t newCapacity = static_cast(m_size * 1.25); + if (newCapacity < capacity) + newCapacity = capacity; + std::vector::type::value_type> newVec; + newVec.reserve(newCapacity); + newVec.assign(vec.begin(), vec.begin() + m_size); + vec.swap(newVec); + } + }; + + shrinkVector(m_componentArray); + shrinkVector(m_dense); + } + }; +} From 2b2411256a2f9dc9d4aa82f4b3c9a3f5cae655ed Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:48:30 +0900 Subject: [PATCH 017/450] feat: add group creation/retrieval in the component manager --- engine/src/ecs/Components.hpp | 629 ++++++++++++++++++++++------------ 1 file changed, 406 insertions(+), 223 deletions(-) diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 9f2851d2a..b28e20cb5 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -1,256 +1,208 @@ -/////////////////////////////////////////////////////////////////////////////// -// -// 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: 08/11/2024 -// Description: Header file for the components and component manager class for the ecs -// -/////////////////////////////////////////////////////////////////////////////// - #pragma once #include #include -#include -#include +#include #include +#include #include -#include -#include #include +#include +#include +#include +#include +#include #include "ECSExceptions.hpp" +#include "Definitions.hpp" #include "Exception.hpp" #include "Logger.hpp" +#include "ComponentArray.hpp" +#include "Group.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 { }; - // Entity definitions - using Entity = std::uint32_t; - constexpr Entity MAX_ENTITIES = 80191; - constexpr Entity INVALID_ENTITY = std::numeric_limits::max(); - - // Component type definitions - using ComponentType = std::uint8_t; - constexpr ComponentType MAX_COMPONENT_TYPE = 32; - - // Global counter shared across all component types. - inline ComponentType globalComponentCounter = 0; - - template - ComponentType getUniqueComponentTypeID() - { - // This static variable is instantiated once per type T, - // but it will be assigned a unique value from the shared global counter. - static const ComponentType id = []() { - assert(globalComponentCounter < MAX_COMPONENT_TYPE && "Maximum number of component types exceeded"); - return globalComponentCounter++; - }(); - return id; - } - - template - ComponentType getComponentTypeID() + /** + * @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 getUniqueComponentTypeID>(); + return {}; } /** - * @class IComponentArray + * @brief Type alias for a tuple of owned component arrays * - * @brief Interface for a component array in the ECS framework. + * Used internally by the Group class to store owned components. + * + * @tparam Owned The owned component types */ - class IComponentArray { - public: - virtual ~IComponentArray() = default; - virtual void entityDestroyed(Entity entity) = 0; - }; + template + using OwnedComponents = std::tuple>...>; /** - * @class ComponentArray + * @brief Type alias for a tuple of non-owned component arrays * - * @brief Component storage using sparse set pattern for O(1) lookup. + * Used internally by the Group class to store references to non-owned components. * - * Dense storage is maintained in dynamically growing vectors. The initial capacity is reserved - * at 1024 elements by default and then the vectors grow as needed. When many components are removed, - * the dense storage is shrunk to 125% of the current size. - * - * @tparam T - The type of component to store. + * @tparam NonOwned The non-owned component types */ - template= 1)>> - class alignas(64) ComponentArray final : public IComponentArray { - public: - ComponentArray() - { - m_sparse.resize(capacity, INVALID_ENTITY); - m_dense.reserve(capacity); - m_componentArray.reserve(capacity); - } - - void insertData(Entity entity, T component) - { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); - - // Ensure m_sparse can hold this entity index. - ensureSparseCapacity(entity); - - if (hasComponent(entity)) { - LOG(NEXO_WARN, "ECS::ComponentArray::insertData: Component already added to entity {}", entity); - return; - } - - const size_t newIndex = m_size; - m_sparse[entity] = newIndex; - m_dense.push_back(entity); - m_componentArray.push_back(std::move(component)); - ++m_size; - } - - void removeData(Entity entity) - { - if (entity >= m_sparse.size() || !hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - - const size_t indexToRemove = m_sparse[entity]; - const size_t lastIndex = m_size - 1; - const Entity lastEntity = m_dense[lastIndex]; - - // Swap and pop. - m_componentArray[indexToRemove] = std::move(m_componentArray[lastIndex]); - m_dense[indexToRemove] = lastEntity; - m_sparse[lastEntity] = indexToRemove; - - m_sparse[entity] = INVALID_ENTITY; - m_componentArray.pop_back(); - m_dense.pop_back(); - --m_size; + template + using NonOwnedComponents = std::tuple>...>; - shrinkIfNeeded(); - } - - [[nodiscard]] T& getData(Entity entity) - { - if (entity >= m_sparse.size() || !hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - return m_componentArray[m_sparse[entity]]; - } - - [[nodiscard]] const T& getData(Entity entity) const - { - if (entity >= m_sparse.size() || !hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - return m_componentArray[m_sparse[entity]]; - } - - [[nodiscard]] bool hasComponent(Entity entity) const - { - return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); - } - - void entityDestroyed(Entity entity) override - { - if (hasComponent(entity)) - removeData(entity); - } - - [[nodiscard]] size_t size() const - { - return m_size; - } - - [[nodiscard]] Entity getEntityAtIndex(size_t index) const - { - if (index >= m_size) - THROW_EXCEPTION(OutOfRange, index); - return m_dense[index]; - } - - [[nodiscard]] std::span rawData() - { - return std::span(m_componentArray.data(), m_size); - } - - [[nodiscard]] std::span rawData() const - { - return std::span(m_componentArray.data(), m_size); - } - - [[nodiscard]] std::span entities() const - { - return std::span(m_dense.data(), m_size); - } + /** + * @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>; - private: - // Dense storage for components. - std::vector m_componentArray; - // Sparse mapping: maps entity ID to index in the dense arrays. - std::vector m_sparse; - // Dense storage for entity IDs. - std::vector m_dense; - // Current number of active components. - size_t m_size = 0; - - // Ensures m_sparse is large enough to index 'entity'. - void ensureSparseCapacity(Entity entity) - { - if (entity >= m_sparse.size()) { - size_t newSize = m_sparse.size(); - if (newSize == 0) - newSize = capacity; - while (entity >= newSize) - newSize *= 2; - m_sparse.resize(newSize, INVALID_ENTITY); - } - } + /** + * @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 + */ + 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(); + } + }; +} - // Shrinks the dense vectors if m_size is less than half of their capacity. - // New capacity is set to 125% of m_size, with a minimum of the capacity given. - void shrinkIfNeeded() - { - auto shrinkVector = [this](auto& vec) { - size_t currentCapacity = vec.capacity(); - if (m_size < currentCapacity / 2) { - size_t newCapacity = static_cast(m_size * 1.25); - if (newCapacity < capacity) - newCapacity = capacity; - std::vector::type::value_type> newVec; - newVec.reserve(newCapacity); - newVec.assign(vec.begin(), vec.begin() + m_size); - vec.swap(newVec); - } - }; - - shrinkVector(m_componentArray); - shrinkVector(m_dense); - } +// 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 { + // Create a combined hash of both signatures + size_t h1 = std::hash()(key.ownedSignature); + size_t h2 = std::hash()(key.nonOwnedSignature); + return h1 ^ (h2 << 1); // Combine hashes with a bit shift + } }; +} + +namespace nexo::ecs { /** * @class ComponentManager * - * @brief Manages all component arrays and provides access to them. + * @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; - // Non-copyable + /** + * @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; - // Movable + /** + * @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 system + * @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 */ @@ -260,7 +212,7 @@ namespace nexo::ecs { const ComponentType typeID = getComponentTypeID(); if (m_componentArrays[typeID] != nullptr) { - LOG(NEXO_WARN, "ECS::ComponentManager::registerComponent: Component already registered"); + LOG(NEXO_WARN, "Component already registered"); return; } @@ -268,19 +220,19 @@ namespace nexo::ecs { } /** - * @brief Gets the component type ID + * @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) { + if (m_componentArrays[typeID] == nullptr) THROW_EXCEPTION(ComponentNotRegistered); - } return typeID; } @@ -288,42 +240,70 @@ namespace nexo::ecs { /** * @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 to add + * @param component The component instance to add + * @param signature The entity's current component signature */ template - void addComponent(Entity entity, T component) + void addComponent(Entity entity, T component, Signature signature) { getComponentArray()->insertData(entity, std::move(component)); + + for (auto& [key, group] : m_groupRegistry) + { + if ((signature & group->allSignature()) == group->allSignature()) + group->addToGroup(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 */ template - void removeComponent(Entity entity) + void removeComponent(Entity entity, Signature previousSignature) { + for (auto& [key, group] : m_groupRegistry) + { + if ((previousSignature & group->allSignature()) == group->allSignature()) + group->removeFromGroup(entity); + } getComponentArray()->removeData(entity); } /** - * @brief Tries to remove a component from an 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 * @return true if the component was removed, false if it didn't exist */ template - bool tryRemoveComponent(Entity entity) + bool tryRemoveComponent(Entity entity, Signature previousSignature) { auto componentArray = getComponentArray(); if (!componentArray->hasComponent(entity)) return false; + for (auto& [key, group] : m_groupRegistry) + { + if ((previousSignature & group->allSignature()) == group->allSignature()) + group->removeFromGroup(entity); + } componentArray->removeData(entity); return true; } @@ -334,6 +314,7 @@ namespace nexo::ecs { * @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) @@ -342,10 +323,11 @@ namespace nexo::ecs { } /** - * @brief Gets a component array + * @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() @@ -360,10 +342,11 @@ namespace nexo::ecs { } /** - * @brief Gets a component array (const version) + * @brief Gets the component array for a specific component type (const version) * * @tparam T The component type - * @return Shared pointer to the component array + * @return Const shared pointer to the component array + * @throws ComponentNotRegistered if the component type is not registered */ template [[nodiscard]] std::shared_ptr> getComponentArray() const @@ -378,11 +361,11 @@ namespace nexo::ecs { } /** - * @brief Tries to get a component from an entity + * @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 + * @return Optional reference to the component, or nullopt if not found */ template [[nodiscard]] std::optional> tryGetComponent(Entity entity) @@ -397,12 +380,212 @@ namespace nexo::ecs { /** * @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 */ void entityDestroyed(Entity entity); + /** + * @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) + { + // Generate a unique key for this group type combination + 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, existingGroup] : 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); + } + } + } + } + + // No conflicts found, create the new group + 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) + { + GroupKey groupKey = generateGroupKey(nonOwned); + + 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]] bool hasCommonOwnedComponents(const GroupKey& key1, const GroupKey& key2) const + { + // If there's any bit that's set in both owned signatures, they share components + return (key1.ownedSignature & key2.ownedSignature).any(); + } + private: - // Use fixed-size array for O(1) component type lookup instead of std::unordered_map + /** + * @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& nonOwned) + { + 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); + 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); + + // Add to group if valid + if (valid) { + std::apply([&](auto&&... arrays) { + ((arrays->addToGroup(e)), ...); + }, ownedArrays); + } + } + + // Create and return the group + 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 + void setNonOwnedBits(Signature& signature, const get_t& nonOwned) + { + ((signature.set(getComponentTypeID())), ...); + } }; } From 212f615568005369383fdbdfe194828f44161e3b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:48:46 +0900 Subject: [PATCH 018/450] feat: add group creation/retrieval in the coordinator class --- engine/src/ecs/Coordinator.hpp | 36 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 86602df5a..1acc77989 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -25,6 +25,7 @@ #include "Logger.hpp" namespace nexo::ecs { + /** * @class Coordinator * @@ -94,11 +95,11 @@ namespace nexo::ecs { template void addComponent(const Entity entity, T component) { - m_componentManager->addComponent(entity, component); + auto signature = m_entityManager->getSignature(entity); + auto oldSignature = signature; + signature.set(m_componentManager->getComponentType(), true); + m_componentManager->addComponent(entity, component, signature); - auto signature = m_entityManager->getSignature(entity); - auto oldSignature = signature; - signature.set(m_componentManager->getComponentType(), true); m_entityManager->setSignature(entity, signature); m_systemManager->entitySignatureChanged(entity, oldSignature, signature); @@ -112,11 +113,12 @@ namespace nexo::ecs { template void removeComponent(const Entity entity) const { - m_componentManager->removeComponent(entity); + auto signature = m_entityManager->getSignature(entity); + auto oldSignature = signature; + signature.set(m_componentManager->getComponentType(), false); + m_componentManager->removeComponent(entity, oldSignature); + - auto signature = m_entityManager->getSignature(entity); - auto oldSignature = signature; - signature.set(m_componentManager->getComponentType(), false); m_entityManager->setSignature(entity, signature); m_systemManager->entitySignatureChanged(entity, oldSignature, signature); @@ -133,10 +135,10 @@ namespace nexo::ecs { template void tryRemoveComponent(const Entity entity) const { - if (m_componentManager->tryRemoveComponent(entity)) + auto signature = m_entityManager->getSignature(entity); + if (m_componentManager->tryRemoveComponent(entity, signature)) { - auto signature = m_entityManager->getSignature(entity); - auto oldSignature = signature; + auto oldSignature = signature; signature.set(m_componentManager->getComponentType(), false); m_entityManager->setSignature(entity, signature); @@ -264,6 +266,18 @@ namespace nexo::ecs { return m_systemManager->registerSystem(); } + template + auto registerGroup(const auto & nonOwned) + { + return m_componentManager->registerGroup(nonOwned); + } + + template + auto getGroup(const auto& nonOwned) + { + return m_componentManager->getGroup(nonOwned); + } + /** * @brief Sets the signature for a system, defining which entities it will process. * From 1a4741aa6130abf4d44b32bef2a333d07244ac27 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:49:13 +0900 Subject: [PATCH 019/450] refactor(ecs-opti): move the common constants and funcs in its own header for easier access --- engine/src/ecs/Definitions.hpp | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 engine/src/ecs/Definitions.hpp diff --git a/engine/src/ecs/Definitions.hpp b/engine/src/ecs/Definitions.hpp new file mode 100644 index 000000000..7d3b0d47e --- /dev/null +++ b/engine/src/ecs/Definitions.hpp @@ -0,0 +1,58 @@ +//// Definitions.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: Mehdy MORVAN +// Date: 31/03/2025 +// Description: Header file containing type definitions and constants +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include + +namespace nexo::ecs { + // Entity type definition + using Entity = std::uint32_t; + constexpr Entity MAX_ENTITIES = 80000; + constexpr Entity INVALID_ENTITY = std::numeric_limits::max(); + + // Component type definitions + using ComponentType = std::uint8_t; + constexpr ComponentType MAX_COMPONENT_TYPE = 32; + + inline ComponentType globalComponentCounter = 0; + + template + ComponentType getUniqueComponentTypeID() + { + // This static variable is instantiated once per type T, + // but it will be assigned a unique value from the shared global counter. + static const ComponentType id = []() { + assert(globalComponentCounter < MAX_COMPONENT_TYPE && "Maximum number of component types exceeded"); + return globalComponentCounter++; + }(); + return id; + } + + template + ComponentType getComponentTypeID() + { + return getUniqueComponentTypeID>(); + } + + // Group type definition + using GroupType = std::uint8_t; + constexpr GroupType MAX_GROUP_NUMBER = 32; + + using Signature = std::bitset; + +} From e0475bab03c34e6254a12b02f66296aa70fd3065 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:49:51 +0900 Subject: [PATCH 020/450] feat(ecs-opti): add InternalError, OverlappingGroups and GroupNotFound exceptions --- engine/src/ecs/ECSExceptions.hpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/engine/src/ecs/ECSExceptions.hpp b/engine/src/ecs/ECSExceptions.hpp index 3cbc8bb31..c14644f09 100644 --- a/engine/src/ecs/ECSExceptions.hpp +++ b/engine/src/ecs/ECSExceptions.hpp @@ -14,12 +14,19 @@ #pragma once #include "Exception.hpp" +#include "Definitions.hpp" #include #include namespace nexo::ecs { - using Entity = std::uint32_t; + + class InternalError final : public Exception { + public: + explicit InternalError(const std::string& message, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Internal error: {}", message), loc) {} + }; class ComponentNotFound final : public Exception { public: @@ -28,6 +35,18 @@ namespace nexo::ecs { : 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) {} + }; + + 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) {} + }; + class ComponentNotRegistered final : public Exception { public: explicit ComponentNotRegistered(const std::source_location loc = std::source_location::current()) @@ -49,7 +68,7 @@ namespace nexo::ecs { class TooManyEntities final : public Exception { public: explicit TooManyEntities(const std::source_location loc = std::source_location::current()) - : Exception("Too many living entities, max is 8191", loc) {} + : Exception(std::format("Too many living entities, max is {}", MAX_ENTITIES), loc) {} }; class OutOfRange final : public Exception { From 6d361d8703f57fce0b7a4855b4d53b2b9ec7c56e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:50:21 +0900 Subject: [PATCH 021/450] chore(ecs-opti): now use the new Definitions.hpp file --- engine/src/ecs/Entity.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/ecs/Entity.hpp b/engine/src/ecs/Entity.hpp index 6b398e48e..2b98d8beb 100644 --- a/engine/src/ecs/Entity.hpp +++ b/engine/src/ecs/Entity.hpp @@ -17,7 +17,7 @@ #include #include -#include "Signature.hpp" +#include "Definitions.hpp" namespace nexo::ecs { From 717c5c79b678727c69eb6083e8e4ee3cd81d66ed Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:50:38 +0900 Subject: [PATCH 022/450] feat: add group and partition system --- engine/src/ecs/Group.hpp | 892 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 892 insertions(+) create mode 100644 engine/src/ecs/Group.hpp diff --git a/engine/src/ecs/Group.hpp b/engine/src/ecs/Group.hpp new file mode 100644 index 000000000..1b6bec59f --- /dev/null +++ b/engine/src/ecs/Group.hpp @@ -0,0 +1,892 @@ +//// Group.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: Mehdy MORVAN +// Date: 01/04/2025 +// Description: Header file for the ECS groups +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "Definitions.hpp" +#include "ComponentArray.hpp" +#include "Exception.hpp" + +#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. + */ + 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 : 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 + Group(OwnedTuple ownedArrays, NonOwnedTuple nonOwnedArrays) + : m_ownedArrays(std::move(ownedArrays)) + , m_nonOwnedArrays(std::move(nonOwnedArrays)) + { + m_ownedSignature = std::apply([](auto&&... arrays) -> Signature { + Signature signature; + ((signature.set(getComponentTypeID::component_type>())), ...); + return signature; + }, m_ownedArrays); + + Signature nonOwnedSignature = std::apply([](auto&&... arrays) -> Signature { + 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. + */ + std::size_t size() const { return std::get<0>(m_ownedArrays)->groupSize(); } + + /** + * @brief Checks if sorting has been invalidated. + * + * @return true If sorting is invalidated. + * @return false Otherwise. + */ + bool sortingInvalidated() const { return m_sortingInvalidated; } + + /** + * @brief Returns the signature for owned components. + * + * @return const Signature& Owned signature. + */ + const Signature& ownedSignature() const { return m_ownedSignature; } + + /** + * @brief Returns the overall signature for both owned and non‑owned components. + * + * @return const Signature& Combined signature. + */ + 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; // Not used + + /** + * @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 + { + 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; + } + + /** + * @brief Inequality operator. + * + * @param other Another iterator. + * @return true If iterators are not equal. + * @return false Otherwise. + */ + bool operator!=(const GroupIterator& other) const { return !(*this == other); } + + private: + const Group* m_view; ///< Pointer to the group. + std::size_t m_index; ///< Current index in the group. + }; + + GroupIterator begin() const { return GroupIterator(this, 0); } + GroupIterator end() const { return GroupIterator(this, size()); } + GroupIterator begin() { return GroupIterator(this, 0); } + 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 + { + using FirstOwned = std::tuple_element_t<0, OwnedTuple>; + auto firstArray = std::get<0>(m_ownedArrays); + 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, size_t count, Func func) const + { + auto firstArray = std::get<0>(m_ownedArrays); + 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. + */ + std::span entities() const + { + auto 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 + return compArray->rawData().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 + return compArray->rawData().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 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) + { + if (!m_sortingInvalidated) + return; + // Get the appropriate component array + 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"); + + // Get the driving array (always the first owned array) + auto drivingArray = std::get<0>(m_ownedArrays); + auto groupSize = drivingArray->groupSize(); + + // Create a vector of entities to sort + 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::sort(entities.begin(), entities.end(), + [&](Entity a, Entity b) { + const auto& compA = compArray->getData(a); + const auto& compB = compArray->getData(b); + if (ascending) + return extractor(compA) < extractor(compB); + else + return extractor(compA) > extractor(compB); + }); + + // Reorder only the owned component arrays according to the new order + 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. + */ + 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()); + + // Create entity-based key extractor that uses the component extractor + EntityKeyExtractor entityKeyExtractor = [this, keyExtractor](Entity e) { + // Get the component and extract the key + if constexpr (tuple_contains_component_v) { + auto compArray = getOwnedImpl(); + return keyExtractor(compArray->getData(e)); + } else if constexpr (tuple_contains_component_v) { + auto compArray = getNonOwnedImpl(); + return keyExtractor(compArray->getData(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()) { + // Create a new partition storage + auto storage = std::make_unique>(this, keyExtractor); + auto* storagePtr = storage.get(); + m_partitionStorageMap[partitionId] = std::move(storage); + + // Rebuild the partitions + storagePtr->rebuild(); + + return PartitionView(this, storagePtr->getPartitions()); + } + + // Get the existing storage and cast to the right type + auto* storage = static_cast*>(it->second.get()); + + // Rebuild if needed + if (storage->isDirty()) + storage->rebuild(); + + // Return a view to the partitions + 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 destructor. + virtual ~IPartitionStorage() = default; + /** + * @brief Checks if the partition storage is dirty (needs rebuilding). + * + * @return true If dirty. + * @return false Otherwise. + */ + 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 : 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)), m_isDirty(true) {} + + 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); + auto groupSize = drivingArray->groupSize(); + + // Skip if no entities + if (groupSize == 0) { + m_partitions.clear(); + m_isDirty = false; + return; + } + + // Collect all entity keys + 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); + } + + // Create the partitions + 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(); + } + + // Reorder the entities according to partitions + 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; ///< 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) + { + // Implementation to rearrange entities in all owned arrays + 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) + { + size_t groupSize = array->groupSize(); + if (newOrder.size() != groupSize) + THROW_EXCEPTION(InternalError, "New order size doesn't match group size"); + + // Create a temporary storage for components + using CompType = typename std::decay_t::component_type; + std::vector tempComponents; + tempComponents.reserve(groupSize); + + // Copy components in the new order + for (Entity e : newOrder) + tempComponents.push_back(array->getData(e)); + + // Update the sparse-to-dense mapping and components + 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 + { + // Retrieve the entity from the first owned array. + auto 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->getData(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 + auto getNonOwnedImpl() const -> std::shared_ptr> + { + if constexpr (I < std::tuple_size_v) { + 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(); + } else + static_assert(I < std::tuple_size_v, "Component type not found in group non‑owned arrays"); + } + + /** + * @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 + auto getOwnedImpl() const -> std::shared_ptr> + { + if constexpr (I < std::tuple_size_v) { + 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(); + } else + static_assert(I < std::tuple_size_v, "Component type not found in group owned arrays"); + } + + /** + * @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)->getData(e))..., + (std::get(m_nonOwnedArrays)->getData(e))...); + } + + // 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. + std::unordered_map> m_partitionStorageMap; ///< Map storing partition data by ID. + + }; +} From dd47eb0169dc2504e7ec5a5f19308b4f10232ef3 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:50:54 +0900 Subject: [PATCH 023/450] chore(ecs-opti): now use the new Definitions.hpp file --- engine/src/ecs/System.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/src/ecs/System.hpp b/engine/src/ecs/System.hpp index 3b39497c6..1d707df13 100644 --- a/engine/src/ecs/System.hpp +++ b/engine/src/ecs/System.hpp @@ -18,7 +18,9 @@ #include #include -#include "Signature.hpp" +#include "Definitions.hpp" +#include "Logger.hpp" +#include "ECSExceptions.hpp" namespace nexo::ecs { class Coordinator; From 04eb95e45e58710ab9ca5ab6007e9c09eb13738a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:51:34 +0900 Subject: [PATCH 024/450] rm(ecs-opti): deleted the Signature.hpp file since we now group everything in the Definitions.hpp file --- engine/src/ecs/Signature.hpp | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 engine/src/ecs/Signature.hpp diff --git a/engine/src/ecs/Signature.hpp b/engine/src/ecs/Signature.hpp deleted file mode 100644 index 1972fc3be..000000000 --- a/engine/src/ecs/Signature.hpp +++ /dev/null @@ -1,21 +0,0 @@ -//// Signature.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: Mehdy MORVAN -// Date: 08/11/2024 -// Description: Header file for the signatures -// -/////////////////////////////////////////////////////////////////////////////// -#pragma once - -#include -#include "Components.hpp" - -namespace nexo::ecs { - using Signature = std::bitset; -} From 3dee706568d3e8fc4faaa167231cab6fe038e9cb Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:51:57 +0900 Subject: [PATCH 025/450] refactor(ecs-opti): update the ecs example to showcase the new features --- examples/ecs/exampleBasic.cpp | 881 +++++++++++++++++++++++++++++----- 1 file changed, 763 insertions(+), 118 deletions(-) diff --git a/examples/ecs/exampleBasic.cpp b/examples/ecs/exampleBasic.cpp index 69ced3b26..8b9446a79 100644 --- a/examples/ecs/exampleBasic.cpp +++ b/examples/ecs/exampleBasic.cpp @@ -1,4 +1,4 @@ -//// exampleBasic.cpp ///////////////////////////////////////////////////////// +//// exampleAdvanced.cpp ///////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -7,154 +7,799 @@ // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // // Author: Mehdy MORVAN -// Date: 27/11/2024 -// Description: Source file for the basic ecs example +// Date: 03/04/2025 +// Description: Comprehensive example showcasing various ECS features +// highlighting proper usage of group ownership // /////////////////////////////////////////////////////////////////////////////// #include #include +#include +#include +#include +#include +#include +#include +#include #include "ecs/Coordinator.hpp" -#include -#include -#include -#include +// ============================================================================ +// Component Definitions +// ============================================================================ + +struct Transform { + float x = 0.0f; + float y = 0.0f; + float rotation = 0.0f; + float scale = 1.0f; +}; + +struct Physics { + float velocityX = 0.0f; + float velocityY = 0.0f; + float mass = 1.0f; + bool hasGravity = true; +}; + +struct Renderable { + enum class Type { Circle, Rectangle, Triangle, Custom }; + Type type = Type::Rectangle; + int zIndex = 0; + float r = 1.0f; + float g = 1.0f; + float b = 1.0f; + float a = 1.0f; +}; + +struct AI { + float detectionRadius = 100.0f; + int behaviorType = 0; // 0: patrol, 1: chase, 2: flee, etc. + float reactionTime = 0.5f; + float timeSinceLastDecision = 0.0f; +}; + +struct Health { + int current = 100; + int maximum = 100; + float regenRate = 0.0f; + bool isDead = false; +}; -struct Position { - float x; - float y; - float z; +struct Tag { + enum class Layer { Player, Enemy, Neutral, Projectile, Environment }; + Layer layer = Layer::Neutral; + std::string name = "Entity"; + int id = 0; }; -struct Velocity { - float x; - float y; - float z; +struct Lifetime { + float timeRemaining = 0.0f; + bool shouldDestroy = false; }; +// Helper to format log messages +void log(const std::string& message) { + std::cout << message << std::endl; +} + +// ============================================================================ +// System Implementations +// ============================================================================ + +// Example 1: Physics system, this demonstrates the use of groups in order to efficiently access multiple owned components together +class PhysicsSystem : public nexo::ecs::System { +public: + PhysicsSystem() { + // Create a group that owns both Transform and Physics + // This is the correct way to handle frequently accessed components together + movementGroup = coord->registerGroup(nexo::ecs::get<>()); + } + + void update(float deltaTime) { + const float GRAVITY = 9.8f; -class MovementSystem : public nexo::ecs::System { - public: + log("Physics update started for " + std::to_string(movementGroup->size()) + " entities"); - MovementSystem() - { - positionArray = coord->getComponentArray(); - velocityArray = coord->getComponentArray(); - } + // ITERATION METHOD 1: Using range-based for loop + // This demonstrates how to access multiple owned components efficiently + for (auto [entity, transform, physics] : *movementGroup) { + // Apply velocity to position + transform.x += physics.velocityX * deltaTime; + transform.y += physics.velocityY * deltaTime; - void update(float deltaTime) - { - for (auto entity : entities) { - auto& position = positionArray->getData(entity); - auto& velocity = velocityArray->getData(entity); + // Apply gravity if enabled + if (physics.hasGravity) { + physics.velocityY += GRAVITY * physics.mass * deltaTime; + } + + // Apply damping + physics.velocityX *= 0.99f; + physics.velocityY *= 0.99f; - // Update position using velocity - position.x += velocity.x * deltaTime; - position.y += velocity.y * deltaTime; - position.z += velocity.z * deltaTime; + // Boundary checking + if (transform.y < -100.0f) { + transform.y = -100.0f; + physics.velocityY = -physics.velocityY * 0.8f; // Bounce } } - private: - std::shared_ptr> positionArray; - std::shared_ptr> velocityArray; + log("Physics update completed"); + } + + // Method to show different ways to access group data + void showPerformanceExamples(float deltaTime) { + log("Demonstrating different access methods for physics calculations:"); + const float GRAVITY = 9.8f; + + // METHOD 1: RANGE-BASED FOR LOOP (Already shown in update method) + auto start1 = std::chrono::high_resolution_clock::now(); + for (auto [entity, transform, physics] : *movementGroup) { + // Apply velocity to position + transform.x += physics.velocityX * deltaTime; + transform.y += physics.velocityY * deltaTime; + + // Apply gravity if enabled + if (physics.hasGravity) { + physics.velocityY += GRAVITY * physics.mass * deltaTime; + } + + // Apply damping + physics.velocityX *= 0.99f; + physics.velocityY *= 0.99f; + + // Boundary checking + if (transform.y < -100.0f) { + transform.y = -100.0f; + physics.velocityY = -physics.velocityY * 0.8f; // Bounce + } + } + auto end1 = std::chrono::high_resolution_clock::now(); + + // METHOD 2: USING EACH METHOD WITH LAMBDA + auto start2 = std::chrono::high_resolution_clock::now(); + + log("Method 2: Using each() with lambda"); + movementGroup->each([deltaTime, GRAVITY](nexo::ecs::Entity entity, Transform& transform, Physics& physics) { + // Same physics calculations as above + transform.x += physics.velocityX * deltaTime; + transform.y += physics.velocityY * deltaTime; + // Apply gravity if enabled + if (physics.hasGravity) { + physics.velocityY += GRAVITY * physics.mass * deltaTime; + } + + // Apply damping + physics.velocityX *= 0.99f; + physics.velocityY *= 0.99f; + + // Boundary checking + if (transform.y < -100.0f) { + transform.y = -100.0f; + physics.velocityY = -physics.velocityY * 0.8f; // Bounce + } + }); + + auto end2 = std::chrono::high_resolution_clock::now(); + + // METHOD 3: DIRECT SPAN ACCESS (Most efficient for tight loops) + auto start3 = std::chrono::high_resolution_clock::now(); + + log("Method 3: Using direct span access (most efficient)"); + auto transformSpan = movementGroup->get(); + auto physicsSpan = movementGroup->get(); + + for (size_t i = 0; i < transformSpan.size(); i++) { + transformSpan[i].x += physicsSpan[i].velocityX * deltaTime; + transformSpan[i].y += physicsSpan[i].velocityY * deltaTime; + + if (physicsSpan[i].hasGravity) { + physicsSpan[i].velocityY += GRAVITY * physicsSpan[i].mass * deltaTime; + } + + // Apply damping + physicsSpan[i].velocityX *= 0.99f; + physicsSpan[i].velocityY *= 0.99f; + } + + auto end3 = std::chrono::high_resolution_clock::now(); + + // METHOD 4: SIMD PROCESSING (Further optimization with vectorization, not extremly pertinent for this case, but it demonstrates that it is possible) + auto start4 = std::chrono::high_resolution_clock::now(); + + log("Method 4: Using SIMD instructions for vectorized processing"); + + transformSpan = movementGroup->get(); + physicsSpan = movementGroup->get(); + const size_t size = transformSpan.size(); + + // Process in chunks of 4 elements using SSE instructions + const __m128 dt = _mm_set1_ps(deltaTime); // [dt, dt, dt, dt] + const __m128 gravity = _mm_set1_ps(GRAVITY); // [9.8, 9.8, 9.8, 9.8] + const __m128 damping = _mm_set1_ps(0.99f); // [0.99, 0.99, 0.99, 0.99] + const __m128 bounce = _mm_set1_ps(-0.8f); // [-0.8, -0.8, -0.8, -0.8] + const __m128 floor = _mm_set1_ps(-100.0f); // [-100, -100, -100, -100] + + // Process chunks of 4 elements at once + size_t i = 0; + for (; i + 3 < size; i += 4) { + // Load 4 x and y positions + __m128 posX = _mm_set_ps( + transformSpan[i+3].x, transformSpan[i+2].x, + transformSpan[i+1].x, transformSpan[i].x + ); + + __m128 posY = _mm_set_ps( + transformSpan[i+3].y, transformSpan[i+2].y, + transformSpan[i+1].y, transformSpan[i].y + ); + + // Load 4 x and y velocities + __m128 velX = _mm_set_ps( + physicsSpan[i+3].velocityX, physicsSpan[i+2].velocityX, + physicsSpan[i+1].velocityX, physicsSpan[i].velocityX + ); + + __m128 velY = _mm_set_ps( + physicsSpan[i+3].velocityY, physicsSpan[i+2].velocityY, + physicsSpan[i+1].velocityY, physicsSpan[i].velocityY + ); + + // Load 4 mass values + __m128 mass = _mm_set_ps( + physicsSpan[i+3].mass, physicsSpan[i+2].mass, + physicsSpan[i+1].mass, physicsSpan[i].mass + ); + + // Load 4 hasGravity flags (as floats: 1.0f for true, 0.0f for false) + __m128 hasGravity = _mm_set_ps( + physicsSpan[i+3].hasGravity ? 1.0f : 0.0f, + physicsSpan[i+2].hasGravity ? 1.0f : 0.0f, + physicsSpan[i+1].hasGravity ? 1.0f : 0.0f, + physicsSpan[i].hasGravity ? 1.0f : 0.0f + ); + + // Calculate velocity * deltaTime + __m128 deltaX = _mm_mul_ps(velX, dt); + __m128 deltaY = _mm_mul_ps(velY, dt); + + // Update positions: pos += vel * dt + posX = _mm_add_ps(posX, deltaX); + posY = _mm_add_ps(posY, deltaY); + + // Apply gravity: vel_y += gravity * mass * dt * hasGravity + __m128 gravityEffect = _mm_mul_ps(gravity, mass); // gravity * mass + gravityEffect = _mm_mul_ps(gravityEffect, dt); // * dt + gravityEffect = _mm_mul_ps(gravityEffect, hasGravity); // * hasGravity + velY = _mm_add_ps(velY, gravityEffect); // velY += ... + + // Apply damping: vel *= 0.99 + velX = _mm_mul_ps(velX, damping); + velY = _mm_mul_ps(velY, damping); + + // Boundary check: if (posY < -100.0) { posY = -100.0; velY *= -0.8; } + __m128 belowFloor = _mm_cmplt_ps(posY, floor); // posY < floor? + __m128 bounceVel = _mm_mul_ps(velY, bounce); // velY * -0.8 + velY = _mm_or_ps( + _mm_and_ps(belowFloor, bounceVel), // if below floor: use bounce vel + _mm_andnot_ps(belowFloor, velY) // else: keep original vel + ); + posY = _mm_or_ps( + _mm_and_ps(belowFloor, floor), // if below floor: use floor value + _mm_andnot_ps(belowFloor, posY) // else: keep original pos + ); + + // Store back the results + float posXResult[4], posYResult[4], velXResult[4], velYResult[4]; + _mm_storeu_ps(posXResult, posX); + _mm_storeu_ps(posYResult, posY); + _mm_storeu_ps(velXResult, velX); + _mm_storeu_ps(velYResult, velY); + + for (size_t j = 0; j < 4; j++) { + transformSpan[i+j].x = posXResult[j]; + transformSpan[i+j].y = posYResult[j]; + physicsSpan[i+j].velocityX = velXResult[j]; + physicsSpan[i+j].velocityY = velYResult[j]; + } + } + + // Process remaining elements normally + for (; i < size; i++) { + transformSpan[i].x += physicsSpan[i].velocityX * deltaTime; + transformSpan[i].y += physicsSpan[i].velocityY * deltaTime; + + if (physicsSpan[i].hasGravity) { + physicsSpan[i].velocityY += GRAVITY * physicsSpan[i].mass * deltaTime; + } + + physicsSpan[i].velocityX *= 0.99f; + physicsSpan[i].velocityY *= 0.99f; + + if (transformSpan[i].y < -100.0f) { + transformSpan[i].y = -100.0f; + physicsSpan[i].velocityY *= -0.8f; + } + } + + auto end4 = std::chrono::high_resolution_clock::now(); + + // Display timing results + auto duration1 = std::chrono::duration_cast(end1 - start1).count(); + auto duration2 = std::chrono::duration_cast(end2 - start2).count(); + auto duration3 = std::chrono::duration_cast(end3 - start3).count(); + auto duration4 = std::chrono::duration_cast(end4 - start4).count(); + + log("Performance comparison:"); + log(" Range-based for: " + std::to_string(duration1) + " μs"); + log(" each() method: " + std::to_string(duration2) + " μs"); + log(" Direct span: " + std::to_string(duration3) + " μs"); + log(" SIMD processing: " + std::to_string(duration4) + " μs"); + } + +private: + // This group properly owns both Transform and Physics together since they're + // frequently accessed together in the physics update + nexo::ecs::GroupAlias< + nexo::ecs::OwnedComponents, + nexo::ecs::NonOwnedComponents<> + > movementGroup = nullptr; }; -int main(int argc, char** argv) { - // Check if the correct number of arguments is provided. - if (argc != 4) { - std::cerr << "Usage: " << argv[0] - << " \n"; - return 1; +// Example 2: Render system demonstrating sorting and partitioning +class RenderSystem : public nexo::ecs::System { +public: + RenderSystem() { + // Create a group that owns Renderable but accesses Transform as non-owned + // This is appropriate because rendering needs to sort/organize by Renderable properties + // but Transform is already owned by the PhysicsSystem + renderGroup = coord->registerGroup(nexo::ecs::get()); } - // Parse command-line arguments. - int numEntities = std::stoi(argv[1]); - int numFrames = std::stoi(argv[2]); - int repeatCount = std::stoi(argv[3]); + void render() { + log("Render pass started for " + std::to_string(renderGroup->size()) + " entities"); + + // SORTING EXAMPLE: Sort by z-index for proper render order + // This shows how owning the Renderable component allows efficient sorting + renderGroup->sortBy( + [](const Renderable& renderable) { return renderable.zIndex; } + ); - std::cout << "Running with " << numEntities << " entities, " - << numFrames << " frames per run, repeated " << repeatCount << " times.\n"; + // PARTITIONING EXAMPLE: Group by renderable type for batch rendering + auto typePartitions = renderGroup->getPartitionView( + [](const Renderable& renderable) { return renderable.type; } + ); + + log("Rendering in " + std::to_string(typePartitions.partitionCount()) + " batches"); + + // Process each partition separately (batch rendering) + auto keys = typePartitions.getPartitionKeys(); + for (auto type : keys) { + std::string typeName; + switch (type) { + case Renderable::Type::Circle: typeName = "Circles"; break; + case Renderable::Type::Rectangle: typeName = "Rectangles"; break; + case Renderable::Type::Triangle: typeName = "Triangles"; break; + case Renderable::Type::Custom: typeName = "Custom shapes"; break; + } + + const auto* partition = typePartitions.getPartition(type); + if (partition) { + log(" Batch rendering " + std::to_string(partition->count) + " " + typeName); + + // Example of using each() on a partition for batch rendering + typePartitions.each(type, [](nexo::ecs::Entity entity, Renderable& renderable, Transform& transform, Tag& tag) { + // Simulated rendering code + std::string typeName; + switch (renderable.type) { + case Renderable::Type::Circle: typeName = "Circle"; break; + case Renderable::Type::Rectangle: typeName = "Rectangle"; break; + case Renderable::Type::Triangle: typeName = "Triangle"; break; + case Renderable::Type::Custom: typeName = "Custom shape"; break; + } + std::cout << " - Rendered " << typeName << " " << tag.name << " at (" + << transform.x << ", " << transform.y + << ") with z-index " << renderable.zIndex << "\n"; + }); + } + } + + log("Render pass completed"); + } + +private: + nexo::ecs::GroupAlias< + nexo::ecs::OwnedComponents, + nexo::ecs::NonOwnedComponents + > renderGroup = nullptr; +}; + +// Example 3: AI and health system demonstrating component addition/removal +class AISystem : public nexo::ecs::System { +public: + AISystem() { + // AI and Health components are frequently accessed together in AI logic, + // so they're owned by the same group + aiGroup = coord->registerGroup(nexo::ecs::get()); + } + + void update(float deltaTime) { + log("AI update started for " + std::to_string(aiGroup->size()) + " entities"); + + // Keep track of entities that need component changes + std::vector entitiesToAddLifetime; + std::vector entitiesToSpawnProjectile; + + // Random number generation for AI decisions + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> posDist(-100.0, 100.0); + std::uniform_int_distribution<> decisionDist(0, 10); - // Initialize the ECS Coordinator. + // PARTITIONING EXAMPLE: Create partitions by behavior type + auto behaviorPartitions = aiGroup->getPartitionView( + [](const AI& ai) { return ai.behaviorType; } + ); + + // Process each behavior type batch separately + for (int behaviorType = 0; behaviorType <= 2; behaviorType++) { + std::string behaviorName; + switch (behaviorType) { + case 0: behaviorName = "Patrol"; break; + case 1: behaviorName = "Chase"; break; + case 2: behaviorName = "Flee"; break; + default: behaviorName = "Unknown"; break; + } + + const auto* partition = behaviorPartitions.getPartition(behaviorType); + if (!partition) { + continue; + } + + log(" Processing " + std::to_string(partition->count) + " entities with " + behaviorName + " behavior"); + + // Process entities with this behavior type + behaviorPartitions.each(behaviorType, [&]( + nexo::ecs::Entity entity, + AI& ai, + Health& health, + Transform& transform, + Tag& tag + ) { + // Update AI decision timer + ai.timeSinceLastDecision += deltaTime; + + // Only make decisions at specific intervals based on reaction time + if (ai.timeSinceLastDecision >= ai.reactionTime) { + ai.timeSinceLastDecision = 0.0f; + + // Change behavior based on health + if (health.current < health.maximum * 0.2f && ai.behaviorType != 2) { + log(" " + tag.name + " is low on health, switching to Flee behavior"); + ai.behaviorType = 2; // Flee + } + else if (health.current > health.maximum * 0.8f && ai.behaviorType == 2) { + log(" " + tag.name + " has recovered, switching to Patrol behavior"); + ai.behaviorType = 0; // Patrol + } + + // Random decision to fire projectile + if (decisionDist(gen) == 0) { + entitiesToSpawnProjectile.push_back(entity); + } + + // Apply health regeneration + if (health.regenRate > 0 && health.current < health.maximum) { + health.current += static_cast(health.regenRate * deltaTime); + health.current = std::min(health.current, health.maximum); + } + + // Check for death + if (health.current <= 0 && !health.isDead) { + health.isDead = true; + health.current = 0; + log(" " + tag.name + " has died!"); + + // Mark for adding lifetime component after iteration + entitiesToAddLifetime.push_back(entity); + } + } + }); + } + + // Process component changes after iteration to avoid invalidation + for (auto entity : entitiesToAddLifetime) { + if (!coord->entityHasComponent(entity)) { + log(" Adding Lifetime component to dead entity " + + coord->getComponent(entity).name); + coord->addComponent(entity, Lifetime{5.0f, false}); + } + } + + // Process projectile spawning after iteration + for (auto entity : entitiesToSpawnProjectile) { + spawnProjectile(entity); + } + + log("AI update completed"); + } + + void spawnProjectile(nexo::ecs::Entity source) { + const auto& sourceTransform = coord->getComponent(source); + const auto& sourceTag = coord->getComponent(source); + + nexo::ecs::Entity projectile = coord->createEntity(); + + // Random projectile direction + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> velDist(-50.0f, 50.0f); + + // Add necessary components + coord->addComponent(projectile, Transform{ + sourceTransform.x, sourceTransform.y, 0.0f, 0.5f + }); + + coord->addComponent(projectile, Physics{ + static_cast(velDist(gen)), + static_cast(velDist(gen)), + 0.1f, true + }); + + coord->addComponent(projectile, Renderable{ + Renderable::Type::Circle, -1, 1.0f, 0.0f, 0.0f, 1.0f + }); + + coord->addComponent(projectile, Lifetime{3.0f, false}); + + coord->addComponent(projectile, Tag{ + Tag::Layer::Projectile, + sourceTag.name + "_projectile", + static_cast(projectile) + }); + + log(" " + sourceTag.name + " spawned a projectile"); + } + + void applyDamage(nexo::ecs::Entity target, int amount) { + if (!coord->entityHasComponent(target)) { + log("Cannot apply damage to entity without Health component"); + return; + } + + auto& health = coord->getComponent(target); + std::string entityName = "Unknown"; + + if (coord->entityHasComponent(target)) { + entityName = coord->getComponent(target).name; + } + + health.current -= amount; + log("Applied " + std::to_string(amount) + " damage to " + entityName + + " (Health: " + std::to_string(health.current) + "/" + + std::to_string(health.maximum) + ")"); + } + +private: + nexo::ecs::GroupAlias< + nexo::ecs::OwnedComponents, + nexo::ecs::NonOwnedComponents + > aiGroup = nullptr; +}; + +// Example 4: Lifetime system for handling entity destruction +class LifetimeSystem : public nexo::ecs::System { +public: + LifetimeSystem() { + // This group owns the Lifetime component + lifetimeGroup = coord->registerGroup(nexo::ecs::get()); + } + + void update(float deltaTime) { + if (lifetimeGroup->size() == 0) { + return; + } + + log("Updating lifetime for " + std::to_string(lifetimeGroup->size()) + " entities"); + + // Collect entities to be destroyed after iteration + std::vector entitiesToDestroy; + + lifetimeGroup->each([&](nexo::ecs::Entity entity, Lifetime& lifetime, Tag& tag) { + lifetime.timeRemaining -= deltaTime; + + if (lifetime.timeRemaining <= 0.0f || lifetime.shouldDestroy) { + log(" Marking " + tag.name + " for destruction"); + entitiesToDestroy.push_back(entity); + } + }); + + // Destroy entities after iteration is complete + for (auto entity : entitiesToDestroy) { + log(" Destroying entity " + std::to_string(entity)); + coord->destroyEntity(entity); + } + } + +private: + nexo::ecs::GroupAlias< + nexo::ecs::OwnedComponents, + nexo::ecs::NonOwnedComponents + > lifetimeGroup = nullptr; +}; + +// ============================================================================ +// Main Program +// ============================================================================ + +int main() { + // Initialize ECS Coordinator nexo::ecs::Coordinator coordinator; coordinator.init(); - // Register components. - coordinator.registerComponent(); - coordinator.registerComponent(); - - // Register and set up the MovementSystem. - auto movementSystem = coordinator.registerSystem(); - nexo::ecs::Signature movementSignature; - movementSignature.set(coordinator.getComponentType(), true); - movementSignature.set(coordinator.getComponentType(), true); - coordinator.setSystemSignature(movementSignature); - - auto setupStart = std::chrono::steady_clock::now(); - // Create entities and assign components. - std::vector createdEntities; - createdEntities.reserve(numEntities); - for (int i = 0; i < numEntities; i++) { - nexo::ecs::Entity newEntity = coordinator.createEntity(); - coordinator.addComponent(newEntity, Position{0.0f, 0.0f, 0.0f}); - coordinator.addComponent(newEntity, Velocity{1.0f, 0.0f, 0.0f}); - createdEntities.push_back(newEntity); - } - auto setupEnd = std::chrono::steady_clock::now(); - auto setupDurationMicro = std::chrono::duration_cast(setupEnd - setupStart).count(); - - std::cout << "Setup duration: " << setupDurationMicro << " microseconds" << std::endl; - - // Remove 25% of the entities randomly. - int numToRemove = numEntities / 4; + log("ECS initialized"); + + // Register all components + coordinator.registerComponent(); + coordinator.registerComponent(); + coordinator.registerComponent(); + coordinator.registerComponent(); + coordinator.registerComponent(); + coordinator.registerComponent(); + coordinator.registerComponent(); + + log("Components registered"); + + // Register and set up the systems + auto physicsSystem = coordinator.registerSystem(); { - // Create a random device and engine. - std::random_device rd; - std::mt19937 engine(rd()); - std::shuffle(createdEntities.begin(), createdEntities.end(), engine); - } - for (int i = 0; i < numToRemove; ++i) { - nexo::ecs::Entity entityToRemove = createdEntities[i]; - coordinator.destroyEntity(entityToRemove); - } - std::cout << "Removed " << numToRemove << " entities randomly after setup.\n"; - - // Run the update loop as specified by the command-line arguments. - // We'll measure frame times in microseconds. - long long overallDurationMicro = 0; - for (int repeat = 0; repeat < repeatCount; ++repeat) { - long long totalDurationMicro = 0; - for (int frame = 0; frame < numFrames; ++frame) { - auto frameStart = std::chrono::steady_clock::now(); - - // Update the MovementSystem (assume a delta time of ~16ms per frame). - movementSystem->update(0.016f); - - auto frameEnd = std::chrono::steady_clock::now(); - auto frameDurationMicro = std::chrono::duration_cast(frameEnd - frameStart).count(); - // Convert microseconds to milliseconds for display. - double frameDurationMilli = frameDurationMicro / 1000.0; - totalDurationMicro += frameDurationMicro; - } - double totalDurationMilli = totalDurationMicro / 1000.0; - double averageDurationMilli = (totalDurationMicro / static_cast(numFrames)) / 1000.0; - std::cout << "Iteration " << repeat << ": total time to render " << numFrames - << " frames: " << totalDurationMilli << " milliseconds.\n"; - std::cout << "Iteration " << repeat << ": average frame duration: " - << averageDurationMilli << " milliseconds.\n\n"; - overallDurationMicro += totalDurationMicro; + nexo::ecs::Signature signature; + signature.set(coordinator.getComponentType()); + signature.set(coordinator.getComponentType()); + coordinator.setSystemSignature(signature); + } + + auto renderSystem = coordinator.registerSystem(); + { + nexo::ecs::Signature signature; + signature.set(coordinator.getComponentType()); + signature.set(coordinator.getComponentType()); + signature.set(coordinator.getComponentType()); + coordinator.setSystemSignature(signature); + } + + auto aiSystem = coordinator.registerSystem(); + { + nexo::ecs::Signature signature; + signature.set(coordinator.getComponentType()); + signature.set(coordinator.getComponentType()); + signature.set(coordinator.getComponentType()); + signature.set(coordinator.getComponentType()); + coordinator.setSystemSignature(signature); + } + + auto lifetimeSystem = coordinator.registerSystem(); + { + nexo::ecs::Signature signature; + signature.set(coordinator.getComponentType()); + signature.set(coordinator.getComponentType()); + coordinator.setSystemSignature(signature); + } + + log("Systems registered"); + + // ======================================================================== + // Entity Creation Examples + // ======================================================================== + log("Creating entities..."); + + // Create a player entity + nexo::ecs::Entity player = coordinator.createEntity(); + coordinator.addComponent(player, Transform{0.0f, 0.0f, 0.0f, 1.5f}); + coordinator.addComponent(player, Physics{0.0f, 0.0f, 2.0f, true}); + coordinator.addComponent(player, Renderable{Renderable::Type::Triangle, 10, 0.0f, 0.0f, 1.0f, 1.0f}); + coordinator.addComponent(player, Health{150, 150, 1.0f}); + coordinator.addComponent(player, Tag{Tag::Layer::Player, "Player", 1}); + log("Created Player entity with ID: " + std::to_string(player)); + + // Create some enemy entities with different behavior types + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> posDist(-100.0, 100.0); + std::uniform_int_distribution<> zIndexDist(1, 20); + std::uniform_int_distribution<> colorDist(0, 255); + std::uniform_int_distribution<> behaviorDist(0, 2); + + std::vector enemies; + for (int i = 0; i < 10; ++i) { + nexo::ecs::Entity enemy = coordinator.createEntity(); + + float posX = static_cast(posDist(gen)); + float posY = static_cast(posDist(gen)); + int behaviorType = behaviorDist(gen); + + coordinator.addComponent(enemy, Transform{posX, posY}); + coordinator.addComponent(enemy, Physics{0.0f, 0.0f, 1.0f, true}); + coordinator.addComponent(enemy, Renderable{ + Renderable::Type::Rectangle, + zIndexDist(gen), + colorDist(gen) / 255.0f, + colorDist(gen) / 255.0f, + colorDist(gen) / 255.0f, + 1.0f + }); + coordinator.addComponent(enemy, AI{ + 50.0f + i * 5.0f, // Detection radius + behaviorType, // Behavior type + 0.5f + i * 0.1f // Reaction time + }); + coordinator.addComponent(enemy, Health{80 + i * 5, 100, 0.5f}); + coordinator.addComponent(enemy, Tag{ + Tag::Layer::Enemy, + "Enemy_" + std::to_string(i), + 100 + i + }); + + enemies.push_back(enemy); + log("Created Enemy entity with ID: " + std::to_string(enemy)); } - // Overall per-frame average (in milliseconds). - double overallAverageFrameMilli = (overallDurationMicro / static_cast(repeatCount * numFrames)) / 1000.0; - std::cout << "Overall average frame duration over all repeats: " - << overallAverageFrameMilli << " milliseconds.\n"; + // Create some environmental objects + for (int i = 0; i < 5; ++i) { + nexo::ecs::Entity environment = coordinator.createEntity(); + + float posX = static_cast(posDist(gen)); + float posY = static_cast(posDist(gen)); + + coordinator.addComponent(environment, Transform{posX, posY, 0.0f, 2.0f + i * 0.5f}); + coordinator.addComponent(environment, Renderable{ + i % 2 == 0 ? Renderable::Type::Circle : Renderable::Type::Custom, + zIndexDist(gen), + 0.0f, 0.5f, 0.0f, 1.0f // Green color + }); + coordinator.addComponent(environment, Tag{ + Tag::Layer::Environment, + "Environment_" + std::to_string(i), + 200 + i + }); - // Average total time to render numFrames frames across all repeats. - double averageTotalTimeMilli = (overallDurationMicro / static_cast(repeatCount)) / 1000.0; - std::cout << "Average time to render " << numFrames << " frames: " - << averageTotalTimeMilli << " milliseconds.\n"; + log("Created Environment entity with ID: " + std::to_string(environment)); + } + + // ======================================================================== + // Performance Example + // ======================================================================== + log("\n=== Performance Comparison of Access Methods ==="); + physicsSystem->showPerformanceExamples(0.016f); + + // ======================================================================== + // Simulation Update Loop + // ======================================================================== + log("\n=== Starting Simulation ==="); + + const float TIME_STEP = 0.016f; // ~60 FPS + for (int frame = 0; frame < 5; ++frame) { + log("\n--- Frame " + std::to_string(frame) + " ---"); + + // Update all systems + physicsSystem->update(TIME_STEP); + aiSystem->update(TIME_STEP); + + // Example: Apply damage to a random enemy every other frame + if (frame % 2 == 0 && !enemies.empty()) { + std::uniform_int_distribution<> enemyDist(0, static_cast(enemies.size()) - 1); + int targetIndex = enemyDist(gen); + + log("\n*** Combat Event ***"); + aiSystem->applyDamage(enemies[targetIndex], 30); + } + + // Update lifetime system + lifetimeSystem->update(TIME_STEP); + + // Render the scene last + renderSystem->render(); + } + log("\n=== Simulation Complete ==="); return 0; } From 46632416644ee0aa17c6c437504fb7513543a587 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 13:52:13 +0900 Subject: [PATCH 026/450] tests(ecs-opti): add tests for the new ecs features --- tests/ecs/CMakeLists.txt | 1 + tests/ecs/Components.test.cpp | 60 ++- tests/ecs/Coordinator.test.cpp | 4 +- tests/ecs/Exceptions.test.cpp | 2 +- tests/ecs/Group.test.cpp | 760 +++++++++++++++++++++++++++++++++ tests/ecs/System.test.cpp | 2 +- 6 files changed, 808 insertions(+), 21 deletions(-) create mode 100644 tests/ecs/Group.test.cpp diff --git a/tests/ecs/CMakeLists.txt b/tests/ecs/CMakeLists.txt index bfd103288..4d2f230c4 100644 --- a/tests/ecs/CMakeLists.txt +++ b/tests/ecs/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable(ecs_tests ${BASEDIR}/Exceptions.test.cpp ${BASEDIR}/SparseSet.test.cpp ${BASEDIR}/SingletonComponent.test.cpp + ${BASEDIR}/Group.test.cpp ) # Find glm and add its include directories diff --git a/tests/ecs/Components.test.cpp b/tests/ecs/Components.test.cpp index ce34b794a..ee3f2c994 100644 --- a/tests/ecs/Components.test.cpp +++ b/tests/ecs/Components.test.cpp @@ -15,6 +15,7 @@ #include #include #include "ecs/Components.hpp" +#include "Definitions.hpp" namespace nexo::ecs { struct TestComponent { @@ -476,8 +477,11 @@ namespace nexo::ecs { { componentManager->registerComponent(); Entity entity = 1; + Signature signature; - componentManager->addComponent(entity, TestComponent{42}); + signature.set(componentManager->getComponentType(), true); + + componentManager->addComponent(entity, TestComponent{42}, signature); TestComponent &retrieved = componentManager->getComponent(entity); EXPECT_EQ(retrieved.value, 42); @@ -487,9 +491,12 @@ namespace nexo::ecs { { componentManager->registerComponent(); Entity entity = 1; + Signature signature; + + signature.set(componentManager->getComponentType(), true); - componentManager->addComponent(entity, TestComponent{42}); - EXPECT_NO_THROW(componentManager->removeComponent(entity)); + componentManager->addComponent(entity, TestComponent{42}, signature); + EXPECT_NO_THROW(componentManager->removeComponent(entity, signature)); EXPECT_THROW(static_cast(componentManager->getComponent(entity)), ComponentNotFound); } @@ -497,12 +504,15 @@ namespace nexo::ecs { { componentManager->registerComponent(); Entity entity = 1; + Signature signature; - EXPECT_FALSE(componentManager->tryRemoveComponent(entity)); // No component yet + EXPECT_FALSE(componentManager->tryRemoveComponent(entity, signature)); // No component yet - componentManager->addComponent(entity, TestComponent{42}); - EXPECT_TRUE(componentManager->tryRemoveComponent(entity)); - EXPECT_FALSE(componentManager->tryRemoveComponent(entity)); + signature.set(componentManager->getComponentType(), true); + componentManager->addComponent(entity, TestComponent{42}, signature); + EXPECT_TRUE(componentManager->tryRemoveComponent(entity, signature)); + signature.set(componentManager->getComponentType(), false); + EXPECT_FALSE(componentManager->tryRemoveComponent(entity, signature)); } TEST_F(ComponentManagerTest, EntityDestroyedCleansUpComponents) @@ -510,9 +520,14 @@ namespace nexo::ecs { componentManager->registerComponent(); Entity entity1 = 1; Entity entity2 = 2; + Signature signature1; + Signature signature2; - componentManager->addComponent(entity1, TestComponent{42}); - componentManager->addComponent(entity2, TestComponent{84}); + signature1.set(componentManager->getComponentType(), true); + signature2.set(componentManager->getComponentType(), true); + + componentManager->addComponent(entity1, TestComponent{42}, signature1); + componentManager->addComponent(entity2, TestComponent{84}, signature2); componentManager->entityDestroyed(entity1); @@ -528,7 +543,7 @@ namespace nexo::ecs { TEST_F(ComponentManagerTest, AddComponentWithoutRegistering) { Entity entity = 1; - EXPECT_THROW(componentManager->addComponent(entity, TestComponent{42}), ComponentNotRegistered); + EXPECT_THROW(componentManager->addComponent(entity, TestComponent{42}, Signature{}), ComponentNotRegistered); } TEST_F(ComponentManagerTest, TryGetComponent) @@ -539,7 +554,9 @@ namespace nexo::ecs { auto result = componentManager->tryGetComponent(entity); EXPECT_FALSE(result.has_value()); - componentManager->addComponent(entity, TestComponent{42}); + Signature signature; + signature.set(componentManager->getComponentType(), true); + componentManager->addComponent(entity, TestComponent{42}, signature); result = componentManager->tryGetComponent(entity); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->get().value, 42); @@ -574,7 +591,9 @@ namespace nexo::ecs { // Setup original manager componentManager->registerComponent(); Entity entity = 1; - componentManager->addComponent(entity, TestComponent{42}); + Signature signature; + signature.set(componentManager->getComponentType(), true); + componentManager->addComponent(entity, TestComponent{42}, signature); // Move construct new manager ComponentManager movedManager(std::move(*componentManager)); @@ -588,7 +607,9 @@ namespace nexo::ecs { // Setup original manager componentManager->registerComponent(); Entity entity = 1; - componentManager->addComponent(entity, TestComponent{42}); + Signature signature; + signature.set(componentManager->getComponentType(), true); + componentManager->addComponent(entity, TestComponent{42}, signature); // Create second manager and move-assign ComponentManager secondManager; @@ -635,7 +656,9 @@ namespace nexo::ecs { Entity entity = 1; ComplexComponent complex{"test", {1, 2, 3}}; - componentManager->addComponent(entity, complex); + Signature signature; + signature.set(componentManager->getComponentType(), true); + componentManager->addComponent(entity, complex, signature); ComplexComponent& retrieved = componentManager->getComponent(entity); EXPECT_EQ(retrieved.name, "test"); @@ -647,7 +670,9 @@ namespace nexo::ecs { componentManager->registerComponent(); Entity entity = 1; - componentManager->addComponent(entity, TestComponent{42}); + Signature signature; + signature.set(componentManager->getComponentType(), true); + componentManager->addComponent(entity, TestComponent{42}, signature); // Get component array directly and use it auto array = componentManager->getComponentArray(); @@ -659,8 +684,9 @@ namespace nexo::ecs { { componentManager->registerComponent(); Entity entity = 1; - - componentManager->addComponent(entity, TestComponent{42}); + Signature signature; + signature.set(componentManager->getComponentType(), true); + componentManager->addComponent(entity, TestComponent{42}, signature); // Modify component directly componentManager->getComponent(entity).value = 100; diff --git a/tests/ecs/Coordinator.test.cpp b/tests/ecs/Coordinator.test.cpp index 6c695c871..61e998e8b 100644 --- a/tests/ecs/Coordinator.test.cpp +++ b/tests/ecs/Coordinator.test.cpp @@ -17,7 +17,7 @@ #include "ecs/Coordinator.hpp" #include "Components.hpp" #include "ecs/SingletonComponent.hpp" -#include "ecs/Signature.hpp" +#include "ecs/Definitions.hpp" #include "ecs/System.hpp" #include "ecs/Entity.hpp" @@ -112,7 +112,7 @@ namespace nexo::ecs { TEST_F(CoordinatorTest, RemoveComponentFromNonexistentEntity) { coordinator->registerComponent(); - Entity nonexistentEntity = 99999; + Entity nonexistentEntity = 100; EXPECT_THROW(coordinator->removeComponent(nonexistentEntity), ComponentNotFound); } diff --git a/tests/ecs/Exceptions.test.cpp b/tests/ecs/Exceptions.test.cpp index 67f453d46..b2fa894ed 100644 --- a/tests/ecs/Exceptions.test.cpp +++ b/tests/ecs/Exceptions.test.cpp @@ -72,7 +72,7 @@ namespace nexo::ecs { TooManyEntities ex; std::string formattedMessage = ex.what(); - EXPECT_NE(formattedMessage.find("Too many living entities, max is 8191"), std::string::npos); + EXPECT_NE(formattedMessage.find(std::format("Too many living entities, max is {}", MAX_ENTITIES)), std::string::npos); EXPECT_NE(formattedMessage.find(expectedFile), std::string::npos); EXPECT_NE(formattedMessage.find(std::to_string(expectedLine)), std::string::npos); } diff --git a/tests/ecs/Group.test.cpp b/tests/ecs/Group.test.cpp new file mode 100644 index 000000000..a3667b32b --- /dev/null +++ b/tests/ecs/Group.test.cpp @@ -0,0 +1,760 @@ +//// Group.test.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: iMeaNz +// Date: 03/04/2025 +// Description: Test file for the ECS Group class +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include "ecs/Definitions.hpp" +#include "ecs/ECSExceptions.hpp" +#include "ecs/ComponentArray.hpp" +#include "ecs/Components.hpp" +#include "ecs/Group.hpp" + +namespace nexo::ecs { + + // Define test components + struct Position { + float x; + float y; + + bool operator==(const Position& other) const { + return x == other.x && y == other.y; + } + }; + + struct Velocity { + float x; + float y; + + bool operator==(const Velocity& other) const { + return x == other.x && y == other.y; + } + }; + + struct Health { + int value; + + bool operator==(const Health& other) const { + return value == other.value; + } + }; + + struct Tag { + std::string name; + + bool operator==(const Tag& other) const { + return name == other.name; + } + }; + + class GroupTest : public ::testing::Test { + protected: + void SetUp() override { + // Create component arrays + positionArray = std::make_shared>(); + velocityArray = std::make_shared>(); + healthArray = std::make_shared>(); + tagArray = std::make_shared>(); + + // Insert test data + for (Entity i = 0; i < 10; ++i) { + positionArray->insertData(i, Position{static_cast(i), static_cast(i * 2)}); + velocityArray->insertData(i, Velocity{static_cast(i + 0.5f), static_cast((i + 1) * 2)}); + healthArray->insertData(i, Health{100 - static_cast(i * 10)}); + tagArray->insertData(i, Tag{"Entity" + std::to_string(i)}); + + // Add entities 0-4 to the group section + if (i < 5) { + positionArray->addToGroup(i); + velocityArray->addToGroup(i); + healthArray->addToGroup(i); + tagArray->addToGroup(i); + } + } + } + + // Helper method to set up health values for partition tests + void groupHealthData() { + // Modify health values to have distinct groups + // Entity 0: Health 90 (High) + // Entity 1: Health 60 (Medium) + // Entity 2: Health 30 (Low) + // Entity 3: Health 80 (High) + // Entity 4: Health 20 (Low) + healthArray->getData(0).value = 90; + healthArray->getData(1).value = 60; + healthArray->getData(2).value = 30; + healthArray->getData(3).value = 80; + healthArray->getData(4).value = 20; + } + + // Helper method to create groups with different configurations + template + auto createGroup(const get_t<>& nonOwned = get<>()) { + using OwnedTuple = std::tuple>...>; + using NonOwnedTuple = std::tuple<>; + auto ownedArrays = std::make_tuple(getComponentArray()...); + auto nonOwnedArrays = std::make_tuple(); + return std::make_shared>(ownedArrays, nonOwnedArrays); + } + + template + auto createGroup(const get_t& nonOwned) { + using OwnedTuple = std::tuple>...>; + using NonOwnedTuple = std::tuple>...>; + auto ownedArrays = std::make_tuple(getComponentArray()...); + auto nonOwnedArrays = std::make_tuple(getComponentArray()...); + return std::make_shared>(ownedArrays, nonOwnedArrays); + } + + + + template + std::shared_ptr> getComponentArray() { + if constexpr (std::is_same_v) { + return positionArray; + } else if constexpr (std::is_same_v) { + return velocityArray; + } else if constexpr (std::is_same_v) { + return healthArray; + } else if constexpr (std::is_same_v) { + return tagArray; + } + } + + std::shared_ptr> positionArray; + std::shared_ptr> velocityArray; + std::shared_ptr> healthArray; + std::shared_ptr> tagArray; + }; + + // ======================================================================== + // Basic Functionality Tests + // ======================================================================== + + TEST_F(GroupTest, Construction) { + auto group = createGroup(get()); + + // Check that signatures are correctly set + EXPECT_TRUE(group->ownedSignature().test(getComponentTypeID())); + EXPECT_TRUE(group->ownedSignature().test(getComponentTypeID())); + EXPECT_FALSE(group->ownedSignature().test(getComponentTypeID())); + EXPECT_FALSE(group->ownedSignature().test(getComponentTypeID())); + + // Check all signature + EXPECT_TRUE(group->allSignature().test(getComponentTypeID())); + EXPECT_TRUE(group->allSignature().test(getComponentTypeID())); + EXPECT_TRUE(group->allSignature().test(getComponentTypeID())); + EXPECT_TRUE(group->allSignature().test(getComponentTypeID())); + + // Verify size + EXPECT_EQ(group->size(), 5); + } + + TEST_F(GroupTest, AddToGroup) { + auto group = createGroup(); + + // Initially the group has entities 0-4 + EXPECT_EQ(group->size(), 5); + + // Add entity 5 to the group + group->addToGroup(5); + EXPECT_EQ(group->size(), 6); + + // Check that entity 5 is in the group using the entities() method + auto entities = group->entities(); + EXPECT_EQ(entities.size(), 6); + EXPECT_NE(std::find(entities.begin(), entities.end(), 5), entities.end()); + } + + TEST_F(GroupTest, RemoveFromGroup) { + auto group = createGroup(); + + // Initially the group has entities 0-4 + EXPECT_EQ(group->size(), 5); + + // Remove entity 2 from the group + group->removeFromGroup(2); + EXPECT_EQ(group->size(), 4); + + // Check that entity 2 is no longer in the group + auto entities = group->entities(); + EXPECT_EQ(entities.size(), 4); + EXPECT_EQ(std::find(entities.begin(), entities.end(), 2), entities.end()); + } + + TEST_F(GroupTest, EntitiesMethod) { + auto group = createGroup(); + + // Check that entities() returns the correct entities + auto entities = group->entities(); + EXPECT_EQ(entities.size(), 5); + + // Entities should be 0-4 + for (Entity i = 0; i < 5; ++i) { + EXPECT_NE(std::find(entities.begin(), entities.end(), i), entities.end()); + } + + // Entity 5 should not be in the group + EXPECT_EQ(std::find(entities.begin(), entities.end(), 5), entities.end()); + } + + TEST_F(GroupTest, SortingInvalidatedFlag) { + auto group = createGroup(); + + // Initially sorting should be invalidated + EXPECT_TRUE(group->sortingInvalidated()); + + // Sort the group + group->sortBy([](const Position& p) { return p.x; }); + + // Sorting should no longer be invalidated + EXPECT_FALSE(group->sortingInvalidated()); + + // Adding a new entity should invalidate sorting + group->addToGroup(5); + EXPECT_TRUE(group->sortingInvalidated()); + + // Sort again + group->sortBy([](const Position& p) { return p.x; }); + EXPECT_FALSE(group->sortingInvalidated()); + + // Removing an entity should invalidate sorting + group->removeFromGroup(0); + EXPECT_TRUE(group->sortingInvalidated()); + } + + // ======================================================================== + // Iterator Tests + // ======================================================================== + + TEST_F(GroupTest, IteratorBasics) { + auto group = createGroup(); + + // Check begin() and end() + auto it = group->begin(); + auto end = group->end(); + + EXPECT_NE(it, end); + + // Advance iterator manually and check it eventually reaches end + std::size_t count = 0; + while (it != end) { + ++it; + ++count; + } + + EXPECT_EQ(count, 5); + } + + TEST_F(GroupTest, IteratorDereference) { + auto group = createGroup(); + + // Dereference the first iterator and check entity and position + auto it = group->begin(); + auto [entity, position] = *it; + + EXPECT_EQ(entity, 0); + EXPECT_FLOAT_EQ(position.x, 0.0f); + EXPECT_FLOAT_EQ(position.y, 0.0f); + + // Move to the next entity + ++it; + auto [entity2, position2] = *it; + + EXPECT_EQ(entity2, 1); + EXPECT_FLOAT_EQ(position2.x, 1.0f); + EXPECT_FLOAT_EQ(position2.y, 2.0f); + } + + TEST_F(GroupTest, RangeBasedFor) { + auto group = createGroup(); + + std::vector entities; + std::vector positions; + std::vector velocities; + + // Use range-based for loop to collect all entities and components + for (auto [entity, position, velocity] : *group) { + entities.push_back(entity); + positions.push_back(position); + velocities.push_back(velocity); + } + + // Check that we got all 5 entities + EXPECT_EQ(entities.size(), 5); + + // Check that the entities and components match + for (std::size_t i = 0; i < entities.size(); ++i) { + EXPECT_EQ(entities[i], i); + EXPECT_FLOAT_EQ(positions[i].x, i); + EXPECT_FLOAT_EQ(positions[i].y, i * 2); + EXPECT_FLOAT_EQ(velocities[i].x, i + 0.5f); + EXPECT_FLOAT_EQ(velocities[i].y, (i + 1) * 2); + } + } + + TEST_F(GroupTest, EachMethod) { + auto group = createGroup(get()); + + std::vector entities; + std::vector positions; + std::vector velocities; + std::vector healths; + + // Use each method to collect all entities and components + group->each([&](Entity entity, Position& position, Velocity& velocity, Health& health) { + entities.push_back(entity); + positions.push_back(position); + velocities.push_back(velocity); + healths.push_back(health); + }); + + // Check that we got all 5 entities + EXPECT_EQ(entities.size(), 5); + + // Check that the entities and components match + for (std::size_t i = 0; i < entities.size(); ++i) { + EXPECT_EQ(entities[i], i); + EXPECT_FLOAT_EQ(positions[i].x, i); + EXPECT_FLOAT_EQ(positions[i].y, i * 2); + EXPECT_FLOAT_EQ(velocities[i].x, i + 0.5f); + EXPECT_FLOAT_EQ(velocities[i].y, (i + 1) * 2); + EXPECT_EQ(healths[i], Health{100 - static_cast(i * 10)}); + } + } + + TEST_F(GroupTest, EachInRangeMethod) { + auto group = createGroup(); + + std::vector entities; + + // Use eachInRange to collect entities from index 1 to 3 + group->eachInRange(1, 3, [&](Entity entity, Position& position, Velocity& velocity) { + entities.push_back(entity); + }); + + // Check that we got entities 1, 2, and 3 + EXPECT_EQ(entities.size(), 3); + EXPECT_EQ(entities[0], 1); + EXPECT_EQ(entities[1], 2); + EXPECT_EQ(entities[2], 3); + } + + TEST_F(GroupTest, EachInRangeOutOfBounds) { + auto group = createGroup(); + + std::vector entities; + + // Start at a valid index but request too many elements + group->eachInRange(3, 10, [&](Entity entity, Position& position, Velocity& velocity) { + entities.push_back(entity); + }); + + // We should only get entities 3 and 4 (not out of bounds) + EXPECT_EQ(entities.size(), 2); + EXPECT_EQ(entities[0], 3); + EXPECT_EQ(entities[1], 4); + } + + // ======================================================================== + // Component Access Tests + // ======================================================================== + + TEST_F(GroupTest, GetMethod) { + auto group = createGroup(); + + // Get all positions + auto positions = group->get(); + + // Check that the positions are correct + EXPECT_EQ(positions.size(), 5); + for (std::size_t i = 0; i < positions.size(); ++i) { + EXPECT_FLOAT_EQ(positions[i].x, i); + EXPECT_FLOAT_EQ(positions[i].y, i * 2); + } + + // Get all velocities + auto velocities = group->get(); + + // Check that the velocities are correct + EXPECT_EQ(velocities.size(), 5); + for (std::size_t i = 0; i < velocities.size(); ++i) { + EXPECT_FLOAT_EQ(velocities[i].x, i + 0.5f); + EXPECT_FLOAT_EQ(velocities[i].y, (i + 1) * 2); + } + } + + TEST_F(GroupTest, GetMethodWithNonOwnedComponent) { + auto group = createGroup(get()); + + // Get all positions (owned) + auto positions = group->get(); + + // Check that the positions are correct + EXPECT_EQ(positions.size(), 5); + for (std::size_t i = 0; i < positions.size(); ++i) { + EXPECT_FLOAT_EQ(positions[i].x, i); + EXPECT_FLOAT_EQ(positions[i].y, i * 2); + } + + // Try to get velocities (non-owned) - this should compile but might have different behavior + // For our implementation, we still want to access the velocity components + auto velocities = group->get(); + + // The non-owned components should be accessible through the get method + EXPECT_GT(velocities->size(), 0); + } + + // ======================================================================== + // Sorting Tests + // ======================================================================== + + TEST_F(GroupTest, SortByPositionXAscending) { + auto group = createGroup(); + + // Add entities in reverse order to ensure sorting will change the order + for (Entity i = 9; i >= 5; --i) { + positionArray->addToGroup(i); + velocityArray->addToGroup(i); + } + + // Sort by position.x in ascending order + group->sortBy([](const Position& p) { return p.x; }); + + // Check that entities are sorted correctly + auto entities = group->entities(); + + // Entities should be in order 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + for (std::size_t i = 0; i < entities.size(); ++i) { + EXPECT_EQ(entities[i], i); + } + } + + TEST_F(GroupTest, SortByPositionXDescending) { + auto group = createGroup(); + + // Add more entities to ensure we have enough for a good test + for (Entity i = 5; i < 10; ++i) { + positionArray->addToGroup(i); + velocityArray->addToGroup(i); + } + + // Sort by position.x in descending order + group->sortBy([](const Position& p) { return p.x; }, false); + + // Check that entities are sorted correctly + auto entities = group->entities(); + + // Entities should be in reverse order 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + for (std::size_t i = 0; i < entities.size(); ++i) { + EXPECT_EQ(entities[i], 9 - i); + } + } + + TEST_F(GroupTest, SortByNonOwnedComponent) { + auto group = createGroup(get()); + + // Add more entities to ensure we have enough for a good test + for (Entity i = 5; i < 10; ++i) { + positionArray->addToGroup(i); + } + + // Sort by health value (non-owned) in ascending order (lower health first) + group->sortBy([](const Health& h) { return h.value; }); + + // Check that entities are sorted correctly + auto entities = group->entities(); + + // Health values are 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 + // So entities should be in reverse order 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + for (std::size_t i = 0; i < entities.size(); ++i) { + EXPECT_EQ(entities[i], 9 - i); + } + } + + TEST_F(GroupTest, SortSaveWithNoInvalidation) { + auto group = createGroup(); + + // Sort the group + group->sortBy([](const Position& p) { return p.x; }); + + // Get the current order + auto initialEntities = group->entities(); + + // Count function calls to check if sorting is skipped + int callCount = 0; + auto countingExtractor = [&callCount](const Position& p) { + callCount++; + return p.x; + }; + + // Call sort again - it should be skipped since sorting is not invalidated + group->sortBy(countingExtractor); + + // No extraction function calls should happen since sorting was skipped + EXPECT_EQ(callCount, 0); + + // Order should remain the same + auto newEntities = group->entities(); + EXPECT_TRUE(std::equal(initialEntities.begin(), initialEntities.end(), newEntities.begin())); + } + + // ======================================================================== + // Partitioning Tests + // ======================================================================== + + TEST_F(GroupTest, GetPartitionViewBasic) { + auto group = createGroup(); + + // Create a partition view based on tag name first character + auto partitionView = group->getPartitionView([](const Tag& tag) { + return tag.name.empty() ? ' ' : tag.name[0]; + }); + + // All entities have tags starting with 'E', so there should be one partition + EXPECT_EQ(partitionView.partitionCount(), 1); + + // Get the keys (should be 'E') + auto keys = partitionView.getPartitionKeys(); + EXPECT_EQ(keys.size(), 1); + EXPECT_EQ(keys[0], 'E'); + + // Get the partition for 'E' + const auto* partition = partitionView.getPartition('E'); + ASSERT_NE(partition, nullptr); + EXPECT_EQ(partition->key, 'E'); + EXPECT_EQ(partition->count, 5); + EXPECT_EQ(partition->startIndex, 0); + } + + TEST_F(GroupTest, MultiplePartitions) { + auto group = createGroup(); + + // Modify health values to create multiple partitions + // Group health values into three categories: Low (0-30), Medium (31-70), High (71-100) + groupHealthData(); + + // Create a partition view based on health category + auto partitionView = group->getPartitionView([](const Health& health) { + if (health.value <= 30) return "Low"; + else if (health.value <= 70) return "Medium"; + else return "High"; + }); + + // There should be three partitions + EXPECT_EQ(partitionView.partitionCount(), 3); + + // Get the keys + auto keys = partitionView.getPartitionKeys(); + EXPECT_EQ(keys.size(), 3); + EXPECT_NE(std::find(keys.begin(), keys.end(), "Low"), keys.end()); + EXPECT_NE(std::find(keys.begin(), keys.end(), "Medium"), keys.end()); + EXPECT_NE(std::find(keys.begin(), keys.end(), "High"), keys.end()); + + // Check each partition + const auto* lowPartition = partitionView.getPartition("Low"); + ASSERT_NE(lowPartition, nullptr); + + const auto* mediumPartition = partitionView.getPartition("Medium"); + ASSERT_NE(mediumPartition, nullptr); + + const auto* highPartition = partitionView.getPartition("High"); + ASSERT_NE(highPartition, nullptr); + } + + TEST_F(GroupTest, PartitionEachMethod) { + auto group = createGroup(); + + // Modify health values to create multiple partitions + groupHealthData(); + + // Create a partition view + auto partitionView = group->getPartitionView([](const Health& health) { + if (health.value <= 30) return "Low"; + else if (health.value <= 70) return "Medium"; + else return "High"; + }); + + // Collect entities in the "Low" partition + std::vector lowEntities; + partitionView.each("Low", [&](Entity entity, Position& position, Health& health) { + lowEntities.push_back(entity); + // Verify the health value is in the low range + EXPECT_LE(health.value, 30); + }); + + // Collect entities in the "High" partition + std::vector highEntities; + partitionView.each("High", [&](Entity entity, Position& position, Health& health) { + highEntities.push_back(entity); + // Verify the health value is in the high range + EXPECT_GE(health.value, 71); + }); + + // Check that we collected the right number of entities + const auto* lowPartition = partitionView.getPartition("Low"); + ASSERT_NE(lowPartition, nullptr); + EXPECT_EQ(lowEntities.size(), lowPartition->count); + + const auto* highPartition = partitionView.getPartition("High"); + ASSERT_NE(highPartition, nullptr); + EXPECT_EQ(highEntities.size(), highPartition->count); + } + + TEST_F(GroupTest, EntityPartitionView) { + auto group = createGroup(); + + // Create a partition view based directly on entity ID parity + auto partitionView = group->getEntityPartitionView( + "parity", + [](Entity e) { return e % 2 == 0 ? "Even" : "Odd"; } + ); + + // There should be two partitions: Even and Odd + EXPECT_EQ(partitionView.partitionCount(), 2); + + // Get the keys + auto keys = partitionView.getPartitionKeys(); + EXPECT_EQ(keys.size(), 2); + EXPECT_NE(std::find(keys.begin(), keys.end(), "Even"), keys.end()); + EXPECT_NE(std::find(keys.begin(), keys.end(), "Odd"), keys.end()); + + // Check each partition + std::vector evenEntities; + partitionView.each("Even", [&](Entity entity, Position& position, Velocity& velocity) { + evenEntities.push_back(entity); + // Verify the entity ID is even + EXPECT_EQ(entity % 2, 0); + }); + + std::vector oddEntities; + partitionView.each("Odd", [&](Entity entity, Position& position, Velocity& velocity) { + oddEntities.push_back(entity); + // Verify the entity ID is odd + EXPECT_EQ(entity % 2, 1); + }); + + // In our initial setup, we have entities 0, 1, 2, 3, 4 in the group + EXPECT_EQ(evenEntities.size(), 3); // 0, 2, 4 + EXPECT_EQ(oddEntities.size(), 2); // 1, 3 + } + + TEST_F(GroupTest, PartitionInvalidation) { + auto group = createGroup(); + + // Create a partition view + auto partitionView = group->getEntityPartitionView( + "parity", + [](Entity e) { return e % 2 == 0 ? "Even" : "Odd"; } + ); + + // Initial counts + auto* evenPartition = partitionView.getPartition("Even"); + auto* oddPartition = partitionView.getPartition("Odd"); + ASSERT_NE(evenPartition, nullptr); + ASSERT_NE(oddPartition, nullptr); + + size_t initialEvenCount = evenPartition->count; + size_t initialOddCount = oddPartition->count; + + // Add a new entity to the group + group->addToGroup(5); // Odd entity + + // Get the partition view again - should rebuild + auto updatedPartitionView = group->getEntityPartitionView( + "parity", + [](Entity e) { return e % 2 == 0 ? "Even" : "Odd"; } + ); + + // Check the new counts + evenPartition = updatedPartitionView.getPartition("Even"); + oddPartition = updatedPartitionView.getPartition("Odd"); + ASSERT_NE(evenPartition, nullptr); + ASSERT_NE(oddPartition, nullptr); + + // Even count should be the same, odd count should increase by 1 + EXPECT_EQ(evenPartition->count, initialEvenCount); + EXPECT_EQ(oddPartition->count, initialOddCount + 1); + } + + // ======================================================================== + // Edge Cases and Error Handling + // ======================================================================== + + TEST_F(GroupTest, EmptyGroup) { + // Clear all entities from component arrays + positionArray = std::make_shared>(); + velocityArray = std::make_shared>(); + + // Create an empty group + auto group = createGroup(); + + // Check size and entities + EXPECT_EQ(group->size(), 0); + EXPECT_EQ(group->entities().size(), 0); + + // Check iterator + EXPECT_EQ(group->begin(), group->end()); + + // Ensure each method doesn't error on empty group + int callCount = 0; + group->each([&](Entity, Position&, Velocity&) { callCount++; }); + EXPECT_EQ(callCount, 0); + + // Ensure get method returns empty span + auto positions = group->get(); + EXPECT_EQ(positions.size(), 0); + + // Ensure sorting doesn't error + group->sortBy([](const Position& p) { return p.x; }); + + // Create partition view + auto partitionView = group->getPartitionView([](const Position& p) { return p.x; }); + + // Should have no partitions + EXPECT_EQ(partitionView.partitionCount(), 0); + } + + TEST_F(GroupTest, RemoveNonExistingEntity) { + auto group = createGroup(); + + // Initially the group has entities 0-4 + EXPECT_EQ(group->size(), 5); + + // Try to remove an entity that's not in the group + EXPECT_THROW(group->removeFromGroup(100), ComponentNotFound); + + // Size should remain the same + EXPECT_EQ(group->size(), 5); + } + + TEST_F(GroupTest, FindNonExistingPartition) { + auto group = createGroup(); + + // Create a partition view + auto partitionView = group->getPartitionView([](const Tag& tag) { + return tag.name.empty() ? ' ' : tag.name[0]; + }); + + // Try to get a partition that doesn't exist + const auto* nonExistingPartition = partitionView.getPartition('Z'); + EXPECT_EQ(nonExistingPartition, nullptr); + + // Try to iterate over a non-existing partition + int callCount = 0; + partitionView.each('Z', [&](Entity, Position&, Tag&) { callCount++; }); + EXPECT_EQ(callCount, 0); + } +}; diff --git a/tests/ecs/System.test.cpp b/tests/ecs/System.test.cpp index 015fb8bc5..750930a38 100644 --- a/tests/ecs/System.test.cpp +++ b/tests/ecs/System.test.cpp @@ -15,7 +15,7 @@ #include #include #include "ecs/System.hpp" -#include "ecs/Signature.hpp" +#include "ecs/Definitions.hpp" namespace nexo::ecs { // Mock system for testing From 974d922cde442ba416e6d3b3a47faa03badbf4e9 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 3 Apr 2025 14:35:30 +0900 Subject: [PATCH 027/450] fix(ecs-opti): add include header for windows --- engine/src/ecs/ComponentArray.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index c00c15b23..2e2e397c2 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -19,6 +19,7 @@ #include "Logger.hpp" #include +#include namespace nexo::ecs { /** From 6bccee5545c44772d87d43f18e6dc567f7d77007 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 16:00:41 +0900 Subject: [PATCH 028/450] refactor(ecs-opti): increase the max number of entities --- engine/src/ecs/Definitions.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/ecs/Definitions.hpp b/engine/src/ecs/Definitions.hpp index 7d3b0d47e..df0d406b4 100644 --- a/engine/src/ecs/Definitions.hpp +++ b/engine/src/ecs/Definitions.hpp @@ -22,7 +22,7 @@ namespace nexo::ecs { // Entity type definition using Entity = std::uint32_t; - constexpr Entity MAX_ENTITIES = 80000; + constexpr Entity MAX_ENTITIES = 500000; constexpr Entity INVALID_ENTITY = std::numeric_limits::max(); // Component type definitions From 73de069289b88eba6a93957812a8505f579460c9 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 16:00:59 +0900 Subject: [PATCH 029/450] feat(ecs-opti): add new query system --- engine/src/ecs/QuerySystem.hpp | 119 +++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 engine/src/ecs/QuerySystem.hpp diff --git a/engine/src/ecs/QuerySystem.hpp b/engine/src/ecs/QuerySystem.hpp new file mode 100644 index 000000000..1698ad7c6 --- /dev/null +++ b/engine/src/ecs/QuerySystem.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include "System.hpp" +#include "Access.hpp" +#include "ComponentArray.hpp" +#include "Coordinator.hpp" +#include +#include +#include + +namespace nexo::ecs { + /** + * @class QuerySystem + * @brief System that directly queries component arrays + * + * @tparam Components Component access specifiers (Read or Write) + */ + template + class QuerySystem : public AQuerySystem { + private: + // Helper template to check if a component type exists in the parameter pack with Read access + template + struct HasReadAccess { + static constexpr bool value = + (std::is_same_v> || + HasReadAccess::value); + }; + + // Base case for the template recursion + template + struct HasReadAccess { + static constexpr bool value = std::is_same_v>; + }; + + // Convenience function to check read access + template + static constexpr bool hasReadAccess() { + return HasReadAccess::value; + } + + public: + QuerySystem() { + if (!coord) return; + + // Set system signature based on required components + (setComponentSignature(m_signature), ...); + + // Store component arrays for faster access + (cacheComponentArray(), ...); + } + + /** + * @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 + typename std::conditional(), const T&, T&>::type + getComponent(Entity entity) { + auto typeIndex = getTypeIndex(); + auto componentArray = std::static_pointer_cast>(m_componentArrays[typeIndex]); + + if constexpr (hasReadAccess()) { + return componentArray->getData(entity); + } else { + return componentArray->getData(entity); + } + } + + const Signature &getSignature() const { + return m_signature; + } + + Signature &getSignature() { + return m_signature; + } + + protected: + /** + * @brief Caches component arrays for faster access + * + * @tparam ComponentAccessType The component access type to cache + */ + template + void cacheComponentArray() { + using T = typename ComponentAccessType::ComponentType; + m_componentArrays[getTypeIndex()] = coord->getComponentArray(); + } + + /** + * @brief Sets the component bit in the system signature + * + * @tparam T The component type + * @param signature The signature to modify + */ + template + void setComponentSignature(Signature& signature) { + signature.set(coord->getComponentType(), true); + } + + /** + * @brief Gets the type index for a component + * + * @tparam T The component type + * @return std::type_index The type index + */ + template + std::type_index getTypeIndex() const { + return std::type_index(typeid(T)); + } + + private: + // Cache of component arrays for faster access + std::unordered_map> m_componentArrays; + Signature m_signature; + }; +} From 148af63896e67bbea8bf49c674a40b5430851dca Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 16:01:13 +0900 Subject: [PATCH 030/450] feat(ecs-opti): add new group system --- engine/src/ecs/GroupSystem.hpp | 230 +++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 engine/src/ecs/GroupSystem.hpp diff --git a/engine/src/ecs/GroupSystem.hpp b/engine/src/ecs/GroupSystem.hpp new file mode 100644 index 000000000..577f75423 --- /dev/null +++ b/engine/src/ecs/GroupSystem.hpp @@ -0,0 +1,230 @@ +#pragma once + +#include "System.hpp" +#include "Access.hpp" +#include "Group.hpp" +#include "ComponentArray.hpp" +#include "Coordinator.hpp" +#include "Access.hpp" +#include +#include +#include +#include + +namespace nexo::ecs { + + /** + * @class GroupSystem + * @brief System that uses component groups for optimized access with enforced permissions + * + * @tparam OwnedAccess Owned<> wrapper with component access types + * @tparam NonOwnedAccess NonOwned<> wrapper with component access types + */ + template> + class GroupSystem : public AGroupSystem { + 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 AccessType accessType = AccessType::Read; + }; + + template + struct ComponentAccessTrait, + std::void_t || ...)>>> { + static constexpr bool found = true; + + template + static constexpr AccessType GetAccessTypeFromPack() { + AccessType 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 + */ + template + class ComponentSpan { + private: + std::span m_span; + + public: + explicit ComponentSpan(std::span span) : m_span(span) {} + + size_t size() const { return m_span.size(); } + + // Conditionally define operator[] based on access type + template + auto operator[](size_t index) -> std::conditional_t< + GetComponentAccess>::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]; + } + } + + template + auto operator[](size_t index) const -> const std::remove_const_t& { + return m_span[index]; + } + + // Iterator support + auto begin() { return m_span.begin(); } + auto end() { return m_span.end(); } + auto begin() const { return m_span.begin(); } + auto end() const { return m_span.end(); } + }; + + public: + GroupSystem() { + if (!coord) return; + + // Create the group + m_group = createGroupImpl( + tupleToTypeList(), + tupleToTypeList() + ); + } + + /** + * @brief Get component array with correct access permissions + * + * @tparam T The component type + * @return ComponentSpan with enforced access control + */ + template + auto get() { + // 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); + } + + /** + * @brief Get a component for an entity with appropriate access control + * + * @tparam AccessType The component access type (Read or Write) + * @param entity The entity to get the component for + * @return Component reference with appropriate const-ness + */ + template + auto getComponent(Entity entity) { + using T = typename AccessType::ComponentType; + + if constexpr (AccessType::accessType == AccessType::Read) { + return std::cref(m_group->template get(entity)); + } else { + return std::ref(m_group->template get(entity)); + } + } + + /** + * @brief Get all entities in this group + * + * @return Span of entities in the group + */ + auto getEntities() const { + return m_group->entities(); + } + + /** + * @brief Get direct access to the underlying group + * + * @return The group object + */ + auto& group() const { + return *m_group; + } + + protected: + std::shared_ptr m_group; + + private: + /** + * @brief Implementation to create a group with the extracted component types + */ + 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()); + return std::static_pointer_cast(group); + } else { + auto group = coord->registerGroup(nexo::ecs::get<>()); + return std::static_pointer_cast(group); + } + } + return nullptr; + } + }; +} From 49818d395260771a68ac428323bb20e0ba0d6625 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 16:01:34 +0900 Subject: [PATCH 031/450] feat(ecs-opti): add some helper templates for query and group system --- engine/src/ecs/Access.hpp | 93 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 engine/src/ecs/Access.hpp diff --git a/engine/src/ecs/Access.hpp b/engine/src/ecs/Access.hpp new file mode 100644 index 000000000..1ea60364a --- /dev/null +++ b/engine/src/ecs/Access.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include + +namespace nexo::ecs { + /** + * @brief Access type for components in systems + */ + enum class AccessType { + Read, ///< Read-only access + Write ///< Read-write access + }; + + /** + * @brief Template to specify component access type + * + * @tparam T The component type + * @tparam Access The access type (Read or Write) + */ + template + struct ComponentAccess { + using ComponentType = T; + static constexpr AccessType accessType = Access; + }; + + /** + * @brief Type alias for read-only component access + */ + template + using Read = ComponentAccess; + + /** + * @brief Type alias for read-write component access + */ + template + using Write = ComponentAccess; + + /** + * @brief Type wrapper for owned components in a group system + * + * @tparam Components Component access types (Read or Write) + */ + template + struct Owned { + using ComponentTypes = std::tuple; + }; + + /** + * @brief Type wrapper for non-owned components in a group system + * + * @tparam Components Component access types (Read or Write) + */ + template + struct NonOwned { + using ComponentTypes = std::tuple; + }; + + /** + * @brief Helper to extract component types from access types + * + * @tparam AccessTypes Pack of component access types + */ + template + struct ExtractComponentTypes; + + /** + * @brief Specialization for extracting component types + */ + template + struct ExtractComponentTypes> { + using Types = std::tuple; + }; + + /** + * @brief Helper to convert a tuple of component access types to a parameter pack + */ + template + void tuple_for_each_impl(Tuple&& tuple, Func&& func, std::index_sequence) { + (func(std::get(std::forward(tuple))), ...); + } + + /** + * @brief Apply a function to each element of a tuple + */ + template + void tuple_for_each(Tuple&& tuple, Func&& func) { + tuple_for_each_impl( + std::forward(tuple), + std::forward(func), + std::make_index_sequence>>{} + ); + } +} From b32d6357a09d8663d92aea52857d0bfeedd7837a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 16:01:57 +0900 Subject: [PATCH 032/450] feat(ecs-opti): add method to create query and group system --- engine/src/ecs/Coordinator.hpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 1acc77989..c1d11e7c3 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -266,6 +266,30 @@ namespace nexo::ecs { return m_systemManager->registerSystem(); } + /** + * @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) { + return m_systemManager->registerQuerySystem(std::forward(args)...); + } + + /** + * @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)...); + } + template auto registerGroup(const auto & nonOwned) { From ec16ce1ccfdac0703f8a0126612608eacf05e2cf Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 16:02:59 +0900 Subject: [PATCH 033/450] feat(ecs-opti): add two new maps to store query and group systems + feat(ecs-opti): add utils method to the sparse set class --- engine/src/ecs/System.hpp | 108 ++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/engine/src/ecs/System.hpp b/engine/src/ecs/System.hpp index 1d707df13..6fbcb0b37 100644 --- a/engine/src/ecs/System.hpp +++ b/engine/src/ecs/System.hpp @@ -30,45 +30,15 @@ namespace nexo::ecs { class SparseSet { public: - void insert(Entity entity) - { - if (contains(entity)) - { - LOG(NEXO_WARN, "Entity {} already added to the sparse set", entity); - return; - } - - sparse[entity] = dense.size(); - dense.push_back(entity); - } + void insert(Entity entity); - void erase(Entity entity) - { - if (!contains(entity)) - { - LOG(NEXO_WARN, "Entity {} does not exist in the sparse set", entity); - return; - } - - size_t index = sparse[entity]; - size_t lastIndex = dense.size() - 1; - Entity lastEntity = dense[lastIndex]; - - dense[index] = lastEntity; - sparse[lastEntity] = index; - dense.pop_back(); - sparse.erase(entity); - } + void erase(Entity entity); - bool empty() const - { - return dense.empty(); - } + bool empty() const { return dense.empty(); } - bool contains(Entity entity) const - { - return sparse.find(entity) != sparse.end(); - } + bool contains(Entity entity) const { return sparse.find(entity) != sparse.end(); } + + size_t size() const { return dense.size(); } const std::vector& getDense() const { return dense; } auto begin() const { return dense.begin(); } @@ -78,6 +48,7 @@ namespace nexo::ecs { std::vector dense; std::unordered_map sparse; }; + /** * @class System * @@ -88,8 +59,28 @@ namespace nexo::ecs { */ class System { public: - SparseSet entities; static std::shared_ptr coord; + SparseSet entities; + + }; + + /** + * @class AQuerySystem + * @brief Base abstract for all query-based systems + */ + class AQuerySystem : public System { + public: + virtual ~AQuerySystem() = default; + virtual const Signature &getSignature() const = 0; + }; + + /** + * @class AGroupSystem + * @brief Abstract base class for all group-based systems + */ + class AGroupSystem : public System { + public: + virtual ~AGroupSystem() = default; }; /** @@ -124,6 +115,44 @@ namespace nexo::ecs { return system; } + /** + * @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) + { + 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; + } + + template + std::shared_ptr registerGroupSystem(Args&&... args) + { + 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; + } + /** * @brief Sets the signature for a system. * @@ -136,9 +165,6 @@ namespace nexo::ecs { { std::type_index typeName(typeid(T)); - if (!m_systems.contains(typeName)) - THROW_EXCEPTION(SystemNotRegistered); - m_signatures.insert({typeName, signature}); } @@ -162,5 +188,7 @@ namespace nexo::ecs { private: std::unordered_map m_signatures{}; std::unordered_map> m_systems{}; + std::unordered_map> m_querySystems{}; + std::unordered_map> m_groupSystems{}; }; } From ef34c1700223afefdce19819dbf8e0b6d54ea395 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 16:03:50 +0900 Subject: [PATCH 034/450] refactor(ecs-opti): moved some sparse set method to the source file + refactor(ecs-opti): now only updates the query system when a component state is updated --- engine/src/ecs/System.cpp | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/engine/src/ecs/System.cpp b/engine/src/ecs/System.cpp index 958284cf7..81259bffb 100644 --- a/engine/src/ecs/System.cpp +++ b/engine/src/ecs/System.cpp @@ -16,12 +16,42 @@ namespace nexo::ecs { + void SparseSet::insert(Entity entity) + { + if (contains(entity)) + { + LOG(NEXO_WARN, "Entity {} already added to the sparse set", entity); + return; + } + + sparse[entity] = dense.size(); + dense.push_back(entity); + } + + void SparseSet::erase(Entity entity) + { + if (!contains(entity)) + { + LOG(NEXO_WARN, "Entity {} does not exist in the sparse set", entity); + return; + } + + size_t index = sparse[entity]; + size_t lastIndex = dense.size() - 1; + Entity lastEntity = dense[lastIndex]; + + dense[index] = lastEntity; + sparse[lastEntity] = index; + dense.pop_back(); + sparse.erase(entity); + } + void SystemManager::entityDestroyed(const Entity entity, const Signature signature) const { - for (const auto &[fst, snd] : m_systems) { + for (const auto &[fst, snd] : m_querySystems) { auto const &type = fst; auto const &system = snd; - if (auto const &systemSignature = m_signatures.at(type); (signature & systemSignature) == systemSignature) + if (auto const &systemSignature = system->getSignature(); (signature & systemSignature) == systemSignature) system->entities.erase(entity); } } @@ -30,8 +60,8 @@ namespace nexo::ecs { const Signature oldSignature, const Signature newSignature) { - for (auto& [type, system] : m_systems) { - const auto systemSignature = m_signatures.at(type); + for (auto& [type, system] : m_querySystems) { + const auto systemSignature = system->getSignature(); // Check if entity qualifies now but did not qualify before. if (((oldSignature & systemSignature) != systemSignature) && ((newSignature & systemSignature) == systemSignature)) { From 8b0a13473f2b4f5b86fa54dfc537d837d681d589 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 16:04:10 +0900 Subject: [PATCH 035/450] feat(ecs-opti): update the example to showcase the new systems --- examples/ecs/exampleBasic.cpp | 955 +++++++--------------------------- 1 file changed, 198 insertions(+), 757 deletions(-) diff --git a/examples/ecs/exampleBasic.cpp b/examples/ecs/exampleBasic.cpp index 8b9446a79..a865413fa 100644 --- a/examples/ecs/exampleBasic.cpp +++ b/examples/ecs/exampleBasic.cpp @@ -1,636 +1,195 @@ -//// exampleAdvanced.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: 03/04/2025 -// Description: Comprehensive example showcasing various ECS features -// highlighting proper usage of group ownership -// -/////////////////////////////////////////////////////////////////////////////// #include #include #include -#include #include -#include #include #include -#include #include "ecs/Coordinator.hpp" +#include "ecs/QuerySystem.hpp" +#include "ecs/GroupSystem.hpp" -// ============================================================================ // Component Definitions -// ============================================================================ - -struct Transform { +struct Position { float x = 0.0f; float y = 0.0f; - float rotation = 0.0f; - float scale = 1.0f; -}; - -struct Physics { - float velocityX = 0.0f; - float velocityY = 0.0f; - float mass = 1.0f; - bool hasGravity = true; -}; - -struct Renderable { - enum class Type { Circle, Rectangle, Triangle, Custom }; - Type type = Type::Rectangle; - int zIndex = 0; - float r = 1.0f; - float g = 1.0f; - float b = 1.0f; - float a = 1.0f; }; -struct AI { - float detectionRadius = 100.0f; - int behaviorType = 0; // 0: patrol, 1: chase, 2: flee, etc. - float reactionTime = 0.5f; - float timeSinceLastDecision = 0.0f; -}; - -struct Health { - int current = 100; - int maximum = 100; - float regenRate = 0.0f; - bool isDead = false; -}; - -struct Tag { - enum class Layer { Player, Enemy, Neutral, Projectile, Environment }; - Layer layer = Layer::Neutral; - std::string name = "Entity"; - int id = 0; -}; - -struct Lifetime { - float timeRemaining = 0.0f; - bool shouldDestroy = false; +struct Velocity { + float x = 0.0f; + float y = 0.0f; }; -// Helper to format log messages -void log(const std::string& message) { +void log(const std::string& message) +{ std::cout << message << std::endl; } -// ============================================================================ -// System Implementations -// ============================================================================ - -// Example 1: Physics system, this demonstrates the use of groups in order to efficiently access multiple owned components together -class PhysicsSystem : public nexo::ecs::System { -public: - PhysicsSystem() { - // Create a group that owns both Transform and Physics - // This is the correct way to handle frequently accessed components together - movementGroup = coord->registerGroup(nexo::ecs::get<>()); - } - - void update(float deltaTime) { - const float GRAVITY = 9.8f; - - log("Physics update started for " + std::to_string(movementGroup->size()) + " entities"); - - // ITERATION METHOD 1: Using range-based for loop - // This demonstrates how to access multiple owned components efficiently - for (auto [entity, transform, physics] : *movementGroup) { - // Apply velocity to position - transform.x += physics.velocityX * deltaTime; - transform.y += physics.velocityY * deltaTime; - - // Apply gravity if enabled - if (physics.hasGravity) { - physics.velocityY += GRAVITY * physics.mass * deltaTime; - } - - // Apply damping - physics.velocityX *= 0.99f; - physics.velocityY *= 0.99f; - - // Boundary checking - if (transform.y < -100.0f) { - transform.y = -100.0f; - physics.velocityY = -physics.velocityY * 0.8f; // Bounce - } - } - - log("Physics update completed"); - } - - // Method to show different ways to access group data - void showPerformanceExamples(float deltaTime) { - log("Demonstrating different access methods for physics calculations:"); - const float GRAVITY = 9.8f; - - // METHOD 1: RANGE-BASED FOR LOOP (Already shown in update method) - auto start1 = std::chrono::high_resolution_clock::now(); - for (auto [entity, transform, physics] : *movementGroup) { - // Apply velocity to position - transform.x += physics.velocityX * deltaTime; - transform.y += physics.velocityY * deltaTime; - - // Apply gravity if enabled - if (physics.hasGravity) { - physics.velocityY += GRAVITY * physics.mass * deltaTime; - } - - // Apply damping - physics.velocityX *= 0.99f; - physics.velocityY *= 0.99f; - - // Boundary checking - if (transform.y < -100.0f) { - transform.y = -100.0f; - physics.velocityY = -physics.velocityY * 0.8f; // Bounce - } - } - auto end1 = std::chrono::high_resolution_clock::now(); - - // METHOD 2: USING EACH METHOD WITH LAMBDA - auto start2 = std::chrono::high_resolution_clock::now(); - - log("Method 2: Using each() with lambda"); - movementGroup->each([deltaTime, GRAVITY](nexo::ecs::Entity entity, Transform& transform, Physics& physics) { - // Same physics calculations as above - transform.x += physics.velocityX * deltaTime; - transform.y += physics.velocityY * deltaTime; - // Apply gravity if enabled - if (physics.hasGravity) { - physics.velocityY += GRAVITY * physics.mass * deltaTime; - } - - // Apply damping - physics.velocityX *= 0.99f; - physics.velocityY *= 0.99f; - - // Boundary checking - if (transform.y < -100.0f) { - transform.y = -100.0f; - physics.velocityY = -physics.velocityY * 0.8f; // Bounce - } - }); - - auto end2 = std::chrono::high_resolution_clock::now(); - - // METHOD 3: DIRECT SPAN ACCESS (Most efficient for tight loops) - auto start3 = std::chrono::high_resolution_clock::now(); - - log("Method 3: Using direct span access (most efficient)"); - auto transformSpan = movementGroup->get(); - auto physicsSpan = movementGroup->get(); - - for (size_t i = 0; i < transformSpan.size(); i++) { - transformSpan[i].x += physicsSpan[i].velocityX * deltaTime; - transformSpan[i].y += physicsSpan[i].velocityY * deltaTime; - - if (physicsSpan[i].hasGravity) { - physicsSpan[i].velocityY += GRAVITY * physicsSpan[i].mass * deltaTime; - } - - // Apply damping - physicsSpan[i].velocityX *= 0.99f; - physicsSpan[i].velocityY *= 0.99f; - } - - auto end3 = std::chrono::high_resolution_clock::now(); - - // METHOD 4: SIMD PROCESSING (Further optimization with vectorization, not extremly pertinent for this case, but it demonstrates that it is possible) - auto start4 = std::chrono::high_resolution_clock::now(); - - log("Method 4: Using SIMD instructions for vectorized processing"); - - transformSpan = movementGroup->get(); - physicsSpan = movementGroup->get(); - const size_t size = transformSpan.size(); - - // Process in chunks of 4 elements using SSE instructions - const __m128 dt = _mm_set1_ps(deltaTime); // [dt, dt, dt, dt] - const __m128 gravity = _mm_set1_ps(GRAVITY); // [9.8, 9.8, 9.8, 9.8] - const __m128 damping = _mm_set1_ps(0.99f); // [0.99, 0.99, 0.99, 0.99] - const __m128 bounce = _mm_set1_ps(-0.8f); // [-0.8, -0.8, -0.8, -0.8] - const __m128 floor = _mm_set1_ps(-100.0f); // [-100, -100, -100, -100] - - // Process chunks of 4 elements at once - size_t i = 0; - for (; i + 3 < size; i += 4) { - // Load 4 x and y positions - __m128 posX = _mm_set_ps( - transformSpan[i+3].x, transformSpan[i+2].x, - transformSpan[i+1].x, transformSpan[i].x - ); - - __m128 posY = _mm_set_ps( - transformSpan[i+3].y, transformSpan[i+2].y, - transformSpan[i+1].y, transformSpan[i].y - ); - - // Load 4 x and y velocities - __m128 velX = _mm_set_ps( - physicsSpan[i+3].velocityX, physicsSpan[i+2].velocityX, - physicsSpan[i+1].velocityX, physicsSpan[i].velocityX - ); - - __m128 velY = _mm_set_ps( - physicsSpan[i+3].velocityY, physicsSpan[i+2].velocityY, - physicsSpan[i+1].velocityY, physicsSpan[i].velocityY - ); - - // Load 4 mass values - __m128 mass = _mm_set_ps( - physicsSpan[i+3].mass, physicsSpan[i+2].mass, - physicsSpan[i+1].mass, physicsSpan[i].mass - ); - - // Load 4 hasGravity flags (as floats: 1.0f for true, 0.0f for false) - __m128 hasGravity = _mm_set_ps( - physicsSpan[i+3].hasGravity ? 1.0f : 0.0f, - physicsSpan[i+2].hasGravity ? 1.0f : 0.0f, - physicsSpan[i+1].hasGravity ? 1.0f : 0.0f, - physicsSpan[i].hasGravity ? 1.0f : 0.0f - ); - - // Calculate velocity * deltaTime - __m128 deltaX = _mm_mul_ps(velX, dt); - __m128 deltaY = _mm_mul_ps(velY, dt); - - // Update positions: pos += vel * dt - posX = _mm_add_ps(posX, deltaX); - posY = _mm_add_ps(posY, deltaY); - - // Apply gravity: vel_y += gravity * mass * dt * hasGravity - __m128 gravityEffect = _mm_mul_ps(gravity, mass); // gravity * mass - gravityEffect = _mm_mul_ps(gravityEffect, dt); // * dt - gravityEffect = _mm_mul_ps(gravityEffect, hasGravity); // * hasGravity - velY = _mm_add_ps(velY, gravityEffect); // velY += ... - - // Apply damping: vel *= 0.99 - velX = _mm_mul_ps(velX, damping); - velY = _mm_mul_ps(velY, damping); - - // Boundary check: if (posY < -100.0) { posY = -100.0; velY *= -0.8; } - __m128 belowFloor = _mm_cmplt_ps(posY, floor); // posY < floor? - __m128 bounceVel = _mm_mul_ps(velY, bounce); // velY * -0.8 - velY = _mm_or_ps( - _mm_and_ps(belowFloor, bounceVel), // if below floor: use bounce vel - _mm_andnot_ps(belowFloor, velY) // else: keep original vel - ); - posY = _mm_or_ps( - _mm_and_ps(belowFloor, floor), // if below floor: use floor value - _mm_andnot_ps(belowFloor, posY) // else: keep original pos - ); - - // Store back the results - float posXResult[4], posYResult[4], velXResult[4], velYResult[4]; - _mm_storeu_ps(posXResult, posX); - _mm_storeu_ps(posYResult, posY); - _mm_storeu_ps(velXResult, velX); - _mm_storeu_ps(velYResult, velY); - - for (size_t j = 0; j < 4; j++) { - transformSpan[i+j].x = posXResult[j]; - transformSpan[i+j].y = posYResult[j]; - physicsSpan[i+j].velocityX = velXResult[j]; - physicsSpan[i+j].velocityY = velYResult[j]; - } - } - - // Process remaining elements normally - for (; i < size; i++) { - transformSpan[i].x += physicsSpan[i].velocityX * deltaTime; - transformSpan[i].y += physicsSpan[i].velocityY * deltaTime; - - if (physicsSpan[i].hasGravity) { - physicsSpan[i].velocityY += GRAVITY * physicsSpan[i].mass * deltaTime; - } - - physicsSpan[i].velocityX *= 0.99f; - physicsSpan[i].velocityY *= 0.99f; - - if (transformSpan[i].y < -100.0f) { - transformSpan[i].y = -100.0f; - physicsSpan[i].velocityY *= -0.8f; - } - } - - auto end4 = std::chrono::high_resolution_clock::now(); - - // Display timing results - auto duration1 = std::chrono::duration_cast(end1 - start1).count(); - auto duration2 = std::chrono::duration_cast(end2 - start2).count(); - auto duration3 = std::chrono::duration_cast(end3 - start3).count(); - auto duration4 = std::chrono::duration_cast(end4 - start4).count(); - - log("Performance comparison:"); - log(" Range-based for: " + std::to_string(duration1) + " μs"); - log(" each() method: " + std::to_string(duration2) + " μs"); - log(" Direct span: " + std::to_string(duration3) + " μs"); - log(" SIMD processing: " + std::to_string(duration4) + " μs"); - } - -private: - // This group properly owns both Transform and Physics together since they're - // frequently accessed together in the physics update - nexo::ecs::GroupAlias< - nexo::ecs::OwnedComponents, - nexo::ecs::NonOwnedComponents<> - > movementGroup = nullptr; +// This query system will increment the position component by the velocity component for each entity having both +// Position is marked as a write component and Velocity as a read component +// This enforces constness at compile time to prevent accidental modification of Velocity +// 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 +> { + 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(); + + for (int i = 0; i < numIterations; i++) { + 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; + } }; -// Example 2: Render system demonstrating sorting and partitioning -class RenderSystem : public nexo::ecs::System { -public: - RenderSystem() { - // Create a group that owns Renderable but accesses Transform as non-owned - // This is appropriate because rendering needs to sort/organize by Renderable properties - // but Transform is already owned by the PhysicsSystem - renderGroup = coord->registerGroup(nexo::ecs::get()); - } - - void render() { - log("Render pass started for " + std::to_string(renderGroup->size()) + " entities"); - - // SORTING EXAMPLE: Sort by z-index for proper render order - // This shows how owning the Renderable component allows efficient sorting - renderGroup->sortBy( - [](const Renderable& renderable) { return renderable.zIndex; } - ); - - // PARTITIONING EXAMPLE: Group by renderable type for batch rendering - auto typePartitions = renderGroup->getPartitionView( - [](const Renderable& renderable) { return renderable.type; } - ); - - log("Rendering in " + std::to_string(typePartitions.partitionCount()) + " batches"); - - // Process each partition separately (batch rendering) - auto keys = typePartitions.getPartitionKeys(); - for (auto type : keys) { - std::string typeName; - switch (type) { - case Renderable::Type::Circle: typeName = "Circles"; break; - case Renderable::Type::Rectangle: typeName = "Rectangles"; break; - case Renderable::Type::Triangle: typeName = "Triangles"; break; - case Renderable::Type::Custom: typeName = "Custom shapes"; break; - } - - const auto* partition = typePartitions.getPartition(type); - if (partition) { - log(" Batch rendering " + std::to_string(partition->count) + " " + typeName); - - // Example of using each() on a partition for batch rendering - typePartitions.each(type, [](nexo::ecs::Entity entity, Renderable& renderable, Transform& transform, Tag& tag) { - // Simulated rendering code - std::string typeName; - switch (renderable.type) { - case Renderable::Type::Circle: typeName = "Circle"; break; - case Renderable::Type::Rectangle: typeName = "Rectangle"; break; - case Renderable::Type::Triangle: typeName = "Triangle"; break; - case Renderable::Type::Custom: typeName = "Custom shape"; break; - } - std::cout << " - Rendered " << typeName << " " << tag.name << " at (" - << transform.x << ", " << transform.y - << ") with z-index " << renderable.zIndex << "\n"; - }); - } - } - - log("Render pass completed"); - } - -private: - nexo::ecs::GroupAlias< - nexo::ecs::OwnedComponents, - nexo::ecs::NonOwnedComponents - > renderGroup = nullptr; +// This a basic full-owning group system +// At startup, the system will automatically create a group of entities with Position and Velocity components +// 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. +// 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<> +> { + 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([](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(); + + 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(); + + 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; + } }; -// Example 3: AI and health system demonstrating component addition/removal -class AISystem : public nexo::ecs::System { -public: - AISystem() { - // AI and Health components are frequently accessed together in AI logic, - // so they're owned by the same group - aiGroup = coord->registerGroup(nexo::ecs::get()); - } - - void update(float deltaTime) { - log("AI update started for " + std::to_string(aiGroup->size()) + " entities"); - - // Keep track of entities that need component changes - std::vector entitiesToAddLifetime; - std::vector entitiesToSpawnProjectile; - - // Random number generation for AI decisions - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution<> posDist(-100.0, 100.0); - std::uniform_int_distribution<> decisionDist(0, 10); - - // PARTITIONING EXAMPLE: Create partitions by behavior type - auto behaviorPartitions = aiGroup->getPartitionView( - [](const AI& ai) { return ai.behaviorType; } - ); - - // Process each behavior type batch separately - for (int behaviorType = 0; behaviorType <= 2; behaviorType++) { - std::string behaviorName; - switch (behaviorType) { - case 0: behaviorName = "Patrol"; break; - case 1: behaviorName = "Chase"; break; - case 2: behaviorName = "Flee"; break; - default: behaviorName = "Unknown"; break; - } - - const auto* partition = behaviorPartitions.getPartition(behaviorType); - if (!partition) { - continue; - } - - log(" Processing " + std::to_string(partition->count) + " entities with " + behaviorName + " behavior"); - - // Process entities with this behavior type - behaviorPartitions.each(behaviorType, [&]( - nexo::ecs::Entity entity, - AI& ai, - Health& health, - Transform& transform, - Tag& tag - ) { - // Update AI decision timer - ai.timeSinceLastDecision += deltaTime; - - // Only make decisions at specific intervals based on reaction time - if (ai.timeSinceLastDecision >= ai.reactionTime) { - ai.timeSinceLastDecision = 0.0f; - - // Change behavior based on health - if (health.current < health.maximum * 0.2f && ai.behaviorType != 2) { - log(" " + tag.name + " is low on health, switching to Flee behavior"); - ai.behaviorType = 2; // Flee - } - else if (health.current > health.maximum * 0.8f && ai.behaviorType == 2) { - log(" " + tag.name + " has recovered, switching to Patrol behavior"); - ai.behaviorType = 0; // Patrol - } - - // Random decision to fire projectile - if (decisionDist(gen) == 0) { - entitiesToSpawnProjectile.push_back(entity); - } - - // Apply health regeneration - if (health.regenRate > 0 && health.current < health.maximum) { - health.current += static_cast(health.regenRate * deltaTime); - health.current = std::min(health.current, health.maximum); - } - - // Check for death - if (health.current <= 0 && !health.isDead) { - health.isDead = true; - health.current = 0; - log(" " + tag.name + " has died!"); - - // Mark for adding lifetime component after iteration - entitiesToAddLifetime.push_back(entity); - } - } - }); - } - - // Process component changes after iteration to avoid invalidation - for (auto entity : entitiesToAddLifetime) { - if (!coord->entityHasComponent(entity)) { - log(" Adding Lifetime component to dead entity " + - coord->getComponent(entity).name); - coord->addComponent(entity, Lifetime{5.0f, false}); - } - } - - // Process projectile spawning after iteration - for (auto entity : entitiesToSpawnProjectile) { - spawnProjectile(entity); - } - - log("AI update completed"); - } - - void spawnProjectile(nexo::ecs::Entity source) { - const auto& sourceTransform = coord->getComponent(source); - const auto& sourceTag = coord->getComponent(source); - - nexo::ecs::Entity projectile = coord->createEntity(); - - // Random projectile direction - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution<> velDist(-50.0f, 50.0f); - - // Add necessary components - coord->addComponent(projectile, Transform{ - sourceTransform.x, sourceTransform.y, 0.0f, 0.5f - }); - - coord->addComponent(projectile, Physics{ - static_cast(velDist(gen)), - static_cast(velDist(gen)), - 0.1f, true - }); - - coord->addComponent(projectile, Renderable{ - Renderable::Type::Circle, -1, 1.0f, 0.0f, 0.0f, 1.0f - }); - - coord->addComponent(projectile, Lifetime{3.0f, false}); - - coord->addComponent(projectile, Tag{ - Tag::Layer::Projectile, - sourceTag.name + "_projectile", - static_cast(projectile) - }); - - log(" " + sourceTag.name + " spawned a projectile"); - } - - void applyDamage(nexo::ecs::Entity target, int amount) { - if (!coord->entityHasComponent(target)) { - log("Cannot apply damage to entity without Health component"); - return; - } - - auto& health = coord->getComponent(target); - std::string entityName = "Unknown"; - - if (coord->entityHasComponent(target)) { - entityName = coord->getComponent(target).name; - } - - health.current -= amount; - log("Applied " + std::to_string(amount) + " damage to " + entityName + - " (Health: " + std::to_string(health.current) + "/" + - std::to_string(health.maximum) + ")"); - } - -private: - nexo::ecs::GroupAlias< - nexo::ecs::OwnedComponents, - nexo::ecs::NonOwnedComponents - > aiGroup = nullptr; -}; - -// Example 4: Lifetime system for handling entity destruction -class LifetimeSystem : public nexo::ecs::System { -public: - LifetimeSystem() { - // This group owns the Lifetime component - lifetimeGroup = coord->registerGroup(nexo::ecs::get()); - } - - void update(float deltaTime) { - if (lifetimeGroup->size() == 0) { - return; - } - - log("Updating lifetime for " + std::to_string(lifetimeGroup->size()) + " entities"); - - // Collect entities to be destroyed after iteration - std::vector entitiesToDestroy; - - lifetimeGroup->each([&](nexo::ecs::Entity entity, Lifetime& lifetime, Tag& tag) { - lifetime.timeRemaining -= deltaTime; - - if (lifetime.timeRemaining <= 0.0f || lifetime.shouldDestroy) { - log(" Marking " + tag.name + " for destruction"); - entitiesToDestroy.push_back(entity); - } - }); - - // Destroy entities after iteration is complete - for (auto entity : entitiesToDestroy) { - log(" Destroying entity " + std::to_string(entity)); - coord->destroyEntity(entity); - } - } - -private: - nexo::ecs::GroupAlias< - nexo::ecs::OwnedComponents, - nexo::ecs::NonOwnedComponents - > lifetimeGroup = nullptr; -}; - -// ============================================================================ -// Main Program -// ============================================================================ - int main() { // Initialize ECS Coordinator nexo::ecs::Coordinator coordinator; @@ -638,168 +197,50 @@ int main() { log("ECS initialized"); - // Register all components - coordinator.registerComponent(); - coordinator.registerComponent(); - coordinator.registerComponent(); - coordinator.registerComponent(); - coordinator.registerComponent(); - coordinator.registerComponent(); - coordinator.registerComponent(); + // Register components + coordinator.registerComponent(); + coordinator.registerComponent(); log("Components registered"); - // Register and set up the systems - auto physicsSystem = coordinator.registerSystem(); - { - nexo::ecs::Signature signature; - signature.set(coordinator.getComponentType()); - signature.set(coordinator.getComponentType()); - coordinator.setSystemSignature(signature); - } - - auto renderSystem = coordinator.registerSystem(); - { - nexo::ecs::Signature signature; - signature.set(coordinator.getComponentType()); - signature.set(coordinator.getComponentType()); - signature.set(coordinator.getComponentType()); - coordinator.setSystemSignature(signature); - } - - auto aiSystem = coordinator.registerSystem(); - { - nexo::ecs::Signature signature; - signature.set(coordinator.getComponentType()); - signature.set(coordinator.getComponentType()); - signature.set(coordinator.getComponentType()); - signature.set(coordinator.getComponentType()); - coordinator.setSystemSignature(signature); - } - - auto lifetimeSystem = coordinator.registerSystem(); - { - nexo::ecs::Signature signature; - signature.set(coordinator.getComponentType()); - signature.set(coordinator.getComponentType()); - coordinator.setSystemSignature(signature); - } + // Register benchmark systems + auto queryBenchmarkSystem = coordinator.registerQuerySystem(); + auto groupBenchmarkSystem = coordinator.registerGroupSystem(); - log("Systems registered"); + log("Benchmark systems registered"); - // ======================================================================== - // Entity Creation Examples - // ======================================================================== - log("Creating entities..."); + // Create 5,000 entities for benchmarking + log("Creating 5,000 entities for benchmarking..."); - // Create a player entity - nexo::ecs::Entity player = coordinator.createEntity(); - coordinator.addComponent(player, Transform{0.0f, 0.0f, 0.0f, 1.5f}); - coordinator.addComponent(player, Physics{0.0f, 0.0f, 2.0f, true}); - coordinator.addComponent(player, Renderable{Renderable::Type::Triangle, 10, 0.0f, 0.0f, 1.0f, 1.0f}); - coordinator.addComponent(player, Health{150, 150, 1.0f}); - coordinator.addComponent(player, Tag{Tag::Layer::Player, "Player", 1}); - log("Created Player entity with ID: " + std::to_string(player)); - - // Create some enemy entities with different behavior types std::random_device rd; std::mt19937 gen(rd()); - std::uniform_real_distribution<> posDist(-100.0, 100.0); - std::uniform_int_distribution<> zIndexDist(1, 20); - std::uniform_int_distribution<> colorDist(0, 255); - std::uniform_int_distribution<> behaviorDist(0, 2); - - std::vector enemies; - for (int i = 0; i < 10; ++i) { - nexo::ecs::Entity enemy = coordinator.createEntity(); - - float posX = static_cast(posDist(gen)); - float posY = static_cast(posDist(gen)); - int behaviorType = behaviorDist(gen); - - coordinator.addComponent(enemy, Transform{posX, posY}); - coordinator.addComponent(enemy, Physics{0.0f, 0.0f, 1.0f, true}); - coordinator.addComponent(enemy, Renderable{ - Renderable::Type::Rectangle, - zIndexDist(gen), - colorDist(gen) / 255.0f, - colorDist(gen) / 255.0f, - colorDist(gen) / 255.0f, - 1.0f - }); - coordinator.addComponent(enemy, AI{ - 50.0f + i * 5.0f, // Detection radius - behaviorType, // Behavior type - 0.5f + i * 0.1f // Reaction time - }); - coordinator.addComponent(enemy, Health{80 + i * 5, 100, 0.5f}); - coordinator.addComponent(enemy, Tag{ - Tag::Layer::Enemy, - "Enemy_" + std::to_string(i), - 100 + i - }); - - enemies.push_back(enemy); - log("Created Enemy entity with ID: " + std::to_string(enemy)); - } + std::uniform_real_distribution<> velocityDist(-10.0, 10.0); - // Create some environmental objects - for (int i = 0; i < 5; ++i) { - nexo::ecs::Entity environment = coordinator.createEntity(); - - float posX = static_cast(posDist(gen)); - float posY = static_cast(posDist(gen)); - - coordinator.addComponent(environment, Transform{posX, posY, 0.0f, 2.0f + i * 0.5f}); - coordinator.addComponent(environment, Renderable{ - i % 2 == 0 ? Renderable::Type::Circle : Renderable::Type::Custom, - zIndexDist(gen), - 0.0f, 0.5f, 0.0f, 1.0f // Green color - }); - coordinator.addComponent(environment, Tag{ - Tag::Layer::Environment, - "Environment_" + std::to_string(i), - 200 + i - }); - - log("Created Environment entity with ID: " + std::to_string(environment)); - } - - // ======================================================================== - // Performance Example - // ======================================================================== - log("\n=== Performance Comparison of Access Methods ==="); - physicsSystem->showPerformanceExamples(0.016f); + std::vector entities; + entities.reserve(5000); - // ======================================================================== - // Simulation Update Loop - // ======================================================================== - log("\n=== Starting Simulation ==="); + for (int i = 0; i < 5000; ++i) { + nexo::ecs::Entity entity = coordinator.createEntity(); - const float TIME_STEP = 0.016f; // ~60 FPS - for (int frame = 0; frame < 5; ++frame) { - log("\n--- Frame " + std::to_string(frame) + " ---"); + float velX = static_cast(velocityDist(gen)); + float velY = static_cast(velocityDist(gen)); - // Update all systems - physicsSystem->update(TIME_STEP); - aiSystem->update(TIME_STEP); + coordinator.addComponent(entity, Position{0.0f, 0.0f}); + coordinator.addComponent(entity, Velocity{velX, velY}); - // Example: Apply damage to a random enemy every other frame - if (frame % 2 == 0 && !enemies.empty()) { - std::uniform_int_distribution<> enemyDist(0, static_cast(enemies.size()) - 1); - int targetIndex = enemyDist(gen); + entities.push_back(entity); + } - log("\n*** Combat Event ***"); - aiSystem->applyDamage(enemies[targetIndex], 30); - } + log("Created " + std::to_string(entities.size()) + " entities"); - // Update lifetime system - lifetimeSystem->update(TIME_STEP); + // Run the benchmarks + log("\n=== Starting QuerySystem Benchmark ==="); + queryBenchmarkSystem->runBenchmark(); + log("=== QuerySystem Benchmark Complete ==="); - // Render the scene last - renderSystem->render(); - } + log("\n=== Starting GroupSystem Benchmark ==="); + groupBenchmarkSystem->runBenchmark(); + log("=== GroupSystem Benchmark Complete ==="); - log("\n=== Simulation Complete ==="); return 0; } From 85826c476219249e239c44e7b93d1532c45fcd09 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 18:38:20 +0900 Subject: [PATCH 036/450] feat(ecs-opti): add internal method to retrieve raw singleton component ptr --- engine/src/ecs/Coordinator.hpp | 12 ++++++ engine/src/ecs/SingletonComponent.hpp | 59 ++++++++++++++++++++------- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index c1d11e7c3..de814f4b0 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -200,6 +200,18 @@ namespace nexo::ecs { 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() + { + return m_singletonComponentManager->getRawSingletonComponent(); + } + /** * @brief Retrieves all component types associated with an entity. * diff --git a/engine/src/ecs/SingletonComponent.hpp b/engine/src/ecs/SingletonComponent.hpp index 2c0ee91a0..6c2b59e09 100644 --- a/engine/src/ecs/SingletonComponent.hpp +++ b/engine/src/ecs/SingletonComponent.hpp @@ -44,6 +44,8 @@ namespace nexo::ecs { 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. * @@ -61,9 +63,19 @@ namespace nexo::ecs { { } - T &getInstance() { - return _instance; - } + SingletonComponent() = default; + virtual ~SingletonComponent() = default; + + // Prevent copying + SingletonComponent(const SingletonComponent&) = delete; + SingletonComponent& operator=(const SingletonComponent&) = delete; + + // Allow moving + SingletonComponent(SingletonComponent&&) = default; + SingletonComponent& operator=(SingletonComponent&&) = default; + + T &getInstance() { return _instance; } + private: T _instance; }; @@ -77,7 +89,6 @@ namespace nexo::ecs { */ class SingletonComponentManager { public: - /** * @brief Registers a singleton component in place by forwarding constructor arguments. * @@ -88,14 +99,15 @@ namespace nexo::ecs { * @param args Arguments to construct an instance of T. */ template - void registerSingletonComponent(Args&&... args) { - using Decayed = std::decay_t; - std::type_index typeName(typeid(Decayed)); - 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)...)}); + void registerSingletonComponent(Args&&... args) + { + using Decayed = std::decay_t; + std::type_index typeName(typeid(Decayed)); + 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)...)}); } /** @@ -106,7 +118,8 @@ namespace nexo::ecs { * @throws SingletonComponentNotRegistered if the component is not registered. */ template - T &getSingletonComponent() { + T &getSingletonComponent() + { const std::type_index typeName(typeid(T)); if (!m_singletonComponents.contains(typeName)) THROW_EXCEPTION(SingletonComponentNotRegistered); @@ -116,6 +129,23 @@ namespace nexo::ecs { 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 std::type_index typeName(typeid(T)); + if (!m_singletonComponents.contains(typeName)) + THROW_EXCEPTION(SingletonComponentNotRegistered); + + return m_singletonComponents[typeName]; + } + /** * @brief Unregisters a singleton component. * @@ -125,7 +155,8 @@ namespace nexo::ecs { * @throws SingletonComponentNotRegistered if the component is not registered. */ template - void unregisterSingletonComponent() { + void unregisterSingletonComponent() + { const std::type_index typeName(typeid(T)); if (!m_singletonComponents.contains(typeName)) THROW_EXCEPTION(SingletonComponentNotRegistered); From e9a1b2315a16309dab7a35b5ffc65ececd57e0ac Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 18:38:45 +0900 Subject: [PATCH 037/450] chore(ecs-opti): rewrote the headers --- engine/src/ecs/Components.cpp | 2 +- engine/src/ecs/Components.hpp | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/engine/src/ecs/Components.cpp b/engine/src/ecs/Components.cpp index 81ab7b97c..e8245d399 100644 --- a/engine/src/ecs/Components.cpp +++ b/engine/src/ecs/Components.cpp @@ -8,7 +8,7 @@ // // Author: Mehdy MORVAN // Date: 10/11/2024 -// Description: Source file for the components array and component manager classes +// Description: Source file for the component manager class // /////////////////////////////////////////////////////////////////////////////// diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index b28e20cb5..2415ec67d 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -1,3 +1,16 @@ +//// Components.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: Mehdy MORVAN +// Date: 10/11/2024 +// Description: Header file for the component manager class +// +/////////////////////////////////////////////////////////////////////////////// #pragma once #include From 5591cbc602c039a75feeb8f89ccaebbba187d885 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 18:39:13 +0900 Subject: [PATCH 038/450] feat(ecs-opti): add base class for singleton components handling in systems --- engine/src/ecs/SingletonComponentMixin.hpp | 114 +++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 engine/src/ecs/SingletonComponentMixin.hpp diff --git a/engine/src/ecs/SingletonComponentMixin.hpp b/engine/src/ecs/SingletonComponentMixin.hpp new file mode 100644 index 000000000..f1f9a172d --- /dev/null +++ b/engine/src/ecs/SingletonComponentMixin.hpp @@ -0,0 +1,114 @@ +//// SingletonComponentMixin.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: Mehdy MORVAN +// Date: 06/04/2025 +// Description: Base class for common singleton components operations in systems +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "Access.hpp" +#include "SingletonComponent.hpp" +#include +#include +#include + +namespace nexo::ecs { + + /** + * @brief Mixin class that provides singleton component functionality to systems + * + * @tparam Derived The derived system type + * @tparam SingletonAccessTypes Singleton component access types (ReadSingleton or WriteSingleton) + */ + template + class SingletonComponentMixin { + private: + // Helper to check if a singleton component has read access in the parameter pack + template + struct HasReadSingletonAccessImpl { + static constexpr bool value = (... || (IsReadSingleton::value && + std::is_same_v)); + }; + + protected: + // Cache of singleton components for faster access + std::unordered_map> m_singletonComponents; + + /** + * @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() + { + try { + auto* derived = static_cast(this); + std::shared_ptr instance = derived->coord->template getRawSingletonComponent(); + m_singletonComponents[getTypeIndex()] = instance; + } catch (const nexo::ecs::SingletonComponentNotRegistered&) { + // Singleton not registered yet, we'll try again when getSingleton is called + } + } + + public: + // Convenience function to check singleton read 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 + typename std::conditional(), const T&, T&>::type + getSingleton() + { + auto typeIndex = getTypeIndex(); + + if (!m_singletonComponents.contains(typeIndex)) { + // Late binding in case the singleton was registered after system creation + cacheSingletonComponent(); + } + + // Get the stored singleton component wrapper + auto& singletonComponentPtr = m_singletonComponents[typeIndex]; + auto* componentWrapper = dynamic_cast*>(singletonComponentPtr.get()); + + if (!componentWrapper) + THROW_EXCEPTION(SingletonComponentNotRegistered); + + // Return the reference with appropriate constness + if constexpr (hasReadSingletonAccess()) { + // For read-only access, return const reference + return const_cast(componentWrapper->getInstance()); + } else { + // For read-write access, return non-const reference + return componentWrapper->getInstance(); + } + } + }; +} From ec6513b21c1c72053ac361de73d643147e8ab5ae Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 18:39:28 +0900 Subject: [PATCH 039/450] feat(ecs-opti): add access helpers for singleton components --- engine/src/ecs/Access.hpp | 75 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/engine/src/ecs/Access.hpp b/engine/src/ecs/Access.hpp index 1ea60364a..55d495d18 100644 --- a/engine/src/ecs/Access.hpp +++ b/engine/src/ecs/Access.hpp @@ -1,6 +1,21 @@ +//// Access.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: Mehdy MORVAN +// Date: 06/04/2025 +// Description: Header file for access enforcement helpers +// +/////////////////////////////////////////////////////////////////////////////// #pragma once #include +#include +#include namespace nexo::ecs { /** @@ -35,6 +50,24 @@ namespace nexo::ecs { template using Write = ComponentAccess; + /** + * @brief Type alias for read-only singleton component access + */ + template + struct ReadSingleton { + using ComponentType = T; + static constexpr AccessType accessType = AccessType::Read; + }; + + /** + * @brief Type alias for read-write singleton component access + */ + template + struct WriteSingleton { + using ComponentType = T; + static constexpr AccessType accessType = AccessType::Write; + }; + /** * @brief Type wrapper for owned components in a group system * @@ -75,7 +108,8 @@ namespace nexo::ecs { * @brief Helper to convert a tuple of component access types to a parameter pack */ template - void tuple_for_each_impl(Tuple&& tuple, Func&& func, std::index_sequence) { + void tuple_for_each_impl(Tuple&& tuple, Func&& func, std::index_sequence) + { (func(std::get(std::forward(tuple))), ...); } @@ -83,11 +117,48 @@ namespace nexo::ecs { * @brief Apply a function to each element of a tuple */ template - void tuple_for_each(Tuple&& tuple, Func&& func) { + void tuple_for_each(Tuple&& tuple, Func&& func) + { tuple_for_each_impl( std::forward(tuple), std::forward(func), std::make_index_sequence>>{} ); } + + /** + * @brief Helper to check if a type is a ReadSingleton + */ + template + struct IsReadSingleton : std::false_type {}; + + template + struct IsReadSingleton> : std::true_type {}; + + /** + * @brief Helper to check if a type is a WriteSingleton + */ + template + struct IsWriteSingleton : std::false_type {}; + + template + struct IsWriteSingleton> : std::true_type {}; + + /** + * @brief Helper to check if a type is any kind of singleton component + */ + template + struct IsSingleton : std::bool_constant::value || IsWriteSingleton::value> {}; + + /** + * @brief Gets the type index for a component + * + * @tparam T The component type + * @return std::type_index The type index + */ + template + std::type_index getTypeIndex() + { + return std::type_index(typeid(T)); + } } From f0326c68ec7583d0e659972cb962428e78e5e6b8 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 18:39:49 +0900 Subject: [PATCH 040/450] feat(ecs-opti): add singleton components handling in group systems --- engine/src/ecs/GroupSystem.hpp | 441 ++++++++++++++++++--------------- 1 file changed, 235 insertions(+), 206 deletions(-) diff --git a/engine/src/ecs/GroupSystem.hpp b/engine/src/ecs/GroupSystem.hpp index 577f75423..d359ff792 100644 --- a/engine/src/ecs/GroupSystem.hpp +++ b/engine/src/ecs/GroupSystem.hpp @@ -1,3 +1,16 @@ +//// GroupSystem.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: Mehdy MORVAN +// Date: 06/04/2025 +// Description: Header file for the Group system class +// +/////////////////////////////////////////////////////////////////////////////// #pragma once #include "System.hpp" @@ -5,7 +18,7 @@ #include "Group.hpp" #include "ComponentArray.hpp" #include "Coordinator.hpp" -#include "Access.hpp" +#include "SingletonComponentMixin.hpp" #include #include #include @@ -19,212 +32,228 @@ namespace nexo::ecs { * * @tparam OwnedAccess Owned<> wrapper with component access types * @tparam NonOwnedAccess NonOwned<> wrapper with component access types + * @tparam SingletonAccessTypes Singleton component access types (ReadSingleton or WriteSingleton) */ - template> - class GroupSystem : public AGroupSystem { - 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 AccessType accessType = AccessType::Read; - }; - - template - struct ComponentAccessTrait, - std::void_t || ...)>>> { - static constexpr bool found = true; - - template - static constexpr AccessType GetAccessTypeFromPack() { - AccessType 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 - */ - template - class ComponentSpan { + template, typename... SingletonAccessTypes> + class GroupSystem : public AGroupSystem, public SingletonComponentMixin< + GroupSystem, + SingletonAccessTypes...> { private: - std::span m_span; - - public: - explicit ComponentSpan(std::span span) : m_span(span) {} - - size_t size() const { return m_span.size(); } - - // Conditionally define operator[] based on access type - template - auto operator[](size_t index) -> std::conditional_t< - GetComponentAccess>::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]; - } - } - - template - auto operator[](size_t index) const -> const std::remove_const_t& { - return m_span[index]; - } - - // Iterator support - auto begin() { return m_span.begin(); } - auto end() { return m_span.end(); } - auto begin() const { return m_span.begin(); } - auto end() const { return m_span.end(); } - }; - - public: - GroupSystem() { - if (!coord) return; - - // Create the group - m_group = createGroupImpl( - tupleToTypeList(), - tupleToTypeList() - ); - } - - /** - * @brief Get component array with correct access permissions - * - * @tparam T The component type - * @return ComponentSpan with enforced access control - */ - template - auto get() { - // 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); - } - - /** - * @brief Get a component for an entity with appropriate access control - * - * @tparam AccessType The component access type (Read or Write) - * @param entity The entity to get the component for - * @return Component reference with appropriate const-ness - */ - template - auto getComponent(Entity entity) { - using T = typename AccessType::ComponentType; - - if constexpr (AccessType::accessType == AccessType::Read) { - return std::cref(m_group->template get(entity)); - } else { - return std::ref(m_group->template get(entity)); - } - } - - /** - * @brief Get all entities in this group - * - * @return Span of entities in the group - */ - auto getEntities() const { - return m_group->entities(); - } - - /** - * @brief Get direct access to the underlying group - * - * @return The group object - */ - auto& group() const { - return *m_group; - } - - protected: - std::shared_ptr m_group; - - private: - /** - * @brief Implementation to create a group with the extracted component types - */ - 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()); - return std::static_pointer_cast(group); - } else { - auto group = coord->registerGroup(nexo::ecs::get<>()); - return std::static_pointer_cast(group); - } - } - return nullptr; - } + // 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 AccessType accessType = AccessType::Read; + }; + + template + struct ComponentAccessTrait, + std::void_t || ...)>>> { + static constexpr bool found = true; + + template + static constexpr AccessType GetAccessTypeFromPack() { + AccessType 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 + */ + template + class ComponentSpan { + private: + std::span m_span; + + public: + explicit ComponentSpan(std::span span) : m_span(span) {} + + size_t size() const { return m_span.size(); } + + // Conditionally define operator[] based on access type + template + auto operator[](size_t index) -> std::conditional_t< + GetComponentAccess>::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]; + } + } + + template + auto operator[](size_t index) const -> const std::remove_const_t& + { + return m_span[index]; + } + + // Iterator support + auto begin() { return m_span.begin(); } + auto end() { return m_span.end(); } + auto begin() const { return m_span.begin(); } + auto end() const { return m_span.end(); } + }; + + public: + // Make the base class a friend to access protected members + friend class SingletonComponentMixin< + GroupSystem, + SingletonAccessTypes...>; + + GroupSystem() + { + if (!coord) + return; + + // Create the group + m_group = createGroupImpl( + tupleToTypeList(), + tupleToTypeList() + ); + + // Initialize singleton components + this->initializeSingletonComponents(); + } + + /** + * @brief Get component array with correct access permissions + * + * @tparam T The component type + * @return ComponentSpan with enforced access control + */ + template + auto get() + { + // 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); + } + + /** + * @brief Get a component for an entity with appropriate access control + * + * @tparam AccessType The component access type (Read or Write) + * @param entity The entity to get the component for + * @return Component reference with appropriate const-ness + */ + template + auto getComponent(Entity entity) + { + using T = typename AccessType::ComponentType; + + if constexpr (AccessType::accessType == AccessType::Read) { + return std::cref(m_group->template get(entity)); + } else { + return std::ref(m_group->template get(entity)); + } + } + + /** + * @brief Get all entities in this group + * + * @return Span of entities in the group + */ + auto getEntities() const + { + return m_group->entities(); + } + + protected: + std::shared_ptr m_group; + + private: + /** + * @brief Implementation to create a group with the extracted component types + */ + template + std::shared_ptr createGroupImpl(std::tuple, std::tuple) + { + if constexpr (sizeof...(OT) > 0) { + if constexpr (sizeof...(NOT) > 0) { + auto group = coord->template registerGroup(nexo::ecs::get()); + return std::static_pointer_cast(group); + } else { + auto group = coord->template registerGroup(nexo::ecs::get<>()); + return std::static_pointer_cast(group); + } + } + return nullptr; + } }; } From 8ecfc01ce796f3055c9bcb9e978e67f6a7d6a711 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 18:40:09 +0900 Subject: [PATCH 041/450] feat(ecs-opti): add singleton components handling in query systems --- engine/src/ecs/QuerySystem.hpp | 190 ++++++++++++++++++--------------- 1 file changed, 103 insertions(+), 87 deletions(-) diff --git a/engine/src/ecs/QuerySystem.hpp b/engine/src/ecs/QuerySystem.hpp index 1698ad7c6..0ce9f4a36 100644 --- a/engine/src/ecs/QuerySystem.hpp +++ b/engine/src/ecs/QuerySystem.hpp @@ -1,9 +1,23 @@ +//// QuerySystem.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: Mehdy MORVAN +// Date: 06/04/2025 +// Description: Header file for the query system class +// +/////////////////////////////////////////////////////////////////////////////// #pragma once #include "System.hpp" #include "Access.hpp" #include "ComponentArray.hpp" #include "Coordinator.hpp" +#include "SingletonComponentMixin.hpp" #include #include #include @@ -13,107 +27,109 @@ namespace nexo::ecs { * @class QuerySystem * @brief System that directly queries component arrays * - * @tparam Components Component access specifiers (Read or Write) + * @tparam Components Component access specifiers (Read, Write, ReadSingleton, WriteSingleton) */ template - class QuerySystem : public AQuerySystem { - private: - // Helper template to check if a component type exists in the parameter pack with Read access - template - struct HasReadAccess { - static constexpr bool value = - (std::is_same_v> || - HasReadAccess::value); - }; + class QuerySystem : public AQuerySystem, public SingletonComponentMixin< + QuerySystem, + Components...> { + private: + // Helper template to check if a component type exists in the parameter pack with Read access + template + struct HasReadAccess { + static constexpr bool value = + (std::is_same_v> || + HasReadAccess::value); + }; - // Base case for the template recursion - template - struct HasReadAccess { - static constexpr bool value = std::is_same_v>; - }; + // Base case for the template recursion + template + struct HasReadAccess { + static constexpr bool value = std::is_same_v>; + }; - // Convenience function to check read access - template - static constexpr bool hasReadAccess() { - return HasReadAccess::value; - } + // Convenience function to check read access + template + static constexpr bool hasReadAccess() + { + return HasReadAccess::value; + } - public: - QuerySystem() { - if (!coord) return; + public: + // Make the base class a friend to access protected members + friend class SingletonComponentMixin, Components...>; - // Set system signature based on required components - (setComponentSignature(m_signature), ...); + QuerySystem() + { + if (!coord) + return; - // Store component arrays for faster access - (cacheComponentArray(), ...); - } + // Set system signature based on required components (ignore singleton components) + (setComponentSignatureIfRegular(m_signature), ...); - /** - * @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 - typename std::conditional(), const T&, T&>::type - getComponent(Entity entity) { - auto typeIndex = getTypeIndex(); - auto componentArray = std::static_pointer_cast>(m_componentArrays[typeIndex]); + // Cache component arrays for faster access (ignore singleton components) + (cacheComponentArrayIfRegular(), ...); - if constexpr (hasReadAccess()) { - return componentArray->getData(entity); - } else { - return componentArray->getData(entity); - } - } + // Initialize singleton components + this->initializeSingletonComponents(); + } - const Signature &getSignature() const { - return m_signature; - } + /** + * @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 + typename std::conditional(), const T&, T&>::type + getComponent(Entity entity) + { + auto typeIndex = getTypeIndex(); + auto componentArray = std::static_pointer_cast>(m_componentArrays[typeIndex]); - Signature &getSignature() { - return m_signature; - } + if constexpr (hasReadAccess()) + return componentArray->getData(entity); + else + return componentArray->getData(entity); + } - protected: - /** - * @brief Caches component arrays for faster access - * - * @tparam ComponentAccessType The component access type to cache - */ - template - void cacheComponentArray() { - using T = typename ComponentAccessType::ComponentType; - m_componentArrays[getTypeIndex()] = coord->getComponentArray(); - } + const Signature& getSignature() const { return m_signature; } + Signature& getSignature() { return m_signature; } - /** - * @brief Sets the component bit in the system signature - * - * @tparam T The component type - * @param signature The signature to modify - */ - template - void setComponentSignature(Signature& signature) { - signature.set(coord->getComponentType(), true); - } + 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; + m_componentArrays[getTypeIndex()] = coord->template getComponentArray(); + } + } - /** - * @brief Gets the type index for a component - * - * @tparam T The component type - * @return std::type_index The type index - */ - template - std::type_index getTypeIndex() const { - return std::type_index(typeid(T)); - } + /** + * @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->template getComponentType(), true); + } + } - private: - // Cache of component arrays for faster access - std::unordered_map> m_componentArrays; - Signature m_signature; + private: + // Cache of component arrays for faster access + std::unordered_map> m_componentArrays; + Signature m_signature; }; } From 61023c569b368247a714c50da1302472b683e646 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 6 Apr 2025 18:40:29 +0900 Subject: [PATCH 042/450] feat(ecs-opti): update the example to showcase the new singleton components feature --- examples/ecs/exampleBasic.cpp | 84 ++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/examples/ecs/exampleBasic.cpp b/examples/ecs/exampleBasic.cpp index a865413fa..f2b0bfaf5 100644 --- a/examples/ecs/exampleBasic.cpp +++ b/examples/ecs/exampleBasic.cpp @@ -19,6 +19,43 @@ struct Velocity { float y = 0.0f; }; +// Define singleton components +// 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; + float worldSize = 100.0f; + + // Default constructor and other constructors as needed + GameConfig() = default; + GameConfig(int entities, float size) : maxEntities(entities), worldSize(size) {} + + // Delete copy constructor to enforce singleton semantics + GameConfig(const GameConfig&) = delete; + GameConfig& operator=(const GameConfig&) = delete; + + // Move operations can be allowed if needed + GameConfig(GameConfig&&) = default; + GameConfig& operator=(GameConfig&&) = default; +}; + +struct GameState { + bool isPaused; + float gameTime; + + // Default constructor and other constructors + GameState() = default; + GameState(bool paused, float time) : isPaused(paused), gameTime(time) {} + + // Delete copy constructor to enforce singleton semantics + GameState(const GameState&) = delete; + GameState& operator=(const GameState&) = delete; + + // Move operations can be allowed if needed + GameState(GameState&&) = default; + GameState& operator=(GameState&&) = default; +}; + void log(const std::string& message) { std::cout << message << std::endl; @@ -26,13 +63,16 @@ void log(const std::string& message) // This query system will increment the position component by the velocity component for each entity having both // Position is marked as a write component and Velocity as a read component +// We also retrieve GameConfig singleton component as read only and GameState as write // This enforces constness at compile time to prevent accidental modification of Velocity // 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::Read, + nexo::ecs::ReadSingleton, + nexo::ecs::WriteSingleton > { public: void runBenchmark() @@ -48,11 +88,24 @@ class QueryBenchmarkSystem : public nexo::ecs::QuerySystem< { 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; @@ -72,6 +125,7 @@ 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 // 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< @@ -79,7 +133,9 @@ class GroupBenchmarkSystem : public nexo::ecs::GroupSystem< nexo::ecs::Write, nexo::ecs::Read >, - nexo::ecs::NonOwned<> + nexo::ecs::NonOwned<>, + nexo::ecs::WriteSingleton, + nexo::ecs::ReadSingleton > { public: void runBenchmark() @@ -160,11 +216,25 @@ class GroupBenchmarkSystem : public nexo::ecs::GroupSystem< // 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) { @@ -201,6 +271,8 @@ int main() { coordinator.registerComponent(); coordinator.registerComponent(); + coordinator.registerSingletonComponent(5000, 10); + coordinator.registerSingletonComponent(true, 10.0f); log("Components registered"); // Register benchmark systems @@ -242,5 +314,13 @@ int main() { groupBenchmarkSystem->runBenchmark(); log("=== GroupSystem Benchmark Complete ==="); + // We make sure to check if the singleton component has been updated + auto &gameState = coordinator.getSingletonComponent(); + log("Game time: " + std::to_string(gameState.gameTime)); + + auto &gameConfig = coordinator.getSingletonComponent(); + log("Max entities: " + std::to_string(gameConfig.maxEntities)); + + return 0; } From a3d732e501661d908d806af1ec6fdbc0e0186db0 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 8 Apr 2025 16:19:44 +0900 Subject: [PATCH 043/450] fix(ecs-opti): now use vector for the entities with specific components --- editor/src/DocumentWindows/SceneTreeWindow.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow.hpp index 06000ef3d..c8c05d22f 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.hpp @@ -127,7 +127,7 @@ namespace nexo::editor { template void generateNodes(std::map &scenes, NodeCreator nodeCreator) { - const std::set entities = nexo::Application::m_coordinator->getAllEntitiesWith(); + const std::vector entities = nexo::Application::m_coordinator->getAllEntitiesWith(); for (const ecs::Entity entity : entities) { const auto& sceneTag = nexo::Application::m_coordinator->getComponent(entity); From f0eaa2a4e425f208de5baa007dfa2a49074d8666 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 8 Apr 2025 16:20:17 +0900 Subject: [PATCH 044/450] feat(ecs-opti): delete copy constructor from render context --- engine/src/components/RenderContext.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/src/components/RenderContext.hpp b/engine/src/components/RenderContext.hpp index bcedd1e08..1e4d76965 100644 --- a/engine/src/components/RenderContext.hpp +++ b/engine/src/components/RenderContext.hpp @@ -30,6 +30,10 @@ namespace nexo::components { renderer3D.init(); } + // Delete copy constructor to enforce singleton semantics + RenderContext(const RenderContext&) = delete; + RenderContext& operator=(const RenderContext&) = delete; + RenderContext(RenderContext&& other) noexcept : sceneRendered(other.sceneRendered), renderer3D(std::move(other.renderer3D)), @@ -38,6 +42,7 @@ namespace nexo::components { { } + ~RenderContext() { renderer3D.shutdown(); From 9503d88dcd3b2f6cfe7bf092002b5e8c5afef900 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 8 Apr 2025 16:21:00 +0900 Subject: [PATCH 045/450] fix(ecs-opti): now use entity signature to remove from group before destroying the entity --- engine/src/ecs/Components.cpp | 9 ++++++++- engine/src/ecs/Components.hpp | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/engine/src/ecs/Components.cpp b/engine/src/ecs/Components.cpp index e8245d399..e11b90b15 100644 --- a/engine/src/ecs/Components.cpp +++ b/engine/src/ecs/Components.cpp @@ -16,8 +16,15 @@ namespace nexo::ecs { - void ComponentManager::entityDestroyed(const Entity entity) + void ComponentManager::entityDestroyed(const Entity entity, const Signature &entitySignature) { + for (auto &[_, group] : m_groupRegistry) + { + if ((entitySignature & group->allSignature()) == group->allSignature()) + { + group->removeFromGroup(entity); + } + } for (auto& componentArray : m_componentArrays) { if (componentArray) { componentArray->entityDestroyed(entity); diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 2415ec67d..e64eade32 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -397,7 +397,7 @@ namespace nexo::ecs { * * @param entity The destroyed entity */ - void entityDestroyed(Entity entity); + void entityDestroyed(Entity entity, const Signature &entitySignature); /** * @brief Creates or retrieves a group for specific component combinations From 7afd4ca632c095290f2b7447f3bed54a26ca2b7e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 8 Apr 2025 16:21:32 +0900 Subject: [PATCH 046/450] refactor(ecs-opti): pass the entity signature to the component manager entity destroyed method --- engine/src/ecs/Coordinator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index 4bf2b9dea..3a74bccb8 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -39,7 +39,7 @@ namespace nexo::ecs { { auto signature = m_entityManager->getSignature(entity); m_entityManager->destroyEntity(entity); - m_componentManager->entityDestroyed(entity); + m_componentManager->entityDestroyed(entity, signature); m_systemManager->entityDestroyed(entity, signature); } From e9cf910514595a0ac51bf69ad536d53dcc9d8810 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 8 Apr 2025 16:22:16 +0900 Subject: [PATCH 047/450] fix(ecs-opti): now store the living entities to optimize the getAllEntitiesWith --- engine/src/ecs/Coordinator.hpp | 19 +++++++------------ engine/src/ecs/Entity.cpp | 19 ++++++++++++++----- engine/src/ecs/Entity.hpp | 6 ++++-- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index de814f4b0..194e3186f 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -235,24 +235,19 @@ namespace nexo::ecs { * @return std::set A set of entities that contain all the specified components. */ template - std::set getAllEntitiesWith() const + std::vector getAllEntitiesWith() const { Signature requiredSignature; (requiredSignature.set(m_componentManager->getComponentType(), true), ...); - std::uint32_t checkedEntities = 0; - std::uint32_t livingEntities = m_entityManager->getLivingEntityCount(); - std::set result; - for (Entity i = 0; i < MAX_ENTITIES; ++i) + std::span livingEntities = m_entityManager->getLivingEntities(); + std::vector result; + result.reserve(livingEntities.size()); + for (auto entity : livingEntities) { - Signature entitySignature = m_entityManager->getSignature(i); - if (entitySignature.none()) - continue; + Signature entitySignature = m_entityManager->getSignature(entity); if ((entitySignature & requiredSignature) == requiredSignature) - result.insert(i); - checkedEntities++; - if (checkedEntities > livingEntities) - break; + result.push_back(entity); } return result; } diff --git a/engine/src/ecs/Entity.cpp b/engine/src/ecs/Entity.cpp index c54c52dcf..55f710d33 100644 --- a/engine/src/ecs/Entity.cpp +++ b/engine/src/ecs/Entity.cpp @@ -14,6 +14,8 @@ #include "Entity.hpp" #include "ECSExceptions.hpp" +#include + namespace nexo::ecs { EntityManager::EntityManager() @@ -24,12 +26,12 @@ namespace nexo::ecs { Entity EntityManager::createEntity() { - if (m_livingEntityCount >= MAX_ENTITIES) + if (m_livingEntities.size() >= MAX_ENTITIES) THROW_EXCEPTION(TooManyEntities); const Entity id = m_availableEntities.front(); m_availableEntities.pop_front(); - ++m_livingEntityCount; + m_livingEntities.push_back(id); return id; } @@ -42,8 +44,9 @@ namespace nexo::ecs { m_signatures[entity].reset(); m_availableEntities.push_front(entity); - if (m_livingEntityCount > 0) - --m_livingEntityCount; + auto it = std::find(m_livingEntities.begin(), m_livingEntities.end(), entity); + if (it != m_livingEntities.end()) + m_livingEntities.erase(it); } void EntityManager::setSignature(const Entity entity, const Signature signature) @@ -64,6 +67,12 @@ namespace nexo::ecs { std::uint32_t EntityManager::getLivingEntityCount() const { - return m_livingEntityCount; + return m_livingEntities.size(); } + + std::span EntityManager::getLivingEntities() const + { + return std::span(m_livingEntities); + } + } diff --git a/engine/src/ecs/Entity.hpp b/engine/src/ecs/Entity.hpp index 2b98d8beb..3b982d008 100644 --- a/engine/src/ecs/Entity.hpp +++ b/engine/src/ecs/Entity.hpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include "Definitions.hpp" @@ -72,12 +74,12 @@ namespace nexo::ecs { [[nodiscard]] Signature getSignature(Entity entity) const; std::uint32_t getLivingEntityCount() const; + std::span getLivingEntities() const; private: std::deque m_availableEntities{}; + std::vector m_livingEntities{}; std::array m_signatures{}; - - std::uint32_t m_livingEntityCount{}; }; } From a1aad122aed88a6ed88fae179ce6f32bb71da27e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 8 Apr 2025 16:23:05 +0900 Subject: [PATCH 048/450] fix(ecs-opti): get method now returns either the component span or the component array itself depending on owning/non-owning --- engine/src/ecs/GroupSystem.hpp | 64 +++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/engine/src/ecs/GroupSystem.hpp b/engine/src/ecs/GroupSystem.hpp index d359ff792..a9ac0b2b9 100644 --- a/engine/src/ecs/GroupSystem.hpp +++ b/engine/src/ecs/GroupSystem.hpp @@ -194,17 +194,43 @@ namespace nexo::ecs { template auto get() { - // 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); + 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 a component for an entity with appropriate access control * @@ -212,17 +238,15 @@ namespace nexo::ecs { * @param entity The entity to get the component for * @return Component reference with appropriate const-ness */ - template - auto getComponent(Entity entity) - { - using T = typename AccessType::ComponentType; - - if constexpr (AccessType::accessType == AccessType::Read) { - return std::cref(m_group->template get(entity)); - } else { - return std::ref(m_group->template get(entity)); - } - } + template + auto getComponent(Entity entity) { + // Use GetComponentAccess to determine if T should be read-only + if constexpr (GetComponentAccess::accessType == AccessType::Read) { + return std::cref(m_group->template get(entity)); + } else { + return std::ref(m_group->template get(entity)); + } + } /** * @brief Get all entities in this group From 85a74ed42d7d444f2284b2847a0a417e5f2d55a0 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 8 Apr 2025 16:23:36 +0900 Subject: [PATCH 049/450] refactor(ecs-opti): refactor the current system to match the new implementation --- engine/src/systems/CameraSystem.cpp | 166 +++++++++--------- engine/src/systems/CameraSystem.hpp | 41 ++++- engine/src/systems/LightSystem.cpp | 2 +- engine/src/systems/LightSystem.hpp | 2 +- engine/src/systems/RenderSystem.cpp | 45 +++-- engine/src/systems/RenderSystem.hpp | 18 +- .../src/systems/lights/AmbientLightSystem.cpp | 28 +-- .../src/systems/lights/AmbientLightSystem.hpp | 14 +- .../lights/DirectionalLightsSystem.cpp | 24 ++- .../lights/DirectionalLightsSystem.hpp | 13 +- .../src/systems/lights/PointLightsSystem.cpp | 24 ++- .../src/systems/lights/PointLightsSystem.hpp | 13 +- .../src/systems/lights/SpotLightsSystem.cpp | 24 ++- .../src/systems/lights/SpotLightsSystem.hpp | 14 +- 14 files changed, 273 insertions(+), 155 deletions(-) diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index fa3af485c..b7c6e2bbf 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -21,27 +21,37 @@ #include "core/event/KeyCodes.hpp" #include "Application.hpp" #include "core/event/WindowEvent.hpp" +#include "core/scene/Scene.hpp" #include "math/Vector.hpp" #include #include namespace nexo::system { - void CameraContextSystem::update() const + void CameraContextSystem::update() { - auto &renderContext = coord->getSingletonComponent(); + auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - for (const auto camera : entities) - { - auto tag = coord->getComponent(camera); - if (!tag.isRendered || sceneRendered != tag.id) - continue; + auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag& tag) { return tag.id; } + ); - auto cameraComponent = coord->getComponent(camera); - auto transformComponent = coord->getComponent(camera); + const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + + //TODO: Throw exception here ? + if (!partition) + return; + const auto cameraSpan = get(); + const auto transformComponentArray = get(); + const auto entitySpan = m_group->entities(); + + for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) + { + const auto &cameraComponent = cameraSpan[i]; + const auto &transformComponent = transformComponentArray->getData(entitySpan[i]); glm::mat4 projectionMatrix = cameraComponent.getProjectionMatrix(); glm::mat4 viewMatrix = cameraComponent.getViewMatrix(transformComponent); glm::mat4 viewProjectionMatrix = projectionMatrix * viewMatrix; @@ -50,34 +60,33 @@ namespace nexo::system { } } - PerspectiveCameraControllerSystem::PerspectiveCameraControllerSystem() + PerspectiveCameraControllerSystem::PerspectiveCameraControllerSystem() : QuerySystem() { Application::getInstance().getEventManager()->registerListener(this); Application::getInstance().getEventManager()->registerListener(this); } - void PerspectiveCameraControllerSystem::update(const Timestep ts) const + void PerspectiveCameraControllerSystem::update(const Timestep ts) { - const auto &renderContext = coord->getSingletonComponent(); + const auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - const auto deltaTime = static_cast(ts); + constexpr float translationSpeed = 5.0f; - for (const auto &entity : entities) - { - constexpr float translationSpeed = 5.0f; - auto tag = coord->getComponent(entity); - if (!tag.isActive || sceneRendered != tag.id) - continue; + for (auto entity : entities) + { + auto &sceneTag = getComponent(entity); + if (!sceneTag.isActive || sceneTag.id != sceneRendered) + continue; + auto &cameraComponent = getComponent(entity); + auto &transform = getComponent(entity); - auto &cameraComponent = coord->getComponent(entity); - cameraComponent.resizing = false; - auto &transform = coord->getComponent(entity); + cameraComponent.resizing = false; - glm::vec3 front = transform.quat * glm::vec3(0.0f, 0.0f, -1.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); @@ -93,50 +102,50 @@ namespace nexo::system { transform.pos += up * translationSpeed * deltaTime; // Up if (event::isKeyPressed(NEXO_KEY_TAB)) transform.pos -= up * translationSpeed * deltaTime; // Down - } + } } void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseScroll &event) { - auto const &renderContext = coord->getSingletonComponent(); + const auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); + constexpr float zoomSpeed = 0.5f; - for (const auto &camera : entities) - { - auto tag = coord->getComponent(camera); - if (!tag.isActive || sceneRendered != tag.id) - continue; - constexpr float zoomSpeed = 0.5f; - auto &transform = coord->getComponent(camera); - glm::vec3 front = transform.quat * glm::vec3(0.0f, 0.0f, -1.0f); + for (auto entity : entities) + { + auto &sceneTag = getComponent(entity); + if (!sceneTag.isActive || sceneTag.id != sceneRendered) + 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 = coord->getSingletonComponent(); + auto const &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); glm::vec2 currentMousePosition(event.x, event.y); - for (const auto &camera : entities) + for (auto entity : entities) { - auto &controller = coord->getComponent(camera); - auto const &cameraComponent = coord->getComponent(camera); - auto tag = coord->getComponent(camera); - const glm::vec2 mouseDelta = (currentMousePosition - controller.lastMousePosition) * controller.mouseSensitivity; - controller.lastMousePosition = currentMousePosition; - - - if (!tag.isActive || sceneRendered != tag.id || cameraComponent.resizing || !event::isMouseDown(NEXO_MOUSE_LEFT)) - continue; + auto &controller = getComponent(entity); + const glm::vec2 mouseDelta = (currentMousePosition - controller.lastMousePosition) * controller.mouseSensitivity; + controller.lastMousePosition = currentMousePosition; + const auto &sceneTag = getComponent(entity); + if (!sceneTag.isActive || sceneTag.id != sceneRendered) + continue; + const auto &cameraComponent = getComponent(entity); + if (cameraComponent.resizing || !event::isMouseDown(NEXO_MOUSE_LEFT)) + continue; controller.yaw += -mouseDelta.x; controller.pitch += -mouseDelta.y; @@ -151,7 +160,7 @@ namespace nexo::system { glm::quat qPitch = glm::angleAxis(glm::radians(controller.pitch), glm::vec3(1.0f, 0.0f, 0.0f)); glm::quat qYaw = glm::angleAxis(glm::radians(controller.yaw), glm::vec3(0.0f, 1.0f, 0.0f)); - auto &transform = coord->getComponent(camera); + auto &transform = getComponent(entity); transform.quat = glm::normalize(qYaw * qPitch); event.consumed = true; } @@ -165,25 +174,25 @@ namespace nexo::system { void PerspectiveCameraTargetSystem::handleEvent(event::EventMouseScroll &event) { - auto const &renderContext = coord->getSingletonComponent(); + auto const &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); + constexpr float zoomSpeed = 0.5f; - for (const auto &camera : entities) + for (auto entity : entities) { - auto tag = coord->getComponent(camera); + auto &tag = getComponent(entity); if (!tag.isActive || sceneRendered != tag.id) continue; - constexpr float zoomSpeed = 0.5f; - auto &target = coord->getComponent(camera); + auto &target = getComponent(entity); target.distance -= event.y * zoomSpeed; - if(target.distance < 0.1f) + if (target.distance < 0.1f) target.distance = 0.1f; - auto &transformCamera = coord->getComponent(camera); - auto const &transformTarget = coord->getComponent(target.targetEntity); + 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. @@ -203,7 +212,7 @@ namespace nexo::system { void PerspectiveCameraTargetSystem::handleEvent(event::EventMouseMove &event) { - auto const &renderContext = coord->getSingletonComponent(); + const auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; @@ -211,41 +220,34 @@ namespace nexo::system { glm::vec2 currentMousePosition(event.x, event.y); - for (const auto &entity : entities) + for (auto entity : entities) { - auto &targetComp = coord->getComponent(entity); - auto tag = coord->getComponent(entity); - auto const &cameraComponent = coord->getComponent(entity); - - if (!tag.isActive || sceneRendered != tag.id || cameraComponent.resizing) - { - targetComp.lastMousePosition = currentMousePosition; - continue; - } - - if (!event::isMouseDown(NEXO_MOUSE_RIGHT)) - { - targetComp.lastMousePosition = currentMousePosition; - continue; - } + 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)) + { + targetComponent.lastMousePosition = currentMousePosition; + continue; + } - auto &transformCamera = coord->getComponent(entity); - auto const &transformTarget = coord->getComponent(targetComp.targetEntity); + auto &transformCameraComponent = coord->getComponent(entity); + const auto &transformTargetComponent = coord->getComponent(targetComponent.targetEntity); - float deltaX = targetComp.lastMousePosition.x - currentMousePosition.x; - float deltaY = targetComp.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)); // Prevent excessive pitch rotation when the camera is nearly vertical. - glm::vec3 front = glm::normalize(transformTarget.pos - transformCamera.pos); + glm::vec3 front = glm::normalize(transformTargetComponent.pos - transformCameraComponent.pos); auto sgn = [](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 = (transformCamera.pos - transformTarget.pos); + glm::vec3 offset = (transformCameraComponent.pos - transformTargetComponent.pos); glm::quat qYaw = glm::angleAxis(xAngle, glm::vec3(0, 1, 0)); @@ -260,14 +262,14 @@ namespace nexo::system { glm::vec3 newOffset = incrementalRotation * offset; - newOffset = glm::normalize(newOffset) * targetComp.distance; + newOffset = glm::normalize(newOffset) * targetComponent.distance; - transformCamera.pos = transformTarget.pos + newOffset; + transformCameraComponent.pos = transformTargetComponent.pos + newOffset; - glm::vec3 newFront = glm::normalize(transformTarget.pos - transformCamera.pos); - transformCamera.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))); - targetComp.lastMousePosition = currentMousePosition; + targetComponent.lastMousePosition = currentMousePosition; event.consumed = true; } } diff --git a/engine/src/systems/CameraSystem.hpp b/engine/src/systems/CameraSystem.hpp index 817037f43..98340cc17 100644 --- a/engine/src/systems/CameraSystem.hpp +++ b/engine/src/systems/CameraSystem.hpp @@ -14,9 +14,14 @@ #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" namespace nexo::system { /** @@ -34,9 +39,15 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class CameraContextSystem : public ecs::System { + class CameraContextSystem : public ecs::GroupSystem< + ecs::Owned< + ecs::Read>, + ecs::NonOwned< + ecs::Read, + ecs::Read>, + ecs::WriteSingleton> { public: - void update() const; + void update(); }; /** @@ -52,12 +63,18 @@ namespace nexo::system { * - components::CameraComponent * - components::TransformComponent */ - class PerspectiveCameraControllerSystem : public ecs::System, LISTENS_TO( - event::EventMouseScroll, - event::EventMouseMove) { + class PerspectiveCameraControllerSystem : 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) const; + void update(Timestep ts); void handleEvent(event::EventMouseScroll &event) override; void handleEvent(event::EventMouseMove &event) override; @@ -76,9 +93,15 @@ namespace nexo::system { * - components::CameraComponent * - components::TransformComponent */ - class PerspectiveCameraTargetSystem : public ecs::System, LISTENS_TO( - event::EventMouseScroll, - event::EventMouseMove) { + class PerspectiveCameraTargetSystem : 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; diff --git a/engine/src/systems/LightSystem.cpp b/engine/src/systems/LightSystem.cpp index 6df39e657..00c7b5d63 100644 --- a/engine/src/systems/LightSystem.cpp +++ b/engine/src/systems/LightSystem.cpp @@ -15,7 +15,7 @@ #include "LightSystem.hpp" namespace nexo::system { - void LightSystem::update() const + void LightSystem::update() { m_ambientLightSystem->update(); m_directionalLightSystem->update(); diff --git a/engine/src/systems/LightSystem.hpp b/engine/src/systems/LightSystem.hpp index 8428e2429..379d14a1a 100644 --- a/engine/src/systems/LightSystem.hpp +++ b/engine/src/systems/LightSystem.hpp @@ -43,7 +43,7 @@ namespace nexo::system { m_pointLightSystem(pointSystem), m_spotLightSystem(spotSystem) {} - void update() const; + void update(); private: std::shared_ptr m_ambientLightSystem = nullptr; std::shared_ptr m_directionalLightSystem = nullptr; diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index a9c2c90b5..d85657757 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -83,9 +83,9 @@ namespace nexo::system { shader->unbind(); } - void RenderSystem::update() const + void RenderSystem::update() { - auto &renderContext = coord->getSingletonComponent(); + auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; @@ -93,6 +93,20 @@ namespace nexo::system { setupLights(renderContext.renderer3D.getShader(), renderContext.sceneLights); + auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag& tag) { return tag.id; } + ); + + const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + + //TODO: Throw exception here ? + if (!partition) + return; + + const auto transformSpan = get(); + const auto renderSpan = get(); + const auto entitySpan = m_group->entities(); + while (!renderContext.cameras.empty()) { const auto &camera = renderContext.cameras.front(); @@ -105,23 +119,22 @@ namespace nexo::system { camera.renderTarget->clearAttachment(1, -1); } - for (const auto entity : entities) - { - auto tag = coord->getComponent(entity); - if (!tag.isRendered || sceneRendered != tag.id) - continue; - const auto transform = coord->getComponent(entity); - const auto renderComponent = coord->getComponent(entity); - if (renderComponent.isRendered) - { - //TODO: Pass to a single renderer - renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition); + + for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) + { + const auto &transform = transformSpan[i]; + const auto &render = renderSpan[i]; + const ecs::Entity entity = entitySpan[i]; + if (render.isRendered) + { + renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition); auto context = std::make_shared(); context->renderer3D = renderContext.renderer3D; - renderComponent.draw(context, transform, entity); + render.draw(context, transform, entity); renderContext.renderer3D.endScene(); - } - } + } + } + if (camera.renderTarget != nullptr) { camera.renderTarget->unbind(); diff --git a/engine/src/systems/RenderSystem.hpp b/engine/src/systems/RenderSystem.hpp index 7fce365ba..a4a3325a8 100644 --- a/engine/src/systems/RenderSystem.hpp +++ b/engine/src/systems/RenderSystem.hpp @@ -13,6 +13,12 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include "Access.hpp" +#include "GroupSystem.hpp" +#include "components/Render.hpp" +#include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" +#include "components/Transform.hpp" #include "ecs/System.hpp" namespace nexo::system { @@ -34,8 +40,14 @@ namespace nexo::system { * - components::TransformComponent * - components::RenderComponent */ - class RenderSystem : public ecs::System { - public: - void update() const; + class RenderSystem : public ecs::GroupSystem< + ecs::Owned< + ecs::Read, + ecs::Read>, + ecs::NonOwned< + ecs::Read>, + ecs::WriteSingleton> { + public: + void update(); }; } diff --git a/engine/src/systems/lights/AmbientLightSystem.cpp b/engine/src/systems/lights/AmbientLightSystem.cpp index 335e6322a..b636fb03f 100644 --- a/engine/src/systems/lights/AmbientLightSystem.cpp +++ b/engine/src/systems/lights/AmbientLightSystem.cpp @@ -13,26 +13,34 @@ /////////////////////////////////////////////////////////////////////////////// #include "AmbientLightSystem.hpp" -#include "components/RenderContext.hpp" -#include "components/SceneComponents.hpp" + +#include "components/Light.hpp" #include "ecs/Coordinator.hpp" namespace nexo::system { - void AmbientLightSystem::update() const + void AmbientLightSystem::update() { - auto &renderContext = coord->getSingletonComponent(); + auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - for (const auto &ambientLights : entities) + auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag& tag) { return tag.id; } + ); + + const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + + //TODO: Throw exception here ? + if (!partition) + return; + + const auto ambientSpan = get(); + + for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { - auto tag = coord->getComponent(ambientLights); - if (!tag.isRendered || sceneRendered != tag.id) - continue; - const auto &ambientComponent = coord->getComponent(ambientLights); - renderContext.sceneLights.ambientLight = ambientComponent.color; + renderContext.sceneLights.ambientLight = ambientSpan[i].color; break; } } diff --git a/engine/src/systems/lights/AmbientLightSystem.hpp b/engine/src/systems/lights/AmbientLightSystem.hpp index 335e65b8b..db437181f 100644 --- a/engine/src/systems/lights/AmbientLightSystem.hpp +++ b/engine/src/systems/lights/AmbientLightSystem.hpp @@ -13,7 +13,10 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include "GroupSystem.hpp" #include "ecs/System.hpp" +#include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" namespace nexo::system { @@ -30,8 +33,13 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class AmbientLightSystem : public ecs::System { - public: - void update() const ; + class AmbientLightSystem : public ecs::GroupSystem< + ecs::Owned< + ecs::Read>, + ecs::NonOwned< + ecs::Read>, + ecs::WriteSingleton> { + public: + void update(); }; } diff --git a/engine/src/systems/lights/DirectionalLightsSystem.cpp b/engine/src/systems/lights/DirectionalLightsSystem.cpp index 79b23517c..726250baf 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.cpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.cpp @@ -19,21 +19,29 @@ #include "ecs/Coordinator.hpp" namespace nexo::system { - void DirectionalLightsSystem::update() const + void DirectionalLightsSystem::update() { - auto &renderContext = coord->getSingletonComponent(); + auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - for (const auto &directionalLights : entities) + auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag& tag) { return tag.id; } + ); + + const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + + //TODO: Throw exception here ? + if (!partition) + return; + + const auto directionalLightSpan = get(); + + for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { - auto tag = coord->getComponent(directionalLights); - if (!tag.isRendered || sceneRendered != tag.id) - continue; - const auto &directionalComponent = coord->getComponent(directionalLights); - renderContext.sceneLights.directionalLights[renderContext.sceneLights.directionalLightCount++] = directionalComponent; + renderContext.sceneLights.directionalLights[renderContext.sceneLights.directionalLightCount++] = directionalLightSpan[i]; } } } diff --git a/engine/src/systems/lights/DirectionalLightsSystem.hpp b/engine/src/systems/lights/DirectionalLightsSystem.hpp index aaac9ea45..c103316aa 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.hpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.hpp @@ -14,6 +14,10 @@ #pragma once #include "ecs/System.hpp" +#include "ecs/GroupSystem.hpp" +#include "components/Light.hpp" +#include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" namespace nexo::system { @@ -30,8 +34,13 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class DirectionalLightsSystem : public ecs::System { + class DirectionalLightsSystem : public ecs::GroupSystem< + ecs::Owned< + ecs::Read>, + ecs::NonOwned< + ecs::Read>, + ecs::WriteSingleton> { public: - void update() const; + void update(); }; } diff --git a/engine/src/systems/lights/PointLightsSystem.cpp b/engine/src/systems/lights/PointLightsSystem.cpp index 4e72cd292..0fe2756bb 100644 --- a/engine/src/systems/lights/PointLightsSystem.cpp +++ b/engine/src/systems/lights/PointLightsSystem.cpp @@ -19,21 +19,29 @@ #include "ecs/Coordinator.hpp" namespace nexo::system { - void PointLightsSystem::update() const + void PointLightsSystem::update() { - auto &renderContext = coord->getSingletonComponent(); + auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - for (const auto &pointLights : entities) + auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag& tag) { return tag.id; } + ); + + const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + + //TODO: Throw exception here ? + if (!partition) + return; + + const auto pointLightSpan = get(); + + for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { - auto tag = coord->getComponent(pointLights); - if (!tag.isRendered || sceneRendered != tag.id) - continue; - const auto &pointComponent = coord->getComponent(pointLights); - renderContext.sceneLights.pointLights[renderContext.sceneLights.pointLightCount++] = pointComponent; + renderContext.sceneLights.pointLights[renderContext.sceneLights.pointLightCount++] = pointLightSpan[i]; } } } diff --git a/engine/src/systems/lights/PointLightsSystem.hpp b/engine/src/systems/lights/PointLightsSystem.hpp index 1608c0e9c..feca65170 100644 --- a/engine/src/systems/lights/PointLightsSystem.hpp +++ b/engine/src/systems/lights/PointLightsSystem.hpp @@ -14,6 +14,10 @@ #pragma once #include "ecs/System.hpp" +#include "components/Light.hpp" +#include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" +#include "ecs/GroupSystem.hpp" namespace nexo::system { @@ -30,8 +34,13 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class PointLightsSystem : public ecs::System { + class PointLightsSystem : public ecs::GroupSystem< + ecs::Owned< + ecs::Read>, + ecs::NonOwned< + ecs::Read>, + ecs::WriteSingleton> { public: - void update() const; + void update(); }; } diff --git a/engine/src/systems/lights/SpotLightsSystem.cpp b/engine/src/systems/lights/SpotLightsSystem.cpp index a9fa8eab4..2030a2795 100644 --- a/engine/src/systems/lights/SpotLightsSystem.cpp +++ b/engine/src/systems/lights/SpotLightsSystem.cpp @@ -20,21 +20,29 @@ namespace nexo::system { - void SpotLightsSystem::update() const + void SpotLightsSystem::update() { - auto &renderContext = coord->getSingletonComponent(); + auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - for (const auto &spotLights : entities) + auto scenePartition = m_group->getPartitionView( + [](const components::SceneTag& tag) { return tag.id; } + ); + + const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + + //TODO: Throw exception here ? + if (!partition) + return; + + const auto spotLightSpan = get(); + + for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { - auto tag = coord->getComponent(spotLights); - if (!tag.isRendered || sceneRendered != tag.id) - continue; - const auto &spotComponent = coord->getComponent(spotLights); - renderContext.sceneLights.spotLights[renderContext.sceneLights.spotLightCount++] = spotComponent; + renderContext.sceneLights.spotLights[renderContext.sceneLights.spotLightCount++] = spotLightSpan[i]; } } } diff --git a/engine/src/systems/lights/SpotLightsSystem.hpp b/engine/src/systems/lights/SpotLightsSystem.hpp index 947bcb68a..4e6b5f1cf 100644 --- a/engine/src/systems/lights/SpotLightsSystem.hpp +++ b/engine/src/systems/lights/SpotLightsSystem.hpp @@ -13,7 +13,12 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include "GroupSystem.hpp" #include "ecs/System.hpp" +#include "ecs/GroupSystem.hpp" +#include "components/Light.hpp" +#include "components/RenderContext.hpp" +#include "components/SceneComponents.hpp" namespace nexo::system { @@ -30,8 +35,13 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class SpotLightsSystem : public ecs::System { + class SpotLightsSystem : public ecs::GroupSystem< + ecs::Owned< + ecs::Read>, + ecs::NonOwned< + ecs::Read>, + ecs::WriteSingleton> { public: - void update() const; + void update(); }; } From d6cb8660614aea83edcc69a1b27999a825de1d13 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 8 Apr 2025 16:24:08 +0900 Subject: [PATCH 050/450] refactor(ecs-opti): refactor the system registration in the init phase --- engine/src/Application.cpp | 46 +++++++++----------------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index 0ceb803d3..26aa707b4 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -29,6 +29,7 @@ #include "Timestep.hpp" #include "renderer/RendererExceptions.hpp" #include "systems/CameraSystem.hpp" +#include "systems/RenderSystem.hpp" #include "systems/lights/DirectionalLightsSystem.hpp" #include "systems/lights/PointLightsSystem.hpp" @@ -162,43 +163,16 @@ namespace nexo { void Application::registerSystems() { - m_cameraContextSystem = registerSystem(); - - m_perspectiveCameraControllerSystem = registerSystem(); - - m_perspectiveCameraTargetSystem = registerSystem(); - - m_renderSystem = registerSystem(); - - auto pointLightSystem = registerSystem(); - - auto directionalLightSystem = registerSystem(); - - auto spotLightSystem = registerSystem(); - - auto ambientLightSystem = registerSystem(); + m_cameraContextSystem = m_coordinator->registerGroupSystem(); + m_perspectiveCameraControllerSystem = m_coordinator->registerQuerySystem(); + m_perspectiveCameraTargetSystem = m_coordinator->registerQuerySystem(); + m_renderSystem = 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); } From f71b3c1120d3b66c7abe8c11a2bd7b5a05f7b163 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 9 Apr 2025 21:09:32 +0900 Subject: [PATCH 051/450] fix(ecs-opti): error handling + style --- engine/src/Application.hpp | 12 - engine/src/ecs/ComponentArray.hpp | 871 ++++++++++++++++------------ engine/src/ecs/Components.hpp | 10 +- engine/src/ecs/Coordinator.hpp | 21 +- engine/src/ecs/ECSExceptions.hpp | 6 + engine/src/ecs/Entity.cpp | 10 +- engine/src/ecs/Entity.hpp | 1 + engine/src/ecs/Group.hpp | 91 ++- engine/src/ecs/GroupSystem.hpp | 33 +- engine/src/ecs/QuerySystem.hpp | 41 +- engine/src/ecs/System.cpp | 4 +- engine/src/ecs/System.hpp | 30 +- engine/src/systems/CameraSystem.cpp | 2 +- 13 files changed, 641 insertions(+), 491 deletions(-) diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index d6d196114..da26f3027 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -231,18 +231,6 @@ namespace nexo { void registerSignalListeners(); void registerEcsComponents() const; void registerWindowCallbacks() const; - template - std::shared_ptr registerSystem() - { - auto system = m_coordinator->registerSystem(); - - ecs::Signature signature; - (signature.set(m_coordinator->getComponentType()), ...); - - m_coordinator->setSystemSignature(signature); - - return system; - } void registerSystems(); void displayProfileResults() const; diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 2e2e397c2..2bc08ea5a 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -20,387 +20,496 @@ #include #include +#include +#include namespace nexo::ecs { - /** - * @class IComponentArray - * @brief Base interface for all component array types. - * - * Provides the common interface that all concrete component arrays must implement, - * allowing type-erased storage and manipulation of components. - */ - class IComponentArray { - public: - virtual ~IComponentArray() = default; - - /** - * @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 - */ - virtual bool hasComponent(Entity entity) const = 0; - - /** - * @brief Handles cleanup when an entity is destroyed - * @param entity The entity being destroyed - */ - virtual void entityDestroyed(Entity entity) = 0; - }; - - /** - * @class ComponentArray - * @brief Stores and manages components of a specific type T. - * - * Implements a sparse-dense storage pattern for efficient component storage and retrieval. - * Components are stored contiguously for cache-friendly access, while maintaining - * O(1) lookups via entity IDs. - * - * @tparam T The component type stored in this array - * @tparam capacity Initial capacity for the sparse array - */ - template= 1)>> - class alignas(64) ComponentArray final : public IComponentArray { - public: - /** - * @brief Type alias for the component type - */ - using component_type = T; - - /** - * @brief Constructs a new component array with initial capacity - * - * Initializes the sparse array with capacity elements, and reserves space - * for the dense arrays. - */ - ComponentArray() - { - m_sparse.resize(capacity, INVALID_ENTITY); - m_dense.reserve(capacity); - m_componentArray.reserve(capacity); - } - - /** - * @brief Inserts a new component for the given entity. - * - * If the entity already has a component, the operation is silently ignored. - * - * @param entity The entity to add the component to - * @param component The component instance to add - * @throws OutOfRange if entity ID exceeds MAX_ENTITIES - */ - void insertData(Entity entity, T component) - { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); - - // Ensure m_sparse can hold this entity index. - ensureSparseCapacity(entity); - - if (hasComponent(entity)) { - LOG(NEXO_WARN, "Entity {} already has component: {}", entity, typeid(T).name()); - return; - } - - const size_t newIndex = m_size; - m_sparse[entity] = newIndex; - m_dense.push_back(entity); - m_componentArray.push_back(std::move(component)); - ++m_size; - } - - /** - * @brief Removes the component for the given entity. - * - * If the entity is grouped (i.e. within the first m_groupSize entries), - * then adjusts the group boundary appropriately. - * - * @param entity The entity to remove the component from - * @throws ComponentNotFound if the entity doesn't have the component - */ - void removeData(Entity entity) - { - if (entity >= m_sparse.size() || !hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - - size_t indexToRemove = m_sparse[entity]; - - // If the entity is part of the group, remove it from the group first. - if (indexToRemove < m_groupSize) { - // Swap with the last grouped element if not already at the end. - size_t groupLastIndex = m_groupSize - 1; - 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[groupLastIndex]] = groupLastIndex; - } - --m_groupSize; - // Now update indexToRemove to reflect the new position. - indexToRemove = groupLastIndex; - } - - // Standard removal from the overall array: - const size_t lastIndex = m_size - 1; - if (indexToRemove != lastIndex) { - std::swap(m_componentArray[indexToRemove], m_componentArray[lastIndex]); - std::swap(m_dense[indexToRemove], m_dense[lastIndex]); - m_sparse[m_dense[indexToRemove]] = indexToRemove; - } - m_sparse[entity] = INVALID_ENTITY; - m_componentArray.pop_back(); - m_dense.pop_back(); - --m_size; - - shrinkIfNeeded(); - } - - /** - * @brief Retrieves a component for the given entity - * - * @param entity The entity to get the component from - * @return Reference to the component - * @throws ComponentNotFound if the entity doesn't have the component - */ - [[nodiscard]] T& getData(Entity entity) - { - if (entity >= m_sparse.size() || !hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - return m_componentArray[m_sparse[entity]]; - } - - /** - * @brief Retrieves a component for the given entity (const version) - * - * @param entity The entity to get the component from - * @return Const reference to the component - * @throws ComponentNotFound if the entity doesn't have the component - */ - [[nodiscard]] const T& getData(Entity entity) const - { - if (entity >= m_sparse.size() || !hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - return m_componentArray[m_sparse[entity]]; - } - - /** - * @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 - { - return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); - } - - /** - * @brief Removes the component from an entity when it's destroyed - * - * @param entity The entity being destroyed - */ - void entityDestroyed(Entity entity) override - { - if (hasComponent(entity)) - removeData(entity); - } - - /** - * @brief Gets the total number of components in the array - * - * @return The number of active components - */ - [[nodiscard]] size_t size() const - { - return m_size; - } - - /** - * @brief Gets the entity at the given index in the dense array - * - * @param index The index to look up - * @return The entity at that index - * @throws OutOfRange if the index is invalid - */ - [[nodiscard]] Entity getEntityAtIndex(size_t index) const - { - if (index >= m_size) - THROW_EXCEPTION(OutOfRange, index); - return m_dense[index]; - } - - /** - * @brief Gets a span view of all components - * - * @return Span of component data - */ - [[nodiscard]] std::span rawData() - { - return std::span(m_componentArray.data(), m_size); - } - - /** - * @brief Gets a const span view of all components - * - * @return Const span of component data - */ - [[nodiscard]] std::span rawData() const - { - return std::span(m_componentArray.data(), m_size); - } - - /** - * @brief Gets a const span view of all entities with this component - * - * @return Const span of entity IDs - */ - [[nodiscard]] std::span entities() const - { - return std::span(m_dense.data(), m_size); - } - - /** - * @brief Moves the component for the given entity into the group region. - * - * This operation swaps the entity with the one at m_groupSize - * and then increments the group pointer. - * - * @param entity The entity to add to the group - * @throws ComponentNotFound if the entity doesn't have the component - */ - void addToGroup(Entity entity) - { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - - size_t index = m_sparse[entity]; - 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]); - // Update sparse mapping for both swapped entities. - m_sparse[m_dense[index]] = index; - m_sparse[m_dense[m_groupSize]] = m_groupSize; - } - ++m_groupSize; - } - - /** - * @brief Moves the component for the given entity out of the group region. - * - * This operation swaps the entity with the last grouped element - * and then decrements the group pointer. - * - * @param entity The entity to remove from the group - * @throws ComponentNotFound if the entity doesn't have the component - */ - void removeFromGroup(Entity entity) - { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - - size_t index = m_sparse[entity]; - if (index >= m_groupSize) - return; - // Decrement group size first; the entity to remove is at index. - --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[m_groupSize]] = m_groupSize; - } - } - - /** - * @brief Forces a component to be set at a specific index (internal use only) - * - * Used primarily during group reordering operations. - * - * @param index The index to set the component at - * @param entity The entity to associate with this component - * @param component The component data to set - * @throws OutOfRange if the index is invalid - */ - void forceSetComponentAt(size_t index, Entity entity, T component) - { - if (index >= m_size) - THROW_EXCEPTION(OutOfRange, index); - - // Update the sparse mapping - m_sparse[entity] = index; - - // Update the dense array - m_dense[index] = entity; - - // Update the component - m_componentArray[index] = std::move(component); - } - - /** - * @brief Gets the number of entities in the group region - * - * @return Number of grouped entities - */ - [[nodiscard]] size_t groupSize() const - { - return m_groupSize; - } - - private: - // Dense storage for components. - std::vector m_componentArray; - // Sparse mapping: maps entity ID to index in the dense arrays. - std::vector m_sparse; - // Dense storage for entity IDs. - std::vector m_dense; - // Current number of active components. - size_t m_size = 0; - // The first m_groupSize entries in m_dense/m_componentArray are considered "grouped". - 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) - { - if (entity >= m_sparse.size()) { - size_t newSize = m_sparse.size(); - if (newSize == 0) - newSize = capacity; - while (entity >= newSize) - newSize *= 2; - m_sparse.resize(newSize, INVALID_ENTITY); - } - } - - /** - * @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() - { - auto shrinkVector = [this](auto& vec) { - size_t currentCapacity = vec.capacity(); - if (m_size < currentCapacity / 2) { - size_t newCapacity = static_cast(m_size * 1.25); - if (newCapacity < capacity) - newCapacity = capacity; - std::vector::type::value_type> newVec; - newVec.reserve(newCapacity); - newVec.assign(vec.begin(), vec.begin() + m_size); - vec.swap(newVec); - } - }; - - shrinkVector(m_componentArray); - shrinkVector(m_dense); - } - }; + /** + * @class IComponentArray + * @brief Base interface for all component array types. + * + * Provides the common interface that all concrete component arrays must implement, + * 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. + */ + class IComponentArray { + public: + virtual ~IComponentArray() = default; + + /** + * @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 + */ + virtual bool hasComponent(Entity entity) const = 0; + + /** + * @brief Handles cleanup when an entity is destroyed + * @param entity The entity being destroyed + */ + virtual void entityDestroyed(Entity entity) = 0; + }; + + /** + * @class ComponentArray + * @brief Stores and manages components of a specific type T. + * + * Implements a sparse-dense storage pattern for efficient component storage and retrieval. + * Components are stored contiguously for cache-friendly access, while maintaining + * O(1) lookups via entity IDs. + * + * @tparam T The component type stored in this array + * @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. + */ + template= 1)>> + class alignas(64) ComponentArray : public IComponentArray { + public: + /** + * @brief Type alias for the component type + */ + using component_type = T; + + /** + * @brief Constructs a new component array with initial capacity + * + * Initializes the sparse array with capacity elements, and reserves space + * for the dense arrays. + */ + ComponentArray() + { + m_sparse.resize(capacity, INVALID_ENTITY); + m_dense.reserve(capacity); + m_componentArray.reserve(capacity); + } + + // Move constructor + ComponentArray(ComponentArray&& other) noexcept + : m_componentArray(std::move(other.m_componentArray)) + , m_sparse(std::move(other.m_sparse)) + , m_dense(std::move(other.m_dense)) + , m_size(other.m_size) + , m_groupSize(other.m_groupSize) + { + other.m_size = 0; + other.m_groupSize = 0; + } + + // Move assignment + ComponentArray& operator=(ComponentArray&& other) noexcept + { + if (this != &other) { + m_componentArray = std::move(other.m_componentArray); + m_sparse = std::move(other.m_sparse); + m_dense = std::move(other.m_dense); + m_size = other.m_size; + m_groupSize = other.m_groupSize; + + other.m_size = 0; + other.m_groupSize = 0; + } + return *this; + } + + /** + * @brief Inserts a new component for the given entity. + * + * @tparam U Type of component parameter (allows for perfect forwarding) + * @param entity The entity to add the component to + * @param component The component instance to add + * @throws ArrayBoundsException if entity ID exceeds MAX_ENTITIES + * + * @pre The entity must be a valid entity ID + */ + template + void insert(Entity entity, U&& component) + { + if (entity >= MAX_ENTITIES) + THROW_EXCEPTION(OutOfRange, entity); + + // Ensure m_sparse can hold this entity index. + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "Entity {} already has component: {}", entity, typeid(T).name()); + return; + } + + const size_t newIndex = m_size; + m_sparse[entity] = newIndex; + m_dense.push_back(entity); + m_componentArray.push_back(std::forward(component)); + ++m_size; + } + + /** + * @brief Removes the component for the given entity. + * + * If the entity is grouped (i.e. within the first m_groupSize entries), + * then adjusts the group boundary appropriately. + * + * @param entity The entity to remove the component from + * @throws ComponentNotFoundException if the entity doesn't have the component + * + * @pre The entity must have the component + */ + void remove(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t indexToRemove = m_sparse[entity]; + + // If the entity is part of the group, remove it from the group first. + if (indexToRemove < m_groupSize) { + // Swap with the last grouped element if not already at the end. + size_t groupLastIndex = m_groupSize - 1; + 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[groupLastIndex]] = groupLastIndex; + } + --m_groupSize; + // Now update indexToRemove to reflect the new position. + indexToRemove = groupLastIndex; + } + + // Standard removal from the overall array: + const size_t lastIndex = m_size - 1; + if (indexToRemove != lastIndex) { + std::swap(m_componentArray[indexToRemove], m_componentArray[lastIndex]); + std::swap(m_dense[indexToRemove], m_dense[lastIndex]); + m_sparse[m_dense[indexToRemove]] = indexToRemove; + } + m_sparse[entity] = INVALID_ENTITY; + m_componentArray.pop_back(); + m_dense.pop_back(); + --m_size; + + shrinkIfNeeded(); + } + + /** + * @brief Retrieves a component for the given entity + * + * @param entity The entity to get the component from + * @return Reference to the component + * @throws ComponentNotFoundException if the entity doesn't have the component + * + * @pre The entity must have the component + */ + [[nodiscard]] T& get(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + return m_componentArray[m_sparse[entity]]; + } + + /** + * @brief Retrieves a component for the given entity (const version) + * + * @param entity The entity to get the component from + * @return Const reference to the component + * @throws ComponentNotFoundException if the entity doesn't have the component + * + * @pre The entity must have the component + */ + [[nodiscard]] const T& get(Entity entity) const + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + return m_componentArray[m_sparse[entity]]; + } + + /** + * @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 + { + return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); + } + + /** + * @brief Removes the component from an entity when it's destroyed + * + * @param entity The entity being destroyed + */ + void entityDestroyed(Entity entity) override + { + if (hasComponent(entity)) + remove(entity); + } + + /** + * @brief Gets the total number of components in the array + * + * @return The number of active components + */ + [[nodiscard]] constexpr size_t size() const + { + return m_size; + } + + /** + * @brief Gets the entity at the given index in the dense array + * + * @param index The index to look up + * @return The entity at that index + * @throws ArrayBoundsException if the index is invalid + * + * @pre The index must be less than the array size + */ + [[nodiscard]] Entity getEntityAtIndex(size_t index) const + { + if (index >= m_size) + THROW_EXCEPTION(OutOfRange, index); + return m_dense[index]; + } + + /** + * @brief Gets a span view of all components + * + * @return Span of component data + */ + [[nodiscard]] std::span getAllComponents() + { + return std::span(m_componentArray.data(), m_size); + } + + /** + * @brief Gets a const span view of all components + * + * @return Const span of component data + */ + [[nodiscard]] std::span getAllComponents() const + { + return std::span(m_componentArray.data(), m_size); + } + + /** + * @brief Gets a const span view of all entities with this component + * + * @return Const span of entity IDs + */ + [[nodiscard]] std::span entities() const + { + return std::span(m_dense.data(), m_size); + } + + /** + * @brief Moves the component for the given entity into the group region. + * + * This operation swaps the entity with the one at m_groupSize + * and then increments the group pointer. + * + * @param entity The entity to add to the group + * @throws ComponentNotFoundException if the entity doesn't have the component + * + * @pre The entity must have the component + */ + void addToGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + 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]); + // Update sparse mapping for both swapped entities. + m_sparse[m_dense[index]] = index; + m_sparse[m_dense[m_groupSize]] = m_groupSize; + } + ++m_groupSize; + } + + /** + * @brief Moves the component for the given entity out of the group region. + * + * This operation swaps the entity with the last grouped element + * and then decrements the group pointer. + * + * @param entity The entity to remove from the group + * @throws ComponentNotFoundException if the entity doesn't have the component + * + * @pre The entity must have the component + */ + void removeFromGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + if (index >= m_groupSize) + return; + // Decrement group size first; the entity to remove is at index. + --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[m_groupSize]] = m_groupSize; + } + } + + /** + * @brief Forces a component to be set at a specific index (internal use only) + * + * Used primarily during group reordering operations. + * + * @param index The index to set the component at + * @param entity The entity to associate with this component + * @param component The component data to set + * @throws OutOfRange if the index is invalid + */ + void forceSetComponentAt(size_t index, Entity entity, T component) + { + if (index >= m_size) + THROW_EXCEPTION(OutOfRange, index); + + // Update the sparse mapping + m_sparse[entity] = index; + + // Update the dense array + m_dense[index] = entity; + + // Update the component + m_componentArray[index] = std::move(component); + } + + /** + * @brief Batch insertion of multiple components + * + * @tparam EntityIt Iterator type for entities + * @tparam CompIt Iterator type for components + * @param entitiesBegin Start iterator for entities + * @param entitiesEnd End iterator for entities + * @param componentsBegin Start iterator for components + * + * @pre The range [entitiesBegin, entitiesEnd) must be valid + * @pre componentsBegin must point to a valid range of at least (entitiesEnd - entitiesBegin) elements + */ + template + void insertBatch(EntityIt entitiesBegin, EntityIt entitiesEnd, CompIt componentsBegin) + { + auto compIt = componentsBegin; + for (auto entityIt = entitiesBegin; entityIt != entitiesEnd; ++entityIt, ++compIt) { + insert(*entityIt, *compIt); + } + } + + /** + * @brief Apply a function to each entity-component pair + * + * @tparam Func Type of function to apply + * @param func Function taking (Entity, T&) to apply to each pair + */ + template + void forEach(Func&& func) + { + for (size_t i = 0; i < m_size; ++i) { + func(m_dense[i], m_componentArray[i]); + } + } + + /** + * @brief Apply a function to each entity-component pair (const version) + * + * @tparam Func Type of function to apply + * @param func Function taking (Entity, const T&) to apply to each pair + */ + template + void forEach(Func&& func) const + { + for (size_t i = 0; i < m_size; ++i) { + func(m_dense[i], m_componentArray[i]); + } + } + + /** + * @brief Gets the number of entities in the group region + * + * @return Number of grouped entities + */ + [[nodiscard]] constexpr size_t groupSize() const + { + return m_groupSize; + } + + /** + * @brief Get the estimated memory usage of this component array + * + * @return Size in bytes of memory used by this component array + */ + [[nodiscard]] size_t memoryUsage() const + { + return sizeof(T) * m_componentArray.capacity() + + sizeof(size_t) * m_sparse.capacity() + + sizeof(Entity) * m_dense.capacity(); + } + + private: + // Dense storage for components. + std::vector m_componentArray; + // Sparse mapping: maps entity ID to index in the dense arrays. + std::vector m_sparse; + // Dense storage for entity IDs. + std::vector m_dense; + // Current number of active components. + size_t m_size = 0; + // The first m_groupSize entries in m_dense/m_componentArray are considered "grouped". + 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) + { + if (entity >= m_sparse.size()) { + size_t newSize = m_sparse.size(); + if (newSize == 0) + newSize = capacity; + while (entity >= newSize) + newSize *= 2; + m_sparse.resize(newSize, INVALID_ENTITY); + } + } + + /** + * @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() + { + if (m_size < m_componentArray.capacity() / 2 && m_componentArray.capacity() > capacity) { + // Only shrink if vectors are significantly oversized to avoid frequent reallocations + size_t newCapacity = static_cast(m_size * 1.5); + if (newCapacity < capacity) + newCapacity = capacity; + + m_componentArray.shrink_to_fit(); + m_dense.shrink_to_fit(); + + // Reserve the optimized capacity to ensure future growth is efficient + m_componentArray.reserve(newCapacity); + m_dense.reserve(newCapacity); + } + } + }; } diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index e64eade32..462b854b4 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -264,7 +264,7 @@ namespace nexo::ecs { template void addComponent(Entity entity, T component, Signature signature) { - getComponentArray()->insertData(entity, std::move(component)); + getComponentArray()->insert(entity, std::move(component)); for (auto& [key, group] : m_groupRegistry) { @@ -291,7 +291,7 @@ namespace nexo::ecs { if ((previousSignature & group->allSignature()) == group->allSignature()) group->removeFromGroup(entity); } - getComponentArray()->removeData(entity); + getComponentArray()->remove(entity); } /** @@ -317,7 +317,7 @@ namespace nexo::ecs { if ((previousSignature & group->allSignature()) == group->allSignature()) group->removeFromGroup(entity); } - componentArray->removeData(entity); + componentArray->remove(entity); return true; } @@ -332,7 +332,7 @@ namespace nexo::ecs { template [[nodiscard]] T& getComponent(Entity entity) { - return getComponentArray()->getData(entity); + return getComponentArray()->get(entity); } /** @@ -387,7 +387,7 @@ namespace nexo::ecs { if (!componentArray->hasComponent(entity)) return std::nullopt; - return componentArray->getData(entity); + return componentArray->get(entity); } /** diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 194e3186f..7d5a1ddc4 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -263,16 +263,6 @@ namespace nexo::ecs { return m_componentManager->getComponentType(); } - /** - * @brief Registers a new system within the SystemManager. - * - * @return std::shared_ptr - Shared pointer to the newly registered system. - */ - template - std::shared_ptr registerSystem() { - return m_systemManager->registerSystem(); - } - /** * @brief Registers a new query system * @@ -282,7 +272,16 @@ namespace nexo::ecs { */ template std::shared_ptr registerQuerySystem(Args&&... args) { - return m_systemManager->registerQuerySystem(std::forward(args)...); + auto newQuerySystem = m_systemManager->registerQuerySystem(std::forward(args)...); + std::span livingEntities = m_entityManager->getLivingEntities(); + Signature querySystemSignature = newQuerySystem->getSignature(); + for (Entity entity : livingEntities) { + Signature entitySignature = m_entityManager->getSignature(entity); + if ((entitySignature & querySystemSignature) == querySystemSignature) { + newQuerySystem->entities.insert(entity); + } + } + return newQuerySystem; } /** diff --git a/engine/src/ecs/ECSExceptions.hpp b/engine/src/ecs/ECSExceptions.hpp index c14644f09..3a6e1e846 100644 --- a/engine/src/ecs/ECSExceptions.hpp +++ b/engine/src/ecs/ECSExceptions.hpp @@ -47,6 +47,12 @@ namespace nexo::ecs { : 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) {} + }; + class ComponentNotRegistered final : public Exception { public: explicit ComponentNotRegistered(const std::source_location loc = std::source_location::current()) diff --git a/engine/src/ecs/Entity.cpp b/engine/src/ecs/Entity.cpp index 55f710d33..a9a8b63c2 100644 --- a/engine/src/ecs/Entity.cpp +++ b/engine/src/ecs/Entity.cpp @@ -15,6 +15,7 @@ #include "ECSExceptions.hpp" #include +#include namespace nexo::ecs { @@ -41,12 +42,15 @@ namespace nexo::ecs { if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); - m_signatures[entity].reset(); - - m_availableEntities.push_front(entity); auto it = std::find(m_livingEntities.begin(), m_livingEntities.end(), entity); if (it != m_livingEntities.end()) m_livingEntities.erase(it); + else + return; + m_signatures[entity].reset(); + + m_availableEntities.push_front(entity); + } void EntityManager::setSignature(const Entity entity, const Signature signature) diff --git a/engine/src/ecs/Entity.hpp b/engine/src/ecs/Entity.hpp index 3b982d008..1c0e34050 100644 --- a/engine/src/ecs/Entity.hpp +++ b/engine/src/ecs/Entity.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "Definitions.hpp" diff --git a/engine/src/ecs/Group.hpp b/engine/src/ecs/Group.hpp index 1b6bec59f..878b339ec 100644 --- a/engine/src/ecs/Group.hpp +++ b/engine/src/ecs/Group.hpp @@ -15,6 +15,7 @@ #include "Definitions.hpp" #include "ComponentArray.hpp" +#include "ECSExceptions.hpp" #include "Exception.hpp" #include @@ -157,6 +158,8 @@ namespace nexo::ecs { : m_ownedArrays(std::move(ownedArrays)) , m_nonOwnedArrays(std::move(nonOwnedArrays)) { + if (std::tuple_size_v == 0) + THROW_EXCEPTION(InternalError, "Group must have at least one owned component"); m_ownedSignature = std::apply([](auto&&... arrays) -> Signature { Signature signature; ((signature.set(getComponentTypeID::component_type>())), ...); @@ -181,7 +184,14 @@ namespace nexo::ecs { * * @return std::size_t Number of entities. */ - std::size_t size() const { return std::get<0>(m_ownedArrays)->groupSize(); } + 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. @@ -235,6 +245,9 @@ namespace nexo::ecs { */ reference operator*() const { + if (m_index >= m_view->size()) { + THROW_EXCEPTION(OutOfRange, m_index); + } return m_view->dereference(m_index); } @@ -297,6 +310,9 @@ namespace nexo::ecs { { using FirstOwned = std::tuple_element_t<0, OwnedTuple>; 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, @@ -316,8 +332,15 @@ namespace nexo::ecs { template void eachInRange(size_t startIndex, size_t count, Func func) const { - auto firstArray = std::get<0>(m_ownedArrays); - const size_t endIndex = std::min(startIndex + count, firstArray->groupSize()); + auto firstArray = std::get<0>(m_ownedArrays); + if (!firstArray) { + THROW_EXCEPTION(InternalError, "Component array is null"); + } + + if (startIndex >= firstArray->groupSize()) { + return; // Nothing to iterate if start is beyond the end + } + const size_t endIndex = std::min(startIndex + count, firstArray->groupSize()); for (size_t i = startIndex; i < endIndex; i++) { Entity e = firstArray->getEntityAtIndex(i); @@ -386,7 +409,10 @@ namespace nexo::ecs { { if constexpr (tuple_contains_component_v) { auto compArray = getOwnedImpl(); // internal lookup in owned tuple - return compArray->rawData().subspan(0, compArray->groupSize()); + 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 @@ -406,7 +432,10 @@ namespace nexo::ecs { { if constexpr (tuple_contains_component_v) { auto compArray = getOwnedImpl(); // internal lookup in owned tuple - return compArray->rawData().subspan(0, compArray->groupSize()); + 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 @@ -417,6 +446,11 @@ namespace nexo::ecs { // Sorting API // ======================================= + void invalidateSorting() + { + m_sortingInvalidated = true; + } + /** * @brief Sorts the group by a specified component field. * @@ -430,6 +464,12 @@ namespace nexo::ecs { 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; // Get the appropriate component array @@ -457,8 +497,8 @@ namespace nexo::ecs { // Sort entities based on the extracted field from the component std::sort(entities.begin(), entities.end(), [&](Entity a, Entity b) { - const auto& compA = compArray->getData(a); - const auto& compB = compArray->getData(b); + const auto& compA = compArray->get(a); + const auto& compB = compArray->get(b); if (ascending) return extractor(compA) < extractor(compB); else @@ -571,10 +611,10 @@ namespace nexo::ecs { // Get the component and extract the key if constexpr (tuple_contains_component_v) { auto compArray = getOwnedImpl(); - return keyExtractor(compArray->getData(e)); + return keyExtractor(compArray->get(e)); } else if constexpr (tuple_contains_component_v) { auto compArray = getNonOwnedImpl(); - return keyExtractor(compArray->getData(e)); + return keyExtractor(compArray->get(e)); } else static_assert(dependent_false::value, "Component type not found in group"); }; @@ -779,7 +819,10 @@ namespace nexo::ecs { { size_t groupSize = array->groupSize(); if (newOrder.size() != groupSize) - THROW_EXCEPTION(InternalError, "New order size doesn't match group size"); + THROW_EXCEPTION(InternalError, "New order size doesn't match group size"); + + if (groupSize == 0) + return; // Nothing to reorder // Create a temporary storage for components using CompType = typename std::decay_t::component_type; @@ -787,13 +830,18 @@ namespace nexo::ecs { tempComponents.reserve(groupSize); // Copy components in the new order - for (Entity e : newOrder) - tempComponents.push_back(array->getData(e)); + try { + for (Entity e : newOrder) + tempComponents.push_back(array->get(e)); - // Update the sparse-to-dense mapping and components - for (size_t i = 0; i < groupSize; i++) { - Entity e = newOrder[i]; - array->forceSetComponentAt(i, e, std::move(tempComponents[i])); + // Update the sparse-to-dense mapping and components + for (size_t i = 0; i < groupSize; i++) { + Entity e = newOrder[i]; + array->forceSetComponentAt(i, e, std::move(tempComponents[i])); + } + } catch (...) { + // If anything goes wrong, don't leave the array in a partially-updated state + THROW_EXCEPTION(InternalError, "Reordering failed, array may be in an inconsistent state"); } } @@ -809,7 +857,7 @@ namespace nexo::ecs { auto 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->getData(entity)...); + 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); @@ -876,16 +924,21 @@ namespace nexo::ecs { std::index_sequence) const { func(e, - (std::get(m_ownedArrays)->getData(e))..., - (std::get(m_nonOwnedArrays)->getData(e))...); + (std::get(m_ownedArrays)->get(e))..., + (std::get(m_nonOwnedArrays)->get(e))...); } + 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. }; diff --git a/engine/src/ecs/GroupSystem.hpp b/engine/src/ecs/GroupSystem.hpp index a9ac0b2b9..57eb3e9a6 100644 --- a/engine/src/ecs/GroupSystem.hpp +++ b/engine/src/ecs/GroupSystem.hpp @@ -172,8 +172,12 @@ namespace nexo::ecs { GroupSystem() { - if (!coord) - return; + 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"); + return; + } // Create the group m_group = createGroupImpl( @@ -194,6 +198,9 @@ namespace nexo::ecs { 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 @@ -231,23 +238,6 @@ namespace nexo::ecs { return OwnedTraitResult::found; } - /** - * @brief Get a component for an entity with appropriate access control - * - * @tparam AccessType The component access type (Read or Write) - * @param entity The entity to get the component for - * @return Component reference with appropriate const-ness - */ - template - auto getComponent(Entity entity) { - // Use GetComponentAccess to determine if T should be read-only - if constexpr (GetComponentAccess::accessType == AccessType::Read) { - return std::cref(m_group->template get(entity)); - } else { - return std::ref(m_group->template get(entity)); - } - } - /** * @brief Get all entities in this group * @@ -255,11 +245,14 @@ namespace nexo::ecs { */ auto getEntities() const { + if (!m_group) { + THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); + } return m_group->entities(); } protected: - std::shared_ptr m_group; + std::shared_ptr m_group = nullptr; private: /** diff --git a/engine/src/ecs/QuerySystem.hpp b/engine/src/ecs/QuerySystem.hpp index 0ce9f4a36..06f7caf3e 100644 --- a/engine/src/ecs/QuerySystem.hpp +++ b/engine/src/ecs/QuerySystem.hpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include "ECSExceptions.hpp" #include "System.hpp" #include "Access.hpp" #include "ComponentArray.hpp" @@ -61,8 +62,10 @@ namespace nexo::ecs { QuerySystem() { - if (!coord) - return; + if (!coord) { + THROW_EXCEPTION(InternalError, "Coordinator is null in QuerySystem constructor"); + return; + } // Set system signature based on required components (ignore singleton components) (setComponentSignatureIfRegular(m_signature), ...); @@ -86,12 +89,23 @@ namespace nexo::ecs { getComponent(Entity entity) { auto typeIndex = getTypeIndex(); - auto componentArray = std::static_pointer_cast>(m_componentArrays[typeIndex]); + 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"); if constexpr (hasReadAccess()) - return componentArray->getData(entity); + return componentArray->get(entity); else - return componentArray->getData(entity); + return componentArray->get(entity); } const Signature& getSignature() const { return m_signature; } @@ -103,14 +117,15 @@ namespace nexo::ecs { * * @tparam ComponentAccessType The component access type to cache */ - template - void cacheComponentArrayIfRegular() - { - if constexpr (!IsSingleton::value) { - using T = typename ComponentAccessType::ComponentType; - m_componentArrays[getTypeIndex()] = coord->template getComponentArray(); - } - } + template + void cacheComponentArrayIfRegular() + { + if constexpr (!IsSingleton::value) { + using T = typename ComponentAccessType::ComponentType; + auto componentArray = coord->template getComponentArray(); + m_componentArrays[getTypeIndex()] = componentArray; + } + } /** * @brief Sets the component bit in the system signature (only for regular components) diff --git a/engine/src/ecs/System.cpp b/engine/src/ecs/System.cpp index 81259bffb..e8b1462d0 100644 --- a/engine/src/ecs/System.cpp +++ b/engine/src/ecs/System.cpp @@ -20,8 +20,8 @@ namespace nexo::ecs { { if (contains(entity)) { - LOG(NEXO_WARN, "Entity {} already added to the sparse set", entity); - return; + LOG(NEXO_WARN, "Entity {} already added to the sparse set", entity); + return; } sparse[entity] = dense.size(); diff --git a/engine/src/ecs/System.hpp b/engine/src/ecs/System.hpp index 6fbcb0b37..142afff70 100644 --- a/engine/src/ecs/System.hpp +++ b/engine/src/ecs/System.hpp @@ -59,8 +59,8 @@ namespace nexo::ecs { */ class System { public: + virtual ~System() = default; static std::shared_ptr coord; - SparseSet entities; }; @@ -72,6 +72,9 @@ namespace nexo::ecs { public: virtual ~AQuerySystem() = default; virtual const Signature &getSignature() const = 0; + + SparseSet entities; + }; /** @@ -93,28 +96,6 @@ namespace nexo::ecs { */ 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 registerSystem() - { - std::type_index typeName(typeid(T)); - - if (m_systems.contains(typeName)) - { - LOG(NEXO_WARN, "ECS::SystemManager::registerSystem: trying to register a system more than once"); - return nullptr; - } - - auto system = std::make_shared(); - m_systems.insert({typeName, system}); - return system; - } - /** * @brief Registers a new system of type T in the ECS framework. * @@ -124,6 +105,7 @@ namespace nexo::ecs { template std::shared_ptr registerQuerySystem(Args&&... args) { + static_assert(std::is_base_of::value, "T must derive from AQuerySystem"); std::type_index typeName(typeid(T)); if (m_querySystems.contains(typeName)) @@ -140,6 +122,7 @@ namespace nexo::ecs { template std::shared_ptr registerGroupSystem(Args&&... args) { + static_assert(std::is_base_of::value, "T must derive from AGroupSystem"); std::type_index typeName(typeid(T)); if (m_groupSystems.contains(typeName)) @@ -187,7 +170,6 @@ namespace nexo::ecs { void entitySignatureChanged(Entity entity, Signature oldSignature, Signature newSignature); private: std::unordered_map m_signatures{}; - std::unordered_map> m_systems{}; std::unordered_map> m_querySystems{}; std::unordered_map> m_groupSystems{}; }; diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index b7c6e2bbf..5a778be9d 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -51,7 +51,7 @@ namespace nexo::system { for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { const auto &cameraComponent = cameraSpan[i]; - const auto &transformComponent = transformComponentArray->getData(entitySpan[i]); + const auto &transformComponent = transformComponentArray->get(entitySpan[i]); glm::mat4 projectionMatrix = cameraComponent.getProjectionMatrix(); glm::mat4 viewMatrix = cameraComponent.getViewMatrix(transformComponent); glm::mat4 viewProjectionMatrix = projectionMatrix * viewMatrix; From 76d111628532711ca6461a33a5c2d3302688a0b4 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 9 Apr 2025 21:09:45 +0900 Subject: [PATCH 052/450] tests(ecs-opti): add tests for the new features of the ecs --- tests/ecs/CMakeLists.txt | 4 + tests/ecs/ComponentArray.test.cpp | 390 +++++++ tests/ecs/Components.test.cpp | 1171 ++++++++++----------- tests/ecs/Coordinator.test.cpp | 286 +----- tests/ecs/Definitions.test.cpp | 89 ++ tests/ecs/Entity.test.cpp | 508 +++++----- tests/ecs/Exceptions.test.cpp | 274 +++-- tests/ecs/Group.test.cpp | 1354 +++++++++++-------------- tests/ecs/GroupSystem.test.cpp | 371 +++++++ tests/ecs/QuerySystem.test.cpp | 302 ++++++ tests/ecs/SingletonComponent.test.cpp | 447 ++++---- tests/ecs/System.test.cpp | 304 +++--- 12 files changed, 3141 insertions(+), 2359 deletions(-) create mode 100644 tests/ecs/ComponentArray.test.cpp create mode 100644 tests/ecs/Definitions.test.cpp create mode 100644 tests/ecs/GroupSystem.test.cpp create mode 100644 tests/ecs/QuerySystem.test.cpp diff --git a/tests/ecs/CMakeLists.txt b/tests/ecs/CMakeLists.txt index 4d2f230c4..5ccd078df 100644 --- a/tests/ecs/CMakeLists.txt +++ b/tests/ecs/CMakeLists.txt @@ -46,6 +46,10 @@ add_executable(ecs_tests ${BASEDIR}/SparseSet.test.cpp ${BASEDIR}/SingletonComponent.test.cpp ${BASEDIR}/Group.test.cpp + ${BASEDIR}/ComponentArray.test.cpp + ${BASEDIR}/Definitions.test.cpp + ${BASEDIR}/GroupSystem.test.cpp + ${BASEDIR}/QuerySystem.test.cpp ) # Find glm and add its include directories diff --git a/tests/ecs/ComponentArray.test.cpp b/tests/ecs/ComponentArray.test.cpp new file mode 100644 index 000000000..d3c4a6489 --- /dev/null +++ b/tests/ecs/ComponentArray.test.cpp @@ -0,0 +1,390 @@ +//// ComponentArray.test.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: 09/04/2025 +// Description: Test file for the component array class +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "ComponentArray.hpp" +#include "ECSExceptions.hpp" + +namespace nexo::ecs { + + struct TestComponent { + int value; + + TestComponent() = default; + TestComponent(int v) : value(v) {} + + bool operator==(const TestComponent& other) const { + return value == other.value; + } + }; + + class ComponentArrayTest : public ::testing::Test { + protected: + std::shared_ptr> componentArray = nullptr; + + void SetUp() override { + componentArray = std::make_shared>(); + for (Entity i = 0; i < 5; ++i) { + TestComponent newComp; + newComp.value = i * 10; + componentArray->insert(i, newComp); + } + } + }; + + // ========================================================= + // ================== BASIC OPERATIONS ===================== + // ========================================================= + + TEST_F(ComponentArrayTest, InsertAddsComponentCorrectly) { + const Entity testEntity = 10; + const TestComponent testComponent(100); + + componentArray->insert(testEntity, testComponent); + + EXPECT_TRUE(componentArray->hasComponent(testEntity)); + EXPECT_EQ(componentArray->get(testEntity), testComponent); + EXPECT_EQ(componentArray->size(), 6); // 5 from setup + 1 new + } + + TEST_F(ComponentArrayTest, InsertDuplicateEntityIsIgnored) { + const Entity testEntity = 1; // Already exists from setup + const TestComponent newComponent(999); + + // Original component should be preserved + TestComponent originalComponent = componentArray->get(testEntity); + + componentArray->insert(testEntity, newComponent); + + // Size should not change + EXPECT_EQ(componentArray->size(), 5); + // Component should not be updated + EXPECT_EQ(componentArray->get(testEntity), originalComponent); + } + + TEST_F(ComponentArrayTest, RemoveRemovesComponentCorrectly) { + const Entity testEntity = 2; + + EXPECT_TRUE(componentArray->hasComponent(testEntity)); + componentArray->remove(testEntity); + EXPECT_FALSE(componentArray->hasComponent(testEntity)); + EXPECT_EQ(componentArray->size(), 4); // 5 from setup - 1 removed + } + + TEST_F(ComponentArrayTest, GetReturnsCorrectComponent) { + for (Entity i = 0; i < 5; ++i) { + EXPECT_EQ(componentArray->get(i).value, i * 10); + } + } + + TEST_F(ComponentArrayTest, GetAllowsModification) { + const Entity testEntity = 3; + TestComponent& component = componentArray->get(testEntity); + component.value = 999; + + EXPECT_EQ(componentArray->get(testEntity).value, 999); + } + + TEST_F(ComponentArrayTest, EntityDestroyedRemovesComponent) { + const Entity testEntity = 4; + + EXPECT_TRUE(componentArray->hasComponent(testEntity)); + componentArray->entityDestroyed(testEntity); + EXPECT_FALSE(componentArray->hasComponent(testEntity)); + EXPECT_EQ(componentArray->size(), 4); + } + + TEST_F(ComponentArrayTest, EntityDestroyedIgnoresNonExistentEntity) { + const Entity nonExistentEntity = 100; + + EXPECT_FALSE(componentArray->hasComponent(nonExistentEntity)); + componentArray->entityDestroyed(nonExistentEntity); + EXPECT_EQ(componentArray->size(), 5); // Size unchanged + } + + TEST_F(ComponentArrayTest, GetEntityAtIndexReturnsCorrectEntity) { + for (size_t i = 0; i < 5; ++i) { + EXPECT_EQ(componentArray->getEntityAtIndex(i), i); + } + } + + TEST_F(ComponentArrayTest, GetAllComponentsReturnsCorrectSpan) { + auto span = componentArray->getAllComponents(); + EXPECT_EQ(span.size(), 5); + + for (size_t i = 0; i < 5; ++i) { + EXPECT_EQ(span[i].value, static_cast(i * 10)); + } + } + + TEST_F(ComponentArrayTest, EntitiesReturnsCorrectEntitySpan) { + auto entitiesSpan = componentArray->entities(); + EXPECT_EQ(entitiesSpan.size(), 5); + + for (size_t i = 0; i < 5; ++i) { + EXPECT_EQ(entitiesSpan[i], i); + } + } + + TEST_F(ComponentArrayTest, SizeReturnsCorrectCount) { + EXPECT_EQ(componentArray->size(), 5); + componentArray->remove(0); + EXPECT_EQ(componentArray->size(), 4); + TestComponent newComp; + newComp.value = 100; + componentArray->insert(10, newComp); + EXPECT_EQ(componentArray->size(), 5); + } + + // ========================================================= + // ================== GROUP OPERATIONS ===================== + // ========================================================= + + TEST_F(ComponentArrayTest, AddToGroupMovesEntityToGroupRegion) { + EXPECT_EQ(componentArray->groupSize(), 0); + + // Add entity 3 to group + componentArray->addToGroup(3); + EXPECT_EQ(componentArray->groupSize(), 1); + EXPECT_EQ(componentArray->getEntityAtIndex(0), 3); + + // Add entity 1 to group + componentArray->addToGroup(1); + EXPECT_EQ(componentArray->groupSize(), 2); + + // Verify both entities are in the group region (first 2 entries) + auto entities = componentArray->entities(); + EXPECT_TRUE(entities[0] == 3 || entities[1] == 3); + EXPECT_TRUE(entities[0] == 1 || entities[1] == 1); + } + + TEST_F(ComponentArrayTest, AddToGroupIgnoresAlreadyGroupedEntity) { + componentArray->addToGroup(2); + EXPECT_EQ(componentArray->groupSize(), 1); + + // Add the same entity again + componentArray->addToGroup(2); + EXPECT_EQ(componentArray->groupSize(), 1); // Size should not change + EXPECT_EQ(componentArray->getEntityAtIndex(0), 2); + } + + TEST_F(ComponentArrayTest, RemoveFromGroupMovesEntityOutOfGroupRegion) { + // First add some entities to the group + componentArray->addToGroup(1); + componentArray->addToGroup(3); + EXPECT_EQ(componentArray->groupSize(), 2); + + // Remove entity 1 from group + componentArray->removeFromGroup(1); + EXPECT_EQ(componentArray->groupSize(), 1); + EXPECT_EQ(componentArray->getEntityAtIndex(0), 3); + + // Remove entity 3 from group + componentArray->removeFromGroup(3); + EXPECT_EQ(componentArray->groupSize(), 0); + } + + TEST_F(ComponentArrayTest, RemoveFromGroupIgnoresNonGroupedEntity) { + componentArray->addToGroup(2); + EXPECT_EQ(componentArray->groupSize(), 1); + + // Try to remove an entity that's not in the group + componentArray->removeFromGroup(4); + EXPECT_EQ(componentArray->groupSize(), 1); // Size should not change + } + + TEST_F(ComponentArrayTest, RemoveHandlesGroupedEntityCorrectly) { + // Add some entities to group + componentArray->addToGroup(1); + componentArray->addToGroup(3); + EXPECT_EQ(componentArray->groupSize(), 2); + + // Remove a grouped entity + componentArray->remove(1); + + // Group size should decrease + EXPECT_EQ(componentArray->groupSize(), 1); + // Total size should decrease + EXPECT_EQ(componentArray->size(), 4); + // Entity 3 should still be in the group + EXPECT_EQ(componentArray->getEntityAtIndex(0), 3); + } + + // ========================================================= + // ================== ERROR HANDLING ======================= + // ========================================================= + + TEST_F(ComponentArrayTest, InsertThrowsOnEntityBeyondMaxEntities) { + const Entity invalidEntity = MAX_ENTITIES; + TestComponent testComp; + testComp.value = 1; + EXPECT_THROW(componentArray->insert(invalidEntity, testComp), OutOfRange); + } + + TEST_F(ComponentArrayTest, RemoveThrowsOnNonExistentComponent) { + const Entity nonExistentEntity = 100; + EXPECT_THROW(componentArray->remove(nonExistentEntity), ComponentNotFound); + } + + TEST_F(ComponentArrayTest, GetThrowsOnNonExistentComponent) { + const Entity nonExistentEntity = 100; + EXPECT_THROW(static_cast(componentArray->get(nonExistentEntity)), ComponentNotFound); + } + + TEST_F(ComponentArrayTest, GetEntityAtIndexThrowsOnInvalidIndex) { + const size_t invalidIndex = 100; + EXPECT_THROW(static_cast(componentArray->getEntityAtIndex(invalidIndex)), OutOfRange); + } + + TEST_F(ComponentArrayTest, AddToGroupThrowsOnNonExistentComponent) { + const Entity nonExistentEntity = 100; + EXPECT_THROW(componentArray->addToGroup(nonExistentEntity), ComponentNotFound); + } + + TEST_F(ComponentArrayTest, RemoveFromGroupThrowsOnNonExistentComponent) { + const Entity nonExistentEntity = 100; + EXPECT_THROW(componentArray->removeFromGroup(nonExistentEntity), ComponentNotFound); + } + + // ========================================================= + // ================== CAPACITY AND MEMORY ================== + // ========================================================= + + TEST_F(ComponentArrayTest, ArrayShrinksWhenManyElementsRemoved) { + // First, add more elements to increase capacity + for (Entity i = 5; i < 20; ++i) { + componentArray->insert(i, TestComponent(i * 10)); + } + size_t largeSize = componentArray->size(); + + // Now remove many elements to trigger shrink + for (Entity i = 0; i < 15; ++i) { + componentArray->remove(i); + } + + // Verify remaining elements are still accessible + for (Entity i = 15; i < 20; ++i) { + EXPECT_TRUE(componentArray->hasComponent(i)); + EXPECT_EQ(componentArray->get(i).value, i * 10); + } + } + + TEST_F(ComponentArrayTest, HandlesSparseEntityDistribution) { + // Insert components with large gaps between entity IDs + componentArray->insert(100, TestComponent(100)); + componentArray->insert(1000, TestComponent(1000)); + componentArray->insert(10000, TestComponent(10000)); + + // Verify components were stored correctly + EXPECT_TRUE(componentArray->hasComponent(100)); + EXPECT_TRUE(componentArray->hasComponent(1000)); + EXPECT_TRUE(componentArray->hasComponent(10000)); + + EXPECT_EQ(componentArray->get(100).value, 100); + EXPECT_EQ(componentArray->get(1000).value, 1000); + EXPECT_EQ(componentArray->get(10000).value, 10000); + + // Make sure non-existent entities in gaps return false + EXPECT_FALSE(componentArray->hasComponent(101)); + EXPECT_FALSE(componentArray->hasComponent(9999)); + } + + // ========================================================= + // ================== COMPLEX TESTS ======================== + // ========================================================= + + TEST_F(ComponentArrayTest, ComplexEntityLifecycle) { + // Initial state: 5 entities with components + EXPECT_EQ(componentArray->size(), 5); + + // Add some to group + componentArray->addToGroup(1); + componentArray->addToGroup(3); + EXPECT_EQ(componentArray->groupSize(), 2); + + // Remove a non-grouped entity + componentArray->remove(4); + EXPECT_EQ(componentArray->size(), 4); + EXPECT_EQ(componentArray->groupSize(), 2); + + // Remove a grouped entity + componentArray->remove(1); + EXPECT_EQ(componentArray->size(), 3); + EXPECT_EQ(componentArray->groupSize(), 1); + + // Add new entities + componentArray->insert(6, TestComponent(60)); + componentArray->insert(7, TestComponent(70)); + EXPECT_EQ(componentArray->size(), 5); + + // Destroy an entity via entityDestroyed + componentArray->entityDestroyed(0); + EXPECT_EQ(componentArray->size(), 4); + + // Add the new entities to group + componentArray->addToGroup(6); + componentArray->addToGroup(7); + EXPECT_EQ(componentArray->groupSize(), 3); + + // Verify the final state + EXPECT_TRUE(componentArray->hasComponent(2)); + EXPECT_TRUE(componentArray->hasComponent(3)); + EXPECT_TRUE(componentArray->hasComponent(6)); + EXPECT_TRUE(componentArray->hasComponent(7)); + + EXPECT_FALSE(componentArray->hasComponent(0)); + EXPECT_FALSE(componentArray->hasComponent(1)); + EXPECT_FALSE(componentArray->hasComponent(4)); + + // Check grouped entities through entity indexes + auto entitiesSpan = componentArray->entities(); + bool found3 = false, found6 = false, found7 = false; + + for (size_t i = 0; i < componentArray->groupSize(); ++i) { + Entity e = entitiesSpan[i]; + if (e == 3) found3 = true; + if (e == 6) found6 = true; + if (e == 7) found7 = true; + } + + EXPECT_TRUE(found3); + EXPECT_TRUE(found6); + EXPECT_TRUE(found7); + } + + TEST_F(ComponentArrayTest, ComplexForEachWithGroupOperations) { + // Add entities to group + componentArray->addToGroup(1); + componentArray->addToGroup(3); + EXPECT_EQ(componentArray->groupSize(), 2); + + // Modify only grouped components + int groupSum = 0; + componentArray->forEach([&](Entity e, TestComponent& comp) { + if (e == 1 || e == 3) { + comp.value *= 2; + groupSum += comp.value; + } + }); + + EXPECT_EQ(groupSum, 80); // (10*2) + (30*2) + EXPECT_EQ(componentArray->get(1).value, 20); + EXPECT_EQ(componentArray->get(3).value, 60); + + // Non-grouped components should be unchanged + EXPECT_EQ(componentArray->get(0).value, 0); + EXPECT_EQ(componentArray->get(2).value, 20); + EXPECT_EQ(componentArray->get(4).value, 40); + } +} diff --git a/tests/ecs/Components.test.cpp b/tests/ecs/Components.test.cpp index ee3f2c994..95824bee7 100644 --- a/tests/ecs/Components.test.cpp +++ b/tests/ecs/Components.test.cpp @@ -1,4 +1,4 @@ -//// Components.test.cpp ////////////////////////////////////////////////////// +//// Components.test.cpp /////////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -7,697 +7,560 @@ // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // // Author: Mehdy MORVAN -// Date: 26/11/2024 -// Description: Test file for the components ecs classes +// Date: 09/04/2025 +// Description: Test file for the component manager // /////////////////////////////////////////////////////////////////////////////// - #include -#include -#include "ecs/Components.hpp" -#include "Definitions.hpp" +#include "Components.hpp" +#include "ECSExceptions.hpp" +#include namespace nexo::ecs { - struct TestComponent { - int value; - }; - struct AnotherComponent { - float data; - }; + struct TestComponentA { + int value; + + TestComponentA(int v = 0) : value(v) {} + + bool operator==(const TestComponentA& other) const { + return value == other.value; + } + }; + + struct TestComponentB { + float x, y; + + TestComponentB(float _x = 0.0f, float _y = 0.0f) : x(_x), y(_y) {} + + bool operator==(const TestComponentB& other) const { + return x == other.x && y == other.y; + } + }; + + struct TestComponentC { + std::string name; + + TestComponentC(const std::string& n = "") : name(n) {} + + bool operator==(const TestComponentC& other) const { + return name == other.name; + } + }; + + struct TestComponentD { + bool flag; + + TestComponentD(bool f = false) : flag(f) {} + + bool operator==(const TestComponentD& other) const { + return flag == other.flag; + } + }; + + class ComponentManagerTest : public ::testing::Test { + protected: + ComponentManager componentManager; + + void SetUp() override { + componentManager.registerComponent(); + componentManager.registerComponent(); + componentManager.registerComponent(); + } + }; + + class GroupKeyTest : public ::testing::Test { + protected: + GroupKey createGroupKey() { + GroupKey key; + key.ownedSignature.set(0); + key.ownedSignature.set(2); + key.nonOwnedSignature.set(1); + key.nonOwnedSignature.set(3); + return key; + } + }; + + // ========================================================= + // ================== COMPONENT REGISTRATION =============== + // ========================================================= + + TEST_F(ComponentManagerTest, RegisterComponentCreatesComponentArray) { + // Register a new component type + componentManager.registerComponent(); + + // Check that the component array was created + auto componentArray = componentManager.getComponentArray(); + EXPECT_NE(componentArray, nullptr); + EXPECT_EQ(componentArray->size(), 0); + } + + TEST_F(ComponentManagerTest, GetComponentTypeReturnsConsistentTypeID) { + // Get component types for registered components + ComponentType typeA1 = componentManager.getComponentType(); + ComponentType typeA2 = componentManager.getComponentType(); + ComponentType typeB = componentManager.getComponentType(); + + // Same component type should return the same ID + EXPECT_EQ(typeA1, typeA2); + + // Different component types should return different IDs + EXPECT_NE(typeA1, typeB); + } + + TEST_F(ComponentManagerTest, GetComponentTypeThrowsForUnregisteredComponent) { + // Try to get type ID for an unregistered component + EXPECT_THROW({ + static_cast(componentManager.getComponentType()); + }, ComponentNotRegistered); + } + + TEST_F(ComponentManagerTest, GetComponentArrayThrowsForUnregisteredComponent) { + // Try to get component array for an unregistered component + EXPECT_THROW({ + static_cast(componentManager.getComponentArray()); + }, ComponentNotRegistered); + } + + // ========================================================= + // ============= COMPONENT ADDITION/REMOVAL ================ + // ========================================================= + + TEST_F(ComponentManagerTest, AddComponentAddsComponentToEntity) { + const Entity entity = 1; + const TestComponentA component(42); + Signature signature; + + // Add component to entity + componentManager.addComponent(entity, component, signature); + + // Check that the component was added + auto& retrievedComponent = componentManager.getComponent(entity); + EXPECT_EQ(retrievedComponent.value, component.value); + + // Check the size of the component array + auto componentArray = componentManager.getComponentArray(); + EXPECT_EQ(componentArray->size(), 1); + EXPECT_TRUE(componentArray->hasComponent(entity)); + } + + TEST_F(ComponentManagerTest, RemoveComponentRemovesComponentFromEntity) { + const Entity entity = 1; + const TestComponentA component(42); + Signature signature; + + // Add component to entity + componentManager.addComponent(entity, component, signature); + + // Verify component exists + EXPECT_TRUE(componentManager.getComponentArray()->hasComponent(entity)); + + // Remove component + componentManager.removeComponent(entity, signature); + + // Check that the component was removed + EXPECT_FALSE(componentManager.getComponentArray()->hasComponent(entity)); + EXPECT_EQ(componentManager.getComponentArray()->size(), 0); + } + + TEST_F(ComponentManagerTest, TryRemoveComponentReturnsTrueIfRemoved) { + const Entity entity = 1; + const TestComponentA component(42); + Signature signature; + + // Add component to entity + componentManager.addComponent(entity, component, signature); + + // Try to remove component + bool removed = componentManager.tryRemoveComponent(entity, signature); + + // Check that the component was removed and function returned true + EXPECT_TRUE(removed); + EXPECT_FALSE(componentManager.getComponentArray()->hasComponent(entity)); + } + + TEST_F(ComponentManagerTest, TryRemoveComponentReturnsFalseIfNotExist) { + const Entity entity = 1; + Signature signature; + + // Try to remove a component that doesn't exist + bool removed = componentManager.tryRemoveComponent(entity, signature); + + // Check that the function returned false + EXPECT_FALSE(removed); + } + + TEST_F(ComponentManagerTest, GetComponentReturnsCorrectComponent) { + const Entity entity = 1; + const TestComponentA component(42); + Signature signature; + + // Add component to entity + componentManager.addComponent(entity, component, signature); + + // Get component + auto& retrievedComponent = componentManager.getComponent(entity); + + // Check that the correct component was returned + EXPECT_EQ(retrievedComponent.value, component.value); + + // Modify the component and verify it was changed in the array + retrievedComponent.value = 100; + auto& verifyComponent = componentManager.getComponent(entity); + EXPECT_EQ(verifyComponent.value, 100); + } + + TEST_F(ComponentManagerTest, TryGetComponentReturnsComponentIfExists) { + const Entity entity = 1; + const TestComponentA component(42); + Signature signature; + + // Add component to entity + componentManager.addComponent(entity, component, signature); + + // Try to get component + auto optComponent = componentManager.tryGetComponent(entity); + + // Check that the component was returned + EXPECT_TRUE(optComponent.has_value()); + EXPECT_EQ(optComponent.value().get().value, component.value); + } + + TEST_F(ComponentManagerTest, TryGetComponentReturnsNulloptIfNotExists) { + const Entity entity = 1; + + // Try to get a component that doesn't exist + auto optComponent = componentManager.tryGetComponent(entity); + + // Check that nullopt was returned + EXPECT_FALSE(optComponent.has_value()); + } - class ComponentManagerTest : public ::testing::Test { - protected: - void SetUp() override - { - componentManager = std::make_unique(); - } + TEST_F(ComponentManagerTest, EntityDestroyedRemovesAllComponents) { + const Entity entity = 1; + Signature signature; - std::unique_ptr componentManager; - }; + // Add multiple components to the entity + signature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentA(42), signature); - TEST(ComponentArrayTest, InsertAndRetrieveData) - { - ComponentArray array; + signature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature); - Entity entity1 = 1; - Entity entity2 = 2; + // Verify the components exist + EXPECT_TRUE(componentManager.getComponentArray()->hasComponent(entity)); + EXPECT_TRUE(componentManager.getComponentArray()->hasComponent(entity)); - array.insertData(entity1, {42}); - array.insertData(entity2, {84}); + // Destroy the entity + componentManager.entityDestroyed(entity, signature); + + // Check that all components were removed + EXPECT_FALSE(componentManager.getComponentArray()->hasComponent(entity)); + EXPECT_FALSE(componentManager.getComponentArray()->hasComponent(entity)); + } + + // ========================================================= + // =================== GROUP KEYS ========================== + // ========================================================= + + TEST_F(GroupKeyTest, GroupKeyEquality) { + GroupKey key1 = createGroupKey(); + GroupKey key2 = createGroupKey(); + + // Same keys should be equal + EXPECT_EQ(key1, key2); + + // Different owned signature should make keys not equal + GroupKey key3 = createGroupKey(); + key3.ownedSignature.set(4); + EXPECT_NE(key1, key3); + + // Different non-owned signature should make keys not equal + GroupKey key4 = createGroupKey(); + key4.nonOwnedSignature.set(5); + EXPECT_NE(key1, key4); + } + + TEST_F(GroupKeyTest, GroupKeyHash) { + GroupKey key1 = createGroupKey(); + GroupKey key2 = createGroupKey(); + + // Same keys should have the same hash + std::hash hasher; + EXPECT_EQ(hasher(key1), hasher(key2)); + + // Different keys should have different hashes (not guaranteed, but likely) + GroupKey key3 = createGroupKey(); + key3.ownedSignature.set(4); + EXPECT_NE(hasher(key1), hasher(key3)); + } + + TEST_F(ComponentManagerTest, HasCommonOwnedComponents) { + GroupKey key1, key2, key3; + + // Set up signatures with overlapping components + key1.ownedSignature.set(0); + key1.ownedSignature.set(1); + + key2.ownedSignature.set(1); + key2.ownedSignature.set(2); + + key3.ownedSignature.set(3); + key3.ownedSignature.set(4); + + // Check for overlapping components + EXPECT_TRUE(componentManager.hasCommonOwnedComponents(key1, key2)); // Both have component 1 + EXPECT_FALSE(componentManager.hasCommonOwnedComponents(key1, key3)); // No common components + EXPECT_FALSE(componentManager.hasCommonOwnedComponents(key2, key3)); // No common components + } + + // ========================================================= + // ================ GROUP REGISTRATION ===================== + // ========================================================= + + TEST_F(ComponentManagerTest, RegisterGroupCreatesNewGroup) { + // Register a new group + auto group = componentManager.registerGroup(get()); + + // Check that the group was created + EXPECT_NE(group, nullptr); + + // Verify group properties + EXPECT_EQ(group->size(), 0); + + // Verify group signature + auto allSignature = group->allSignature(); + EXPECT_TRUE(allSignature.test(getComponentTypeID())); + EXPECT_TRUE(allSignature.test(getComponentTypeID())); + EXPECT_TRUE(allSignature.test(getComponentTypeID())); + } + + TEST_F(ComponentManagerTest, RegisterGroupReturnsSameGroupWhenCalledTwice) { + // Register group twice + auto group1 = componentManager.registerGroup(get()); + auto group2 = componentManager.registerGroup(get()); + + // Should return the same group object + EXPECT_EQ(group1, group2); + } + + TEST_F(ComponentManagerTest, RegisterGroupThrowsOnOverlappingOwnedComponents) { + // Register first group + componentManager.registerGroup(get()); + + // The EXPECT_THROW macro seems to dislike templating for some reason + bool exceptionThrown = false; + try { + componentManager.registerGroup(get()); + } catch (const OverlappingGroupsException& e) { + exceptionThrown = true; + } + EXPECT_TRUE(exceptionThrown); + } + + TEST_F(ComponentManagerTest, RegisterGroupThrowsOnUnregisteredComponents) { + bool exceptionThrown = false; + try { + componentManager.registerGroup(get()); + } catch (const ComponentNotRegistered& e) { + exceptionThrown = true; + } + EXPECT_TRUE(exceptionThrown); + } + + TEST_F(ComponentManagerTest, GetGroupReturnsRegisteredGroup) { + // Register a group + auto registeredGroup = componentManager.registerGroup(get()); + + // Get the group + auto retrievedGroup = componentManager.getGroup(get()); + + // Should return the same group object + EXPECT_EQ(registeredGroup, retrievedGroup); + } + + TEST_F(ComponentManagerTest, GetGroupThrowsOnNonexistentGroup) { + bool exceptionThrown = false; + try { + componentManager.getGroup(get()); + } catch (const GroupNotFound& e) { + exceptionThrown = true; + } + EXPECT_TRUE(exceptionThrown); + } + + TEST_F(ComponentManagerTest, GroupAddsEntitiesOnComponentsAdded) { + auto group = componentManager.registerGroup(get()); + + // Create required entity components + const Entity entity = 1; + Signature signature; + + signature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentA(42), signature); + + signature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature); + + // Entity should be automatically added to the group + EXPECT_EQ(group->size(), 1); + + // Verify the entity is in the group + auto entitySpan = group->entities(); + EXPECT_EQ(entitySpan.size(), 1); + EXPECT_EQ(entitySpan[0], entity); + } + + TEST_F(ComponentManagerTest, GroupRemovesEntitiesOnComponentsRemoved) { + // Create entity with required components first + const Entity entity = 1; + Signature signature; + signature.set(getComponentTypeID()); + + componentManager.addComponent(entity, TestComponentA(42), signature); + + signature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature); + + // Register group + auto group = componentManager.registerGroup(get()); + + // Verify entity is in the group + EXPECT_EQ(group->size(), 1); + + // Remove a required component + componentManager.removeComponent(entity, signature); + + // Entity should be automatically removed from the group + EXPECT_EQ(group->size(), 0); + } + + TEST_F(ComponentManagerTest, GroupHandlesMultipleEntities) { + // Register group + auto group = componentManager.registerGroup(get()); + + for (Entity e = 1; e <= 5; ++e) { + Signature signature; + signature.set(getComponentTypeID()); + componentManager.addComponent(e, TestComponentA(e * 10), signature); + + signature.set(getComponentTypeID()); + componentManager.addComponent(e, TestComponentB(e * 1.0f, e * 2.0f), signature); + } + + // All entities should be in the group + EXPECT_EQ(group->size(), 5); + Signature signature; + signature.set(getComponentTypeID()); + signature.set(getComponentTypeID()); - EXPECT_EQ(array.getData(entity1).value, 42); - EXPECT_EQ(array.getData(entity2).value, 84); - } + // Remove some entities + for (Entity e = 1; e <= 5; e += 2) { + componentManager.removeComponent(e, signature); + } - TEST(ComponentArrayTest, RemoveData) - { - ComponentArray array; + // Only remaining entities should be in the group + EXPECT_EQ(group->size(), 2); - Entity entity = 1; + // Verify the correct entities remain + auto entitySpan = group->entities(); + std::set expectedEntities = {2, 4}; + std::set actualEntities(entitySpan.begin(), entitySpan.end()); + EXPECT_EQ(actualEntities, expectedEntities); + } - array.insertData(entity, {42}); - EXPECT_NO_THROW(array.removeData(entity)); + TEST_F(ComponentManagerTest, EntityDestroyedRemovesFromGroups) { + // Create entity with required components + const Entity entity = 1; + Signature signature; - EXPECT_THROW(static_cast(array.getData(entity)), ComponentNotFound); - } + componentManager.addComponent(entity, TestComponentA(42), signature); + signature.set(getComponentTypeID()); - TEST(ComponentArrayTest, HandleEntityDestruction) - { - ComponentArray array; + componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature); + signature.set(getComponentTypeID()); - Entity entity = 1; + // Register group + auto group = componentManager.registerGroup(get()); - array.insertData(entity, {42}); - array.entityDestroyed(entity); + // Verify entity is in the group + EXPECT_EQ(group->size(), 1); - EXPECT_THROW(static_cast(array.getData(entity)), ComponentNotFound); - } + // Destroy the entity + componentManager.entityDestroyed(entity, signature); - TEST(ComponentArrayTest, InsertDuplicateEntity) - { - ComponentArray array; + // Entity should be removed from the group + EXPECT_EQ(group->size(), 0); + } - Entity entity = 1; + // ========================================================= + // ================ INTEGRATION TEST ====================== + // ========================================================= - array.insertData(entity, {42}); - EXPECT_NO_THROW(array.insertData(entity, {100})); + TEST_F(ComponentManagerTest, ComplexIntegrationTest) { + // Register additional component + componentManager.registerComponent(); - EXPECT_EQ(array.getData(entity).value, 42); // Original value should remain - } + // Register multiple groups + auto groupAB = componentManager.registerGroup(get()); + auto groupCD = componentManager.registerGroup(get()); - TEST(ComponentArrayTest, InitialStateEmpty) - { - ComponentArray array; - EXPECT_EQ(array.size(), 0); - EXPECT_FALSE(array.hasComponent(0)); - } + // Create entities with various component combinations + Signature signature1, signature2, signature3; - TEST(ComponentArrayTest, SizeTracking) - { - ComponentArray array; + // Entity 1: A + B + C + signature1.set(getComponentTypeID()); + componentManager.addComponent(1, TestComponentA(10), signature1); - array.insertData(1, {42}); - EXPECT_EQ(array.size(), 1); + signature1.set(getComponentTypeID()); + componentManager.addComponent(1, TestComponentB(1.0f, 2.0f), signature1); - array.insertData(2, {84}); - EXPECT_EQ(array.size(), 2); + signature1.set(getComponentTypeID()); + componentManager.addComponent(1, TestComponentC("Entity1"), signature1); - array.removeData(1); - EXPECT_EQ(array.size(), 1); - } + // Entity 2: A + B + D + signature2.set(getComponentTypeID()); + componentManager.addComponent(2, TestComponentA(20), signature2); - TEST(ComponentArrayTest, GetEntityAtIndex) - { - ComponentArray array; + signature2.set(getComponentTypeID()); + componentManager.addComponent(2, TestComponentB(3.0f, 4.0f), signature2); - array.insertData(5, {42}); - array.insertData(10, {84}); + signature2.set(getComponentTypeID()); + componentManager.addComponent(2, TestComponentD(true), signature2); - EXPECT_EQ(array.getEntityAtIndex(0), 5); - EXPECT_EQ(array.getEntityAtIndex(1), 10); - EXPECT_THROW(static_cast(array.getEntityAtIndex(2)), OutOfRange); - } + // Entity 3: C + D + signature3.set(getComponentTypeID()); + componentManager.addComponent(3, TestComponentC("Entity3"), signature3); - TEST(ComponentArrayTest, RawDataAccess) - { - ComponentArray array; + signature3.set(getComponentTypeID()); + componentManager.addComponent(3, TestComponentD(false), signature3); - array.insertData(1, {42}); - array.insertData(2, {84}); + // Verify group membership + EXPECT_EQ(groupAB->size(), 2); // Entities 1 and 2 + EXPECT_EQ(groupCD->size(), 1); // Entity 3 - auto data = array.rawData(); - EXPECT_EQ(data.size(), 2); - EXPECT_EQ(data[0].value, 42); - EXPECT_EQ(data[1].value, 84); + // Remove components to change group membership + componentManager.removeComponent(1, signature1); + signature1.reset(getComponentTypeID()); - // Test const version - const auto& constArray = array; - auto constData = constArray.rawData(); - EXPECT_EQ(constData.size(), 2); - EXPECT_EQ(constData[0].value, 42); - EXPECT_EQ(constData[1].value, 84); - } + signature1.set(getComponentTypeID()); + componentManager.addComponent(1, TestComponentD(true), signature1); - TEST(ComponentArrayTest, EntitiesAccess) - { - ComponentArray array; + // Check updated group membership + EXPECT_EQ(groupAB->size(), 1); // Only Entity 2 now + EXPECT_EQ(groupCD->size(), 2); // Entities 1 and 3 now - array.insertData(5, {42}); - array.insertData(10, {84}); + // Destroy an entity + componentManager.entityDestroyed(2, signature2); - auto entities = array.entities(); - EXPECT_EQ(entities.size(), 2); - EXPECT_EQ(entities[0], 5); - EXPECT_EQ(entities[1], 10); - } + // Verify final state + EXPECT_EQ(groupAB->size(), 0); + EXPECT_EQ(groupCD->size(), 2); - TEST(ComponentArrayTest, SwapAndPopRemovalMechanism) - { - ComponentArray array; - - // Insert three components - array.insertData(1, {1}); - array.insertData(2, {2}); - array.insertData(3, {3}); - - // Remove the middle one - array.removeData(2); - - // Check if the entities are correctly mapped - EXPECT_EQ(array.size(), 2); - EXPECT_TRUE(array.hasComponent(1)); - EXPECT_FALSE(array.hasComponent(2)); - EXPECT_TRUE(array.hasComponent(3)); - - // The last entity (3) should now be at index 1 - EXPECT_EQ(array.getEntityAtIndex(1), 3); - - // Data should be preserved correctly - EXPECT_EQ(array.getData(1).value, 1); - EXPECT_EQ(array.getData(3).value, 3); - } - - TEST(ComponentArrayTest, AutomaticGrowth) - { - // Create array with small initial capacity - ComponentArray array; - - // Insert component for an entity beyond initial capacity - array.insertData(10, {42}); - - EXPECT_TRUE(array.hasComponent(10)); - EXPECT_EQ(array.getData(10).value, 42); - } - - TEST(ComponentArrayTest, ConstGetData) - { - ComponentArray array; - array.insertData(1, {42}); - - const ComponentArray& constArray = array; - EXPECT_EQ(constArray.getData(1).value, 42); - EXPECT_THROW(static_cast(constArray.getData(2)), ComponentNotFound); - } - - TEST(ComponentArrayTest, LargeEntityIDs) - { - ComponentArray array; - - Entity largeEntity = MAX_ENTITIES - 1; - array.insertData(largeEntity, {42}); - - EXPECT_TRUE(array.hasComponent(largeEntity)); - EXPECT_EQ(array.getData(largeEntity).value, 42); - } - - TEST(ComponentArrayTest, MultipleInsertionsAndRemovals) - { - ComponentArray array; - - // Insert several components - for (Entity e = 0; e < 100; ++e) { - array.insertData(e, {static_cast(e)}); - } - - EXPECT_EQ(array.size(), 100); - - // Remove some components - for (Entity e = 0; e < 100; e += 2) { - array.removeData(e); - } - - EXPECT_EQ(array.size(), 50); - - // Check remaining components - for (Entity e = 1; e < 100; e += 2) { - EXPECT_TRUE(array.hasComponent(e)); - EXPECT_EQ(array.getData(e).value, e); - } - } - - TEST(ComponentArrayTest, ShrinkingBehavior) - { - ComponentArray array; - - // Insert many components to force capacity increase - for (Entity e = 0; e < 100; ++e) { - array.insertData(e, {static_cast(e)}); - } - - // Remove most components to trigger shrinking - for (Entity e = 20; e < 100; ++e) { - array.removeData(e); - } - - // Check remaining components are still accessible - for (Entity e = 0; e < 20; ++e) { - EXPECT_TRUE(array.hasComponent(e)); - EXPECT_EQ(array.getData(e).value, e); - } - } - - TEST(ComponentArrayTest, ComplexComponentType) - { - struct ComplexComponent { - std::string name; - std::vector data; - bool flag; - - bool operator==(const ComplexComponent& other) const { - return name == other.name && data == other.data && flag == other.flag; - } - }; - - ComponentArray array; - - ComplexComponent comp1{"test", {1, 2, 3}, true}; - ComplexComponent comp2{"another", {4, 5}, false}; - - array.insertData(1, comp1); - array.insertData(2, comp2); - - EXPECT_EQ(array.getData(1), comp1); - EXPECT_EQ(array.getData(2), comp2); - } - - TEST(ComponentArrayTest, InsertBeyondMaxEntities) - { - ComponentArray array; - EXPECT_THROW(array.insertData(MAX_ENTITIES, {42}), OutOfRange); - } - - TEST(ComponentArrayTest, AccessNonExistentComponent) - { - ComponentArray array; - EXPECT_THROW(static_cast(array.getData(1)), ComponentNotFound); - } - - TEST(ComponentArrayTest, RemoveNonExistentComponent) - { - ComponentArray array; - EXPECT_THROW(array.removeData(1), ComponentNotFound); - } - - TEST(ComponentArrayTest, EntityDestroyedWithComponent) - { - ComponentArray array; - - array.insertData(1, {42}); - EXPECT_TRUE(array.hasComponent(1)); - - array.entityDestroyed(1); - EXPECT_FALSE(array.hasComponent(1)); - EXPECT_EQ(array.size(), 0); - } - - TEST(ComponentArrayTest, EntityDestroyedWithoutComponent) - { - ComponentArray array; - - array.insertData(2, {42}); - EXPECT_FALSE(array.hasComponent(1)); - - // Should not throw or affect other components - EXPECT_NO_THROW(array.entityDestroyed(1)); - EXPECT_TRUE(array.hasComponent(2)); - EXPECT_EQ(array.size(), 1); - } - - TEST(ComponentArrayTest, VerySmallCapacity) - { - ComponentArray array; - - // Insert beyond initial capacity - array.insertData(0, {0}); - array.insertData(1, {1}); - array.insertData(2, {2}); - - EXPECT_EQ(array.size(), 3); - EXPECT_TRUE(array.hasComponent(0)); - EXPECT_TRUE(array.hasComponent(1)); - EXPECT_TRUE(array.hasComponent(2)); - } - - TEST(ComponentArrayTest, ConcurrentEntityRemovalAndAccess) - { - ComponentArray array; - - for (Entity e = 0; e < 10; ++e) { - array.insertData(e, {static_cast(e)}); - } - - // Remove entities and check that others remain accessible - for (Entity e = 0; e < 10; e += 2) { - array.removeData(e); - - // Check remaining entities are still correctly accessible - for (Entity remaining = 1; remaining < 10; remaining += 2) { - EXPECT_TRUE(array.hasComponent(remaining)); - EXPECT_EQ(array.getData(remaining).value, remaining); - } - } - } - - TEST(ComponentArrayTest, ReinsertAfterRemoval) - { - ComponentArray array; - - array.insertData(1, {42}); - array.removeData(1); - EXPECT_FALSE(array.hasComponent(1)); - - // Reinsert with new value - array.insertData(1, {100}); - EXPECT_TRUE(array.hasComponent(1)); - EXPECT_EQ(array.getData(1).value, 100); - } - - TEST(ComponentArrayTest, MoveSemanticRespect) - { - // Instead of using a true move-only type, use a class that tracks moves - struct MoveTrackingComponent { - std::shared_ptr ptr; - int moves = 0; - - MoveTrackingComponent(int val) : ptr(std::make_shared(val)) {} - - MoveTrackingComponent(const MoveTrackingComponent& other) : ptr(other.ptr), moves(other.moves) {} - - MoveTrackingComponent(MoveTrackingComponent&& other) noexcept - : ptr(std::move(other.ptr)), moves(other.moves + 1) {} - - MoveTrackingComponent& operator=(const MoveTrackingComponent& other) { - ptr = other.ptr; - moves = other.moves; - return *this; - } - - MoveTrackingComponent& operator=(MoveTrackingComponent&& other) noexcept { - ptr = std::move(other.ptr); - moves = other.moves + 1; - return *this; - } - }; - - ComponentArray array; - - array.insertData(1, MoveTrackingComponent(42)); - EXPECT_EQ(*array.getData(1).ptr, 42); - - // Don't remove enough components to trigger shrinking - // Just verify the basic functionality works - } - - TEST(ComponentArrayTest, SmallOperationsWithoutShrinking) - { - // Using a component with expensive move operations - struct ExpensiveComponent { - std::vector data; - - ExpensiveComponent(std::initializer_list values) : data(values) {} - }; - - ComponentArray array; - - // Add just a few components - array.insertData(1, {1, 2, 3}); - array.insertData(2, {4, 5, 6}); - array.insertData(3, {7, 8, 9}); - - // Verify data access - EXPECT_EQ(array.getData(1).data, std::vector({1, 2, 3})); - EXPECT_EQ(array.getData(2).data, std::vector({4, 5, 6})); - EXPECT_EQ(array.getData(3).data, std::vector({7, 8, 9})); - - // Remove one (not enough to trigger shrinking) - array.removeData(2); - - // Verify remaining data - EXPECT_EQ(array.getData(1).data, std::vector({1, 2, 3})); - EXPECT_EQ(array.getData(3).data, std::vector({7, 8, 9})); - } - - TEST(ComponentArrayTest, EdgeCaseEmptyRemoval) - { - ComponentArray array; - - array.insertData(1, {42}); - array.removeData(1); - - // Array should be empty now - EXPECT_EQ(array.size(), 0); - EXPECT_FALSE(array.hasComponent(1)); - - // Removing again should throw - EXPECT_THROW(array.removeData(1), ComponentNotFound); - } - - TEST(ComponentArrayTest, HandleOverflow) - { - ComponentArray array; - - for (Entity entity = 0; entity < MAX_ENTITIES; ++entity) - { - EXPECT_NO_THROW(array.insertData(entity, {static_cast(entity)})); - } - - EXPECT_THROW(array.insertData(MAX_ENTITIES, {999}), OutOfRange); - } - - TEST_F(ComponentManagerTest, RegisterAndRetrieveComponentType) - { - componentManager->registerComponent(); - ComponentType type = componentManager->getComponentType(); - - EXPECT_EQ(type, 0); // First registered component type - } - - TEST_F(ComponentManagerTest, AddAndRetrieveComponent) - { - componentManager->registerComponent(); - Entity entity = 1; - Signature signature; - - signature.set(componentManager->getComponentType(), true); - - componentManager->addComponent(entity, TestComponent{42}, signature); - TestComponent &retrieved = componentManager->getComponent(entity); - - EXPECT_EQ(retrieved.value, 42); - } - - TEST_F(ComponentManagerTest, RemoveComponent) - { - componentManager->registerComponent(); - Entity entity = 1; - Signature signature; - - signature.set(componentManager->getComponentType(), true); - - componentManager->addComponent(entity, TestComponent{42}, signature); - EXPECT_NO_THROW(componentManager->removeComponent(entity, signature)); - EXPECT_THROW(static_cast(componentManager->getComponent(entity)), ComponentNotFound); - } - - TEST_F(ComponentManagerTest, TryRemoveComponent) - { - componentManager->registerComponent(); - Entity entity = 1; - Signature signature; - - EXPECT_FALSE(componentManager->tryRemoveComponent(entity, signature)); // No component yet - - signature.set(componentManager->getComponentType(), true); - componentManager->addComponent(entity, TestComponent{42}, signature); - EXPECT_TRUE(componentManager->tryRemoveComponent(entity, signature)); - signature.set(componentManager->getComponentType(), false); - EXPECT_FALSE(componentManager->tryRemoveComponent(entity, signature)); - } - - TEST_F(ComponentManagerTest, EntityDestroyedCleansUpComponents) - { - componentManager->registerComponent(); - Entity entity1 = 1; - Entity entity2 = 2; - Signature signature1; - Signature signature2; - - signature1.set(componentManager->getComponentType(), true); - signature2.set(componentManager->getComponentType(), true); - - componentManager->addComponent(entity1, TestComponent{42}, signature1); - componentManager->addComponent(entity2, TestComponent{84}, signature2); - - componentManager->entityDestroyed(entity1); - - EXPECT_THROW(static_cast(componentManager->getComponent(entity1)), ComponentNotFound); - EXPECT_EQ(componentManager->getComponent(entity2).value, 84); - } - - TEST_F(ComponentManagerTest, RetrieveUnregisteredComponentType) - { - EXPECT_THROW(static_cast(componentManager->getComponentType()), ComponentNotRegistered); - } - - TEST_F(ComponentManagerTest, AddComponentWithoutRegistering) - { - Entity entity = 1; - EXPECT_THROW(componentManager->addComponent(entity, TestComponent{42}, Signature{}), ComponentNotRegistered); - } - - TEST_F(ComponentManagerTest, TryGetComponent) - { - componentManager->registerComponent(); - Entity entity = 1; - - auto result = componentManager->tryGetComponent(entity); - EXPECT_FALSE(result.has_value()); - - Signature signature; - signature.set(componentManager->getComponentType(), true); - componentManager->addComponent(entity, TestComponent{42}, signature); - result = componentManager->tryGetComponent(entity); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(result->get().value, 42); - } - - TEST_F(ComponentManagerTest, GetComponentArray) - { - componentManager->registerComponent(); - - // Get component array - auto array = componentManager->getComponentArray(); - EXPECT_NE(array, nullptr); - - // Get const component array - const auto& constManager = *componentManager; - auto constArray = constManager.getComponentArray(); - EXPECT_NE(constArray, nullptr); - } - - TEST_F(ComponentManagerTest, GetComponentArrayUnregistered) - { - // Get component array for unregistered component - EXPECT_THROW(static_cast(componentManager->getComponentArray()), ComponentNotRegistered); - - // Get const component array for unregistered component - const auto& constManager = *componentManager; - EXPECT_THROW(static_cast(constManager.getComponentArray()), ComponentNotRegistered); - } - - TEST_F(ComponentManagerTest, MoveConstructor) - { - // Setup original manager - componentManager->registerComponent(); - Entity entity = 1; - Signature signature; - signature.set(componentManager->getComponentType(), true); - componentManager->addComponent(entity, TestComponent{42}, signature); - - // Move construct new manager - ComponentManager movedManager(std::move(*componentManager)); - - // Should be able to access component through moved manager - EXPECT_EQ(movedManager.getComponent(entity).value, 42); - } - - TEST_F(ComponentManagerTest, MoveAssignment) - { - // Setup original manager - componentManager->registerComponent(); - Entity entity = 1; - Signature signature; - signature.set(componentManager->getComponentType(), true); - componentManager->addComponent(entity, TestComponent{42}, signature); - - // Create second manager and move-assign - ComponentManager secondManager; - secondManager = std::move(*componentManager); - - // Should be able to access component through second manager - EXPECT_EQ(secondManager.getComponent(entity).value, 42); - } - - TEST_F(ComponentManagerTest, ComponentTypeIDs) - { - componentManager->registerComponent(); - componentManager->registerComponent(); - - // Different component types should have different IDs - ComponentType testType = componentManager->getComponentType(); - ComponentType anotherType = componentManager->getComponentType(); - - EXPECT_NE(testType, anotherType); - } - - TEST_F(ComponentManagerTest, EntityDestroyedNoComponents) - { - // EntityDestroyed with no components registered should not crash - EXPECT_NO_THROW(componentManager->entityDestroyed(1)); - } - - TEST_F(ComponentManagerTest, EntityDestroyedEmptyComponentArray) - { - componentManager->registerComponent(); - - // EntityDestroyed with registered but empty component array should not crash - EXPECT_NO_THROW(componentManager->entityDestroyed(1)); - } - - TEST_F(ComponentManagerTest, ComplexComponentStorage) - { - struct ComplexComponent { - std::string name; - std::vector data; - }; - - componentManager->registerComponent(); - Entity entity = 1; - - ComplexComponent complex{"test", {1, 2, 3}}; - Signature signature; - signature.set(componentManager->getComponentType(), true); - componentManager->addComponent(entity, complex, signature); - - ComplexComponent& retrieved = componentManager->getComponent(entity); - EXPECT_EQ(retrieved.name, "test"); - EXPECT_EQ(retrieved.data, std::vector({1, 2, 3})); - } - - TEST_F(ComponentManagerTest, GetComponentArrayDirect) - { - componentManager->registerComponent(); - Entity entity = 1; - - Signature signature; - signature.set(componentManager->getComponentType(), true); - componentManager->addComponent(entity, TestComponent{42}, signature); - - // Get component array directly and use it - auto array = componentManager->getComponentArray(); - TestComponent& component = array->getData(entity); - EXPECT_EQ(component.value, 42); - } - - TEST_F(ComponentManagerTest, ComponentModification) - { - componentManager->registerComponent(); - Entity entity = 1; - Signature signature; - signature.set(componentManager->getComponentType(), true); - componentManager->addComponent(entity, TestComponent{42}, signature); - - // Modify component directly - componentManager->getComponent(entity).value = 100; - - // Verify modification took effect - EXPECT_EQ(componentManager->getComponent(entity).value, 100); - } - - TEST_F(ComponentManagerTest, RegisterDuplicateComponent) - { - componentManager->registerComponent(); - EXPECT_NO_THROW(componentManager->registerComponent()); - } + // Check component arrays + EXPECT_EQ(componentManager.getComponentArray()->size(), 1); + EXPECT_EQ(componentManager.getComponentArray()->size(), 0); + EXPECT_EQ(componentManager.getComponentArray()->size(), 2); + EXPECT_EQ(componentManager.getComponentArray()->size(), 2); + } } diff --git a/tests/ecs/Coordinator.test.cpp b/tests/ecs/Coordinator.test.cpp index 61e998e8b..ed8a0905f 100644 --- a/tests/ecs/Coordinator.test.cpp +++ b/tests/ecs/Coordinator.test.cpp @@ -37,12 +37,12 @@ namespace nexo::ecs { struct TestSingletonComponent { int value; - }; - // Mock System for testing - class MockSystem : public System { - public: - MOCK_METHOD(void, update, (), (const)); + TestSingletonComponent() = default; + TestSingletonComponent(int v) : value(v) {} + + TestSingletonComponent(const TestSingletonComponent&) = delete; + TestSingletonComponent& operator=(const TestSingletonComponent&) = delete; }; class CoordinatorTest : public ::testing::Test { @@ -69,7 +69,7 @@ namespace nexo::ecs { TEST_F(CoordinatorTest, DestroyNonexistentEntity) { Entity nonexistentEntity = 99999; - EXPECT_THROW(coordinator->destroyEntity(nonexistentEntity), OutOfRange); + EXPECT_NO_THROW(coordinator->destroyEntity(nonexistentEntity)); } TEST_F(CoordinatorTest, RegisterAndAddComponent) { @@ -100,15 +100,6 @@ namespace nexo::ecs { EXPECT_NO_THROW(coordinator->tryRemoveComponent(entity)); } - TEST_F(CoordinatorTest, AddComponentToNonexistentEntity) { - coordinator->registerComponent(); - - Entity nonexistentEntity = 99999; - TestComponent component{42}; - - EXPECT_THROW(coordinator->addComponent(nonexistentEntity, component), OutOfRange); - } - TEST_F(CoordinatorTest, RemoveComponentFromNonexistentEntity) { coordinator->registerComponent(); @@ -117,56 +108,6 @@ namespace nexo::ecs { EXPECT_THROW(coordinator->removeComponent(nonexistentEntity), ComponentNotFound); } - TEST_F(CoordinatorTest, RegisterSystemAndSetSignature) { - auto system = coordinator->registerSystem(); - - EXPECT_NE(system, nullptr); - - Signature signature; - signature.set(1); - - EXPECT_NO_THROW(coordinator->setSystemSignature(signature)); - } - - TEST_F(CoordinatorTest, UpdateSystemEntities) { - auto system = coordinator->registerSystem(); - coordinator->registerComponent(); - - Signature signature; - signature.set(coordinator->getComponentType()); - - coordinator->setSystemSignature(signature); - - Entity entity = coordinator->createEntity(); - TestComponent component{42}; - - coordinator->registerComponent(); - coordinator->addComponent(entity, component); - - EXPECT_TRUE(system->entities.contains(entity)); - } - - TEST_F(CoordinatorTest, SystemDoesNotIncludeMismatchedEntity) { - auto system = coordinator->registerSystem(); - - Signature signature; - signature.set(1); - - coordinator->setSystemSignature(signature); - - Entity entity = coordinator->createEntity(); - TestComponent component{42}; - - coordinator->registerComponent(); - // Entity signature does not match the system - Signature entitySignature; - entitySignature.set(2); - coordinator->setSystemSignature(entitySignature); - - coordinator->addComponent(entity, component); - EXPECT_FALSE(system->entities.contains(entity)); - } - TEST_F(CoordinatorTest, GetAllComponents) { coordinator->registerComponent(); @@ -187,7 +128,7 @@ namespace nexo::ecs { ComponentA compA{10}; coordinator->addComponent(e1, compA); - std::set result = coordinator->getAllEntitiesWith(); + std::vector result = coordinator->getAllEntitiesWith(); EXPECT_TRUE(result.empty()); } @@ -199,9 +140,9 @@ namespace nexo::ecs { coordinator->addComponent(e2, ComponentA{20}); coordinator->addComponent(e2, ComponentB{3.14f}); - std::set result = coordinator->getAllEntitiesWith(); + std::vector result = coordinator->getAllEntitiesWith(); EXPECT_EQ(result.size(), 1); - EXPECT_TRUE(result.find(e2) != result.end()); + EXPECT_TRUE(std::find(result.begin(), result.end(), e2) != result.end()); } TEST_F(CoordinatorTest, GetAllEntitiesWith_MultipleMatches) { @@ -217,28 +158,25 @@ namespace nexo::ecs { coordinator->addComponent(e3, ComponentA{3}); coordinator->addComponent(e3, ComponentB{3.0f}); - std::set result = coordinator->getAllEntitiesWith(); + std::vector result = coordinator->getAllEntitiesWith(); EXPECT_EQ(result.size(), 3); - EXPECT_TRUE(result.find(e1) != result.end()); - EXPECT_TRUE(result.find(e2) != result.end()); - EXPECT_TRUE(result.find(e3) != result.end()); + EXPECT_TRUE(std::find(result.begin(), result.end(), e1) != result.end()); + EXPECT_TRUE(std::find(result.begin(), result.end(), e2) != result.end()); + EXPECT_TRUE(std::find(result.begin(), result.end(), e3) != result.end()); } TEST_F(CoordinatorTest, DestroyedEntityNotReturned) { - // Create an entity with both components. Entity e1 = coordinator->createEntity(); coordinator->addComponent(e1, ComponentA{10}); coordinator->addComponent(e1, ComponentB{2.5f}); - // Verify it is returned. - std::set result = coordinator->getAllEntitiesWith(); - EXPECT_TRUE(result.find(e1) != result.end()); + std::vector result = coordinator->getAllEntitiesWith(); + EXPECT_TRUE(std::find(result.begin(), result.end(), e1) != result.end()); - // Destroy the entity. coordinator->destroyEntity(e1); result = coordinator->getAllEntitiesWith(); - EXPECT_TRUE(result.find(e1) == result.end()); + EXPECT_TRUE(std::find(result.begin(), result.end(), e1) == result.end()); } TEST_F(CoordinatorTest, TryGetComponentWorks) { @@ -268,34 +206,23 @@ namespace nexo::ecs { // Register the singleton. coordinator->registerSingletonComponent(77); - // Check that it can be retrieved. - { - TestSingletonComponent &retrieved = coordinator->getSingletonComponent(); - EXPECT_EQ(retrieved.value, 77); - } - // Remove the singleton. EXPECT_NO_THROW(coordinator->removeSingletonComponent()); // After removal, trying to get the singleton should throw. EXPECT_THROW({ coordinator->getSingletonComponent(); - }, std::exception); + }, SingletonComponentNotRegistered); } TEST_F(CoordinatorTest, SingletonComponent_ReRegister) { coordinator->registerSingletonComponent(100); - { - TestSingletonComponent &retrieved = coordinator->getSingletonComponent(); - EXPECT_EQ(retrieved.value, 100); - } // Remove and register a new value. coordinator->removeSingletonComponent(); coordinator->registerSingletonComponent(200); - { - TestSingletonComponent &retrieved = coordinator->getSingletonComponent(); - EXPECT_EQ(retrieved.value, 200); - } + + TestSingletonComponent &retrieved = coordinator->getSingletonComponent(); + EXPECT_EQ(retrieved.value, 200); } TEST_F(CoordinatorTest, GetComponentArray) { @@ -308,10 +235,8 @@ namespace nexo::ecs { // Test basic functionality of the component array Entity entity = coordinator->createEntity(); - componentArray->insertData(entity, TestComponent{42}); - EXPECT_EQ(componentArray->getData(entity).data, 42); - - // Verify the component was actually added to the entity + componentArray->insert(entity, TestComponent{42}); + EXPECT_EQ(componentArray->get(entity).data, 42); EXPECT_EQ(coordinator->getComponent(entity).data, 42); } @@ -394,48 +319,6 @@ namespace nexo::ecs { EXPECT_TRUE(coordinator->entityHasComponent(entity)); } - TEST_F(CoordinatorTest, GetAllComponentsComprehensive) { - coordinator->registerComponent(); - coordinator->registerComponent(); - coordinator->registerComponent(); - - Entity entity = coordinator->createEntity(); - - // Initially, the entity has no components - auto components = coordinator->getAllComponents(entity); - EXPECT_TRUE(components.empty()); - - // Add components - coordinator->addComponent(entity, TestComponent{42}); - coordinator->addComponent(entity, ComponentA{10}); - coordinator->addComponent(entity, ComponentB{3.14f}); - - components = coordinator->getAllComponents(entity); - EXPECT_EQ(components.size(), 3); - - // Verify components are correctly returned - bool hasTestComponent = false; - bool hasComponentA = false; - bool hasComponentB = false; - - for (const auto& [type, value] : components) { - if (type == std::type_index(typeid(TestComponent))) { - hasTestComponent = true; - EXPECT_EQ(std::any_cast(value).data, 42); - } else if (type == std::type_index(typeid(ComponentA))) { - hasComponentA = true; - EXPECT_EQ(std::any_cast(value).value, 10); - } else if (type == std::type_index(typeid(ComponentB))) { - hasComponentB = true; - EXPECT_FLOAT_EQ(std::any_cast(value).data, 3.14f); - } - } - - EXPECT_TRUE(hasTestComponent); - EXPECT_TRUE(hasComponentA); - EXPECT_TRUE(hasComponentB); - } - TEST_F(CoordinatorTest, ModifyComponent) { coordinator->registerComponent(); @@ -452,119 +335,6 @@ namespace nexo::ecs { EXPECT_EQ(coordinator->getComponent(entity).data, 200); } - // This test verifies the updateSystemEntities functionality indirectly - TEST_F(CoordinatorTest, SystemEntityUpdatesWhenComponentsChange) { - // Setup system with a specific signature - auto system = coordinator->registerSystem(); - coordinator->registerComponent(); - coordinator->registerComponent(); - - Signature signature; - signature.set(coordinator->getComponentType()); - signature.set(coordinator->getComponentType()); - coordinator->setSystemSignature(signature); - - // Create entity and add only TestComponent - should not be in system - Entity entity = coordinator->createEntity(); - coordinator->addComponent(entity, TestComponent{42}); - EXPECT_FALSE(system->entities.contains(entity)); - - // Add ComponentA - now it should match the system signature - coordinator->addComponent(entity, ComponentA{10}); - EXPECT_TRUE(system->entities.contains(entity)); - - // Remove ComponentA - should no longer match - coordinator->removeComponent(entity); - EXPECT_FALSE(system->entities.contains(entity)); - } - - TEST_F(CoordinatorTest, MultipleSystemsWithDifferentSignatures) { - // Create two different systems with different signatures - auto systemA = coordinator->registerSystem(); - - class AnotherMockSystem : public System { - public: - MOCK_METHOD(void, update, (), (const)); - }; - - auto systemB = coordinator->registerSystem(); - - coordinator->registerComponent(); - coordinator->registerComponent(); - coordinator->registerComponent(); - - // Set different signatures for each system - Signature signatureA; - signatureA.set(coordinator->getComponentType()); - signatureA.set(coordinator->getComponentType()); - coordinator->setSystemSignature(signatureA); - - Signature signatureB; - signatureB.set(coordinator->getComponentType()); - coordinator->setSystemSignature(signatureB); - - // Create entities with different combinations of components - Entity entity1 = coordinator->createEntity(); - Entity entity2 = coordinator->createEntity(); - Entity entity3 = coordinator->createEntity(); - - // Entity1 has TestComponent and ComponentA - should only be in systemA - coordinator->addComponent(entity1, TestComponent{1}); - coordinator->addComponent(entity1, ComponentA{1}); - - // Entity2 has ComponentB - should only be in systemB - coordinator->addComponent(entity2, ComponentB{2.0f}); - - // Entity3 has all components - should be in both systems - coordinator->addComponent(entity3, TestComponent{3}); - coordinator->addComponent(entity3, ComponentA{3}); - coordinator->addComponent(entity3, ComponentB{3.0f}); - - // Check each system has the expected entities - EXPECT_TRUE(systemA->entities.contains(entity1)); - EXPECT_FALSE(systemA->entities.contains(entity2)); - EXPECT_TRUE(systemA->entities.contains(entity3)); - - EXPECT_FALSE(systemB->entities.contains(entity1)); - EXPECT_TRUE(systemB->entities.contains(entity2)); - EXPECT_TRUE(systemB->entities.contains(entity3)); - } - - TEST_F(CoordinatorTest, EntityCreationMaximumLimit) { - // This test should be adjusted based on your MAX_ENTITIES value - // For simplicity, let's create a reasonable number of entities - const int numEntities = MAX_ENTITIES; // Adjust based on your MAX_ENTITIES - - std::vector entities; - coordinator->registerComponent(); - - // Create entities up to the limit - for (int i = 0; i < numEntities; ++i) { - EXPECT_NO_THROW({ - Entity entity = coordinator->createEntity(); - entities.push_back(entity); - }); - } - - // Verify all entities were created correctly - for (const auto& entity : entities) { - EXPECT_NO_THROW(coordinator->addComponent(entity, TestComponent{1})); - } - - // Delete half the entities - for (size_t i = 0; i < entities.size() / 2; ++i) { - EXPECT_NO_THROW(coordinator->destroyEntity(entities[i])); - } - - // Create more entities to replace the deleted ones - for (size_t i = 0; i < entities.size() / 2; ++i) { - EXPECT_NO_THROW({ - Entity entity = coordinator->createEntity(); - // Could store these new entities if needed - }); - } - } - TEST_F(CoordinatorTest, ComponentArrayIntegration) { coordinator->registerComponent(); @@ -575,19 +345,19 @@ namespace nexo::ecs { Entity entity = coordinator->createEntity(); // Add component directly through component array - componentArray->insertData(entity, TestComponent{42}); + componentArray->insert(entity, TestComponent{42}); // Verify we can access it through the coordinator EXPECT_EQ(coordinator->getComponent(entity).data, 42); // Modify through component array - componentArray->getData(entity).data = 100; + componentArray->get(entity).data = 100; // Verify change is visible through coordinator EXPECT_EQ(coordinator->getComponent(entity).data, 100); // Remove through component array - componentArray->removeData(entity); + componentArray->remove(entity); // Verify it's gone EXPECT_THROW(coordinator->getComponent(entity), ComponentNotFound); @@ -608,6 +378,12 @@ namespace nexo::ecs { // Register multiple singleton types struct AnotherSingletonComponent { float value; + + AnotherSingletonComponent() = default; + AnotherSingletonComponent(float v) : value(v) {} + + AnotherSingletonComponent(const AnotherSingletonComponent&) = delete; + AnotherSingletonComponent& operator=(const AnotherSingletonComponent&) = delete; }; coordinator->registerSingletonComponent(42); diff --git a/tests/ecs/Definitions.test.cpp b/tests/ecs/Definitions.test.cpp new file mode 100644 index 000000000..aafd59058 --- /dev/null +++ b/tests/ecs/Definitions.test.cpp @@ -0,0 +1,89 @@ +//// Definitions.test.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: 09/04/2025 +// Description: Test file for the utils in the definitions header +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "Definitions.hpp" +#include +#include +#include + +namespace nexo::ecs { + + class DefinitionsTest : public ::testing::Test {}; + + // Test structures for component type ID assignment + struct TestComponent1 {}; + struct TestComponent2 {}; + struct TestComponent3 {}; + + // A structure template to test type uniqueness + template + struct GenericComponent {}; + + // getUniqueComponentTypeID function tests + TEST_F(DefinitionsTest, GetUniqueComponentTypeIDAssignsUniqueIDs) { + // Get IDs for multiple component types + ComponentType id1 = getUniqueComponentTypeID(); + ComponentType id2 = getUniqueComponentTypeID(); + ComponentType id3 = getUniqueComponentTypeID(); + + // IDs should be unique + EXPECT_NE(id1, id2); + EXPECT_NE(id1, id3); + EXPECT_NE(id2, id3); + + // IDs should be assigned sequentially starting from 0 + EXPECT_EQ(id2, id1 + 1); + EXPECT_EQ(id3, id2 + 1); + } + + TEST_F(DefinitionsTest, GetUniqueComponentTypeIDReturnsSameIDForSameType) { + // Get the ID for the same type multiple times + ComponentType id1 = getUniqueComponentTypeID(); + ComponentType id2 = getUniqueComponentTypeID(); + ComponentType id3 = getUniqueComponentTypeID(); + + // Same type should get the same ID + EXPECT_EQ(id1, id2); + EXPECT_EQ(id1, id3); + } + + // getComponentTypeID function tests + TEST_F(DefinitionsTest, GetComponentTypeIDRemovesTypeQualifiers) { + // Get IDs for TestComponent1 with different qualifiers + ComponentType baseId = getComponentTypeID(); + ComponentType constId = getComponentTypeID(); + ComponentType volatileId = getComponentTypeID(); + ComponentType refId = getComponentTypeID(); + ComponentType constRefId = getComponentTypeID(); + + // All should return the same ID regardless of qualifiers + EXPECT_EQ(baseId, constId); + EXPECT_EQ(baseId, volatileId); + EXPECT_EQ(baseId, refId); + EXPECT_EQ(baseId, constRefId); + } + + TEST_F(DefinitionsTest, GetComponentTypeIDForTemplatedTypes) { + // Test with template types + ComponentType id1 = getComponentTypeID>(); + ComponentType id2 = getComponentTypeID>(); + ComponentType id3 = getComponentTypeID>(); + + // Different template instantiations should get different IDs + EXPECT_NE(id1, id2); + EXPECT_NE(id1, id3); + EXPECT_NE(id2, id3); + } +} diff --git a/tests/ecs/Entity.test.cpp b/tests/ecs/Entity.test.cpp index 65f5cb617..c703622a1 100644 --- a/tests/ecs/Entity.test.cpp +++ b/tests/ecs/Entity.test.cpp @@ -1,4 +1,4 @@ -//// Entity.test.cpp ////////////////////////////////////////////////////////// +//// Entity.test.cpp /////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -6,246 +6,284 @@ // zzz zzz zzz z zzzz zzzz zzzz zzzz // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // -// Author: Mehdy MORVAN -// Date: 26/11/2024 -// Description: Test file for the entity manager +// Author: iMeaNz +// Date: 2025-04-09 +// Description: Test file for Entity Manager class // /////////////////////////////////////////////////////////////////////////////// #include -#include -#include "ecs/Entity.hpp" -#include "Components.hpp" +#include "Entity.hpp" +#include "ECSExceptions.hpp" +#include +#include +#include namespace nexo::ecs { - class EntityManagerTest : public ::testing::Test { - protected: - void SetUp() override { - entityManager = std::make_unique(); - } - std::unique_ptr entityManager; - }; + class EntityManagerTest : public ::testing::Test { + protected: + EntityManager entityManager; + + // Helper to create multiple entities + std::vector createMultipleEntities(size_t count) { + std::vector entities; + entities.reserve(count); + for (size_t i = 0; i < count; ++i) { + entities.push_back(entityManager.createEntity()); + } + return entities; + } + }; + + // Test constructor + TEST_F(EntityManagerTest, ConstructorInitializesCorrectly) { + EXPECT_EQ(entityManager.getLivingEntityCount(), 0); + + // Create and then destroy an entity to verify the pool works + Entity e = entityManager.createEntity(); + EXPECT_EQ(e, 0); // First entity should be 0 + entityManager.destroyEntity(e); + EXPECT_EQ(entityManager.getLivingEntityCount(), 0); + + // Creating again should reuse the entity ID + Entity e2 = entityManager.createEntity(); + EXPECT_EQ(e2, 0); + } + + TEST_F(EntityManagerTest, CreateEntityReturnsUniqueIDs) { + const size_t numEntities = 100; + std::set uniqueEntities; + + for (size_t i = 0; i < numEntities; ++i) { + Entity e = entityManager.createEntity(); + EXPECT_EQ(uniqueEntities.count(e), 0) << "Entity ID " << e << " was issued multiple times"; + uniqueEntities.insert(e); + } + + EXPECT_EQ(entityManager.getLivingEntityCount(), numEntities); + } + + // Test destroyEntity + TEST_F(EntityManagerTest, DestroyEntityRemovesFromLivingEntities) { + // Create some entities + auto entities = createMultipleEntities(5); + EXPECT_EQ(entityManager.getLivingEntityCount(), 5); + + // Destroy middle entity + entityManager.destroyEntity(entities[2]); + EXPECT_EQ(entityManager.getLivingEntityCount(), 4); + + // Check it's removed from living entities + auto livingEntities = entityManager.getLivingEntities(); + EXPECT_FALSE(std::find(livingEntities.begin(), livingEntities.end(), entities[2]) != livingEntities.end()); + + // Other entities should still be there + EXPECT_TRUE(std::find(livingEntities.begin(), livingEntities.end(), entities[0]) != livingEntities.end()); + EXPECT_TRUE(std::find(livingEntities.begin(), livingEntities.end(), entities[1]) != livingEntities.end()); + EXPECT_TRUE(std::find(livingEntities.begin(), livingEntities.end(), entities[3]) != livingEntities.end()); + EXPECT_TRUE(std::find(livingEntities.begin(), livingEntities.end(), entities[4]) != livingEntities.end()); + } + + TEST_F(EntityManagerTest, DestroyEntityThrowsWithInvalidEntity) { + EXPECT_THROW({ entityManager.destroyEntity(MAX_ENTITIES); }, OutOfRange); + } + + // Test ID recycling + TEST_F(EntityManagerTest, DestroyedEntityIDsAreRecycled) { + // Create entities + auto entities = createMultipleEntities(5); + + // Destroy entities in reverse order + entityManager.destroyEntity(entities[4]); + entityManager.destroyEntity(entities[2]); + entityManager.destroyEntity(entities[0]); + + // New entities should reuse IDs in LIFO order (stack behavior) + Entity e1 = entityManager.createEntity(); + Entity e2 = entityManager.createEntity(); + Entity e3 = entityManager.createEntity(); + + // The IDs should be reused in reverse order of destruction + EXPECT_EQ(e1, entities[0]); + EXPECT_EQ(e2, entities[2]); + EXPECT_EQ(e3, entities[4]); + } + + TEST_F(EntityManagerTest, SetAndGetSignatureWorkCorrectly) { + // Create an entity + Entity e = entityManager.createEntity(); + + // Create a test signature + Signature signature; + signature.set(1); // Set bit 1 + signature.set(3); // Set bit 3 + + // Set signature + entityManager.setSignature(e, signature); + + // Get signature and verify it matches + Signature retrievedSignature = entityManager.getSignature(e); + EXPECT_EQ(retrievedSignature, signature); + EXPECT_TRUE(retrievedSignature.test(1)); + EXPECT_TRUE(retrievedSignature.test(3)); + EXPECT_FALSE(retrievedSignature.test(0)); + EXPECT_FALSE(retrievedSignature.test(2)); + } + + // Test setting signature for invalid entity + TEST_F(EntityManagerTest, SetSignatureThrowsWithInvalidEntity) { + Signature signature; + EXPECT_THROW({ entityManager.setSignature(MAX_ENTITIES, signature); }, OutOfRange); + } + + // Test getting signature for invalid entity + TEST_F(EntityManagerTest, GetSignatureThrowsWithInvalidEntity) { + EXPECT_THROW({ static_cast(entityManager.getSignature(MAX_ENTITIES)); }, OutOfRange); + } + + // Test signature is reset on entity destruction + TEST_F(EntityManagerTest, DestroyEntityResetsSignature) { + // Create an entity and set signature + Entity e = entityManager.createEntity(); + + Signature signature; + signature.set(1); + signature.set(3); + + entityManager.setSignature(e, signature); + + // Destroy entity + entityManager.destroyEntity(e); + + // Create a new entity (should reuse the ID) + Entity newE = entityManager.createEntity(); + EXPECT_EQ(newE, e); + + // Signature should be reset + Signature newSignature = entityManager.getSignature(newE); + EXPECT_EQ(newSignature.count(), 0); + EXPECT_FALSE(newSignature.test(1)); + EXPECT_FALSE(newSignature.test(3)); + } + + // Test get living entities + TEST_F(EntityManagerTest, GetLivingEntitiesReturnsCorrectEntities) { + // Empty at start + EXPECT_EQ(entityManager.getLivingEntities().size(), 0); + + // Create some entities + auto entities = createMultipleEntities(5); + + // Check living entities + auto livingEntities = entityManager.getLivingEntities(); + EXPECT_EQ(livingEntities.size(), 5); + + // Verify all created entities are in the living set + for (auto e : entities) { + bool found = false; + for (auto le : livingEntities) { + if (le == e) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "Entity " << e << " not found in living entities"; + } + + // Remove an entity + entityManager.destroyEntity(entities[2]); + + // Check updated living entities + livingEntities = entityManager.getLivingEntities(); + EXPECT_EQ(livingEntities.size(), 4); + + // Verify entity was removed + bool found = false; + for (auto le : livingEntities) { + if (le == entities[2]) { + found = true; + break; + } + } + EXPECT_FALSE(found) << "Destroyed entity " << entities[2] << " still found in living entities"; + } + + // Test get living entity count + TEST_F(EntityManagerTest, GetLivingEntityCountReturnsCorrectCount) { + EXPECT_EQ(entityManager.getLivingEntityCount(), 0); + + auto e1 = entityManager.createEntity(); + EXPECT_EQ(entityManager.getLivingEntityCount(), 1); + + auto e2 = entityManager.createEntity(); + EXPECT_EQ(entityManager.getLivingEntityCount(), 2); + + entityManager.destroyEntity(e1); + EXPECT_EQ(entityManager.getLivingEntityCount(), 1); + + entityManager.destroyEntity(e2); + EXPECT_EQ(entityManager.getLivingEntityCount(), 0); + } + + // Complex scenario test + TEST_F(EntityManagerTest, ComplexEntityLifecycleScenario) { + // Create a batch of entities + std::vector batch1 = createMultipleEntities(10); + EXPECT_EQ(entityManager.getLivingEntityCount(), 10); + + // Set signatures for some entities + for (size_t i = 0; i < batch1.size(); i += 2) { + Signature sig; + sig.set(i % MAX_COMPONENT_TYPE); + entityManager.setSignature(batch1[i], sig); + } + + // Destroy some entities + for (size_t i = 0; i < batch1.size(); i += 3) { + entityManager.destroyEntity(batch1[i]); + } + + // Create new entities (should reuse some IDs) + std::vector batch2 = createMultipleEntities(5); + + // Verify expected count + EXPECT_EQ(entityManager.getLivingEntityCount(), 11); // 10 - 4 + 5 = 11 (10 original - 4 destroyed + 5 new) + + // Check recycled IDs have clean signatures + for (Entity e : batch2) { + Signature sig = entityManager.getSignature(e); + EXPECT_EQ(sig.count(), 0) << "Recycled entity " << e << " has non-empty signature"; + } + + // Destroy all entities + auto livingEntities = entityManager.getLivingEntities(); + std::vector toDestroy; + toDestroy.reserve(livingEntities.size()); + for (Entity e : livingEntities) { + toDestroy.push_back(e); + } + for (Entity e : toDestroy) { + entityManager.destroyEntity(e); + } + + EXPECT_EQ(entityManager.getLivingEntityCount(), 0); + } + + // Edge case - destroy entity that's already destroyed + TEST_F(EntityManagerTest, DestroyAlreadyDestroyedEntity) { + // This doesn't throw, just performs the operation + Entity e = entityManager.createEntity(); + entityManager.destroyEntity(e); + + // Should not throw, signature already reset + entityManager.destroyEntity(e); + + EXPECT_EQ(entityManager.getLivingEntityCount(), 0); + + // Create a new entity, should still get the same ID back + Entity newE = entityManager.createEntity(); + EXPECT_EQ(newE, e); + } - TEST_F(EntityManagerTest, CreateAndDestroyEntity) { - Entity entity = entityManager->createEntity(); - EXPECT_EQ(entity, 0); // First entity should have ID 0 - - entityManager->destroyEntity(entity); - - // Recreate the entity, it should use the ID in front - Entity reusedEntity = entityManager->createEntity(); - EXPECT_EQ(reusedEntity, 0); - } - - TEST_F(EntityManagerTest, TooManyEntities) { - for (Entity i = 0; i < MAX_ENTITIES; ++i) { - EXPECT_NO_THROW(entityManager->createEntity()); - } - - EXPECT_THROW(entityManager->createEntity(), TooManyEntities); - } - - TEST_F(EntityManagerTest, SetAndGetSignature) { - Entity entity = entityManager->createEntity(); - Signature signature; - signature.set(1); // Set the 1st bit - - entityManager->setSignature(entity, signature); - - Signature retrieved = entityManager->getSignature(entity); - EXPECT_EQ(retrieved, signature); - } - - TEST_F(EntityManagerTest, SetSignatureOutOfRange) { - Entity invalidEntity = MAX_ENTITIES; // Invalid entity ID - Signature signature; - - EXPECT_THROW(entityManager->setSignature(invalidEntity, signature), OutOfRange); - } - - TEST_F(EntityManagerTest, GetSignatureOutOfRange) { - Entity invalidEntity = MAX_ENTITIES; // Invalid entity ID - - EXPECT_THROW(static_cast(entityManager->getSignature(invalidEntity)), OutOfRange); - } - - TEST_F(EntityManagerTest, DestroyEntityResetsSignature) { - Entity entity = entityManager->createEntity(); - Signature signature; - signature.set(1); // Set the 1st bit - - entityManager->setSignature(entity, signature); - - entityManager->destroyEntity(entity); - - Signature resetSignature = entityManager->getSignature(entity); - EXPECT_TRUE(resetSignature.none()); // Signature should be reset - } - - TEST_F(EntityManagerTest, DestroyEntityOutOfRange) { - Entity invalidEntity = MAX_ENTITIES; // Invalid entity ID - - EXPECT_THROW(entityManager->destroyEntity(invalidEntity), OutOfRange); - } - - TEST_F(EntityManagerTest, CreateAndDestroyAllEntities) { - std::vector entities; - - for (Entity i = 0; i < MAX_ENTITIES; ++i) { - entities.push_back(entityManager->createEntity()); - } - - EXPECT_THROW(entityManager->createEntity(), TooManyEntities); - - for (Entity entity : entities) { - EXPECT_NO_THROW(entityManager->destroyEntity(entity)); - } - - EXPECT_NO_THROW(entityManager->createEntity()); - } - - TEST_F(EntityManagerTest, GetLivingEntityCount) { - // Initially should be 0 - EXPECT_EQ(entityManager->getLivingEntityCount(), 0); - - // Create one entity - Entity entity1 = entityManager->createEntity(); - EXPECT_EQ(entityManager->getLivingEntityCount(), 1); - - // Create another entity - Entity entity2 = entityManager->createEntity(); - EXPECT_EQ(entityManager->getLivingEntityCount(), 2); - - // Destroy one entity - entityManager->destroyEntity(entity1); - EXPECT_EQ(entityManager->getLivingEntityCount(), 1); - - // Destroy the other entity - entityManager->destroyEntity(entity2); - EXPECT_EQ(entityManager->getLivingEntityCount(), 0); - } - - TEST_F(EntityManagerTest, EntityIdSequence) { - // Create multiple entities and check the ID sequence - Entity entity1 = entityManager->createEntity(); - Entity entity2 = entityManager->createEntity(); - Entity entity3 = entityManager->createEntity(); - - // IDs should be sequential starting from 0 - EXPECT_EQ(entity1, 0); - EXPECT_EQ(entity2, 1); - EXPECT_EQ(entity3, 2); - } - - TEST_F(EntityManagerTest, ComplexEntityRecycling) { - // Create several entities - std::vector entities; - for (int i = 0; i < 5; ++i) { - entities.push_back(entityManager->createEntity()); - } - - // Destroy entities in a non-sequential order - entityManager->destroyEntity(entities[2]); // Destroy entity with ID 2 - entityManager->destroyEntity(entities[0]); // Destroy entity with ID 0 - - // Create new entities and check the recycling order - // Entities should be reused in FIFO order (first destroyed first reused) - Entity newEntity1 = entityManager->createEntity(); - Entity newEntity2 = entityManager->createEntity(); - - // The first available entity should be recycled first, - // which depends on the order they were added to the availability queue - EXPECT_EQ(newEntity1, 0); // ID 2 should be recycled first (FIFO) - EXPECT_EQ(newEntity2, 2); // ID 0 should be recycled next - - // Destroy all entities and check if count reaches zero - entityManager->destroyEntity(entities[1]); - entityManager->destroyEntity(entities[3]); - entityManager->destroyEntity(entities[4]); - entityManager->destroyEntity(newEntity1); - entityManager->destroyEntity(newEntity2); - - EXPECT_EQ(entityManager->getLivingEntityCount(), 0); - } - - TEST_F(EntityManagerTest, EntityCountTracking) { - // Test edge cases in entity count tracking - - // Create MAX_ENTITIES entities - std::vector entities; - for (Entity i = 0; i < MAX_ENTITIES; ++i) { - entities.push_back(entityManager->createEntity()); - } - - EXPECT_EQ(entityManager->getLivingEntityCount(), MAX_ENTITIES); - - // Destroy half of the entities - for (Entity i = 0; i < MAX_ENTITIES / 2; ++i) { - entityManager->destroyEntity(entities[i]); - } - - if (MAX_ENTITIES % 2 == 0) { - EXPECT_EQ(entityManager->getLivingEntityCount(), MAX_ENTITIES / 2); - } else { - EXPECT_EQ(entityManager->getLivingEntityCount(), MAX_ENTITIES / 2 + 1); - } - - // Create half of them again - for (Entity i = 0; i < MAX_ENTITIES / 2; ++i) { - entityManager->createEntity(); - } - - EXPECT_EQ(entityManager->getLivingEntityCount(), MAX_ENTITIES); - } - - TEST_F(EntityManagerTest, DestroyAlreadyDestroyedEntity) { - // Create an entity - Entity entity = entityManager->createEntity(); - EXPECT_EQ(entityManager->getLivingEntityCount(), 1); - - // Destroy it once - entityManager->destroyEntity(entity); - EXPECT_EQ(entityManager->getLivingEntityCount(), 0); - - // Destroy it again - this should work without errors - // and not affect the living entity count (which should remain 0) - EXPECT_NO_THROW(entityManager->destroyEntity(entity)); - EXPECT_EQ(entityManager->getLivingEntityCount(), 0); - } - - TEST_F(EntityManagerTest, SignatureManipulation) { - // Create an entity - Entity entity = entityManager->createEntity(); - - // Initial signature should be empty - EXPECT_TRUE(entityManager->getSignature(entity).none()); - - // Set some bits - Signature signature; - signature.set(0); - signature.set(5); - signature.set(10); - entityManager->setSignature(entity, signature); - - // Check that signature was properly set - Signature retrieved = entityManager->getSignature(entity); - EXPECT_TRUE(retrieved.test(0)); - EXPECT_TRUE(retrieved.test(5)); - EXPECT_TRUE(retrieved.test(10)); - EXPECT_FALSE(retrieved.test(1)); - - // Modify signature - Signature newSignature = retrieved; - newSignature.reset(0); - newSignature.set(3); - entityManager->setSignature(entity, newSignature); - - // Check updated signature - retrieved = entityManager->getSignature(entity); - EXPECT_FALSE(retrieved.test(0)); - EXPECT_TRUE(retrieved.test(3)); - EXPECT_TRUE(retrieved.test(5)); - EXPECT_TRUE(retrieved.test(10)); - } } diff --git a/tests/ecs/Exceptions.test.cpp b/tests/ecs/Exceptions.test.cpp index b2fa894ed..1f63c3289 100644 --- a/tests/ecs/Exceptions.test.cpp +++ b/tests/ecs/Exceptions.test.cpp @@ -1,4 +1,4 @@ -//// ECSExceptions.test.cpp /////////////////////////////////////////////////// +//// ECSExceptionsTest.cpp /////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -6,86 +6,208 @@ // zzz zzz zzz z zzzz zzzz zzzz zzzz // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // -// Author: Mehdy MORVAN -// Date: 02/12/2024 -// Description: Test file for the ECSExceptions class +// Author: iMeaNz +// Date: 2025-04-09 +// Description: Test file for ECS exceptions // /////////////////////////////////////////////////////////////////////////////// #include -#include "ecs/ECSExceptions.hpp" +#include "ECSExceptions.hpp" +#include +#include namespace nexo::ecs { - TEST(ECSExceptionsTest, ComponentNotFound) { - constexpr const char* expectedFile = __FILE__; - constexpr unsigned int expectedLine = __LINE__ + 2; - - ComponentNotFound ex(42); - std::string formattedMessage = ex.what(); - - EXPECT_NE(formattedMessage.find("Component not found for: 42"), std::string::npos); - EXPECT_NE(formattedMessage.find(expectedFile), std::string::npos); - EXPECT_NE(formattedMessage.find(std::to_string(expectedLine)), std::string::npos); - } - - TEST(ECSExceptionsTest, ComponentNotRegistered) { - constexpr const char* expectedFile = __FILE__; - constexpr unsigned int expectedLine = __LINE__ + 2; - - ComponentNotRegistered ex; - std::string formattedMessage = ex.what(); - - EXPECT_NE(formattedMessage.find("Component has not been registered before use"), std::string::npos); - EXPECT_NE(formattedMessage.find(expectedFile), std::string::npos); - EXPECT_NE(formattedMessage.find(std::to_string(expectedLine)), std::string::npos); - } - - TEST(ECSExceptionsTest, SingletonComponentNotRegistered) { - constexpr const char* expectedFile = __FILE__; - constexpr unsigned int expectedLine = __LINE__ + 2; - - SingletonComponentNotRegistered ex; - std::string formattedMessage = ex.what(); - - EXPECT_NE(formattedMessage.find("Singleton component has not been registered before use"), std::string::npos); - EXPECT_NE(formattedMessage.find(expectedFile), std::string::npos); - EXPECT_NE(formattedMessage.find(std::to_string(expectedLine)), std::string::npos); - } - - TEST(ECSExceptionsTest, SystemNotRegistered) { - constexpr const char* expectedFile = __FILE__; - constexpr unsigned int expectedLine = __LINE__ + 2; - - SystemNotRegistered ex; - std::string formattedMessage = ex.what(); - - EXPECT_NE(formattedMessage.find("System has not been registered before use"), std::string::npos); - EXPECT_NE(formattedMessage.find(expectedFile), std::string::npos); - EXPECT_NE(formattedMessage.find(std::to_string(expectedLine)), std::string::npos); - } - - TEST(ECSExceptionsTest, TooManyEntities) { - constexpr const char* expectedFile = __FILE__; - constexpr unsigned int expectedLine = __LINE__ + 2; - - TooManyEntities ex; - std::string formattedMessage = ex.what(); - - EXPECT_NE(formattedMessage.find(std::format("Too many living entities, max is {}", MAX_ENTITIES)), std::string::npos); - EXPECT_NE(formattedMessage.find(expectedFile), std::string::npos); - EXPECT_NE(formattedMessage.find(std::to_string(expectedLine)), std::string::npos); - } - - TEST(ECSExceptionsTest, OutOfRange) { - constexpr const char* expectedFile = __FILE__; - constexpr unsigned int expectedLine = __LINE__ + 2; - - OutOfRange ex(256); - std::string formattedMessage = ex.what(); - - EXPECT_NE(formattedMessage.find("Index 256 is out of range"), std::string::npos); - EXPECT_NE(formattedMessage.find(expectedFile), std::string::npos); - EXPECT_NE(formattedMessage.find(std::to_string(expectedLine)), std::string::npos); - } + // Base test fixture for exception tests + class ECSExceptionsTest : public ::testing::Test { + protected: + void SetUp() override { + // No specific setup needed + } + + // Helper method to verify exception inheritance + template + void verifyExceptionHierarchy() { + static_assert(std::is_base_of_v, + "Exception class must inherit from Exception"); + static_assert(std::is_final_v, + "Exception class should be marked final"); + } + + // Helper method to check exception message contains expected text + template + void verifyExceptionMessage(const std::string& expectedSubstring, Args&&... args) { + try { + throw ExceptionType(std::forward(args)...); + } catch (const Exception& e) { + std::string message = e.what(); + EXPECT_NE(message.find(expectedSubstring), std::string::npos) + << "Exception message '" << message << "' should contain '" + << expectedSubstring << "'"; + } catch (...) { + FAIL() << "Exception was not caught as Exception base class"; + } + } + }; + + // Test InternalError exception + TEST_F(ECSExceptionsTest, InternalErrorTest) { + verifyExceptionHierarchy(); + + const std::string errorMsg = "Something bad happened"; + verifyExceptionMessage(errorMsg, errorMsg); + verifyExceptionMessage("Internal error", errorMsg); + + // Test polymorphic catching + try { + throw InternalError("Test error"); + FAIL() << "Exception was not thrown"; + } catch (const InternalError& e) { + SUCCEED(); + } catch (...) { + FAIL() << "Wrong exception type caught"; + } + } + + // Test ComponentNotFound exception + TEST_F(ECSExceptionsTest, ComponentNotFoundTest) { + verifyExceptionHierarchy(); + + const Entity testEntity = 42; + verifyExceptionMessage(std::to_string(testEntity), testEntity); + verifyExceptionMessage("Component not found", testEntity); + + // Test with different entity values + verifyExceptionMessage("0", Entity(0)); + verifyExceptionMessage(std::to_string(MAX_ENTITIES-1), MAX_ENTITIES-1); + } + + // Test OverlappingGroupsException exception + TEST_F(ECSExceptionsTest, OverlappingGroupsExceptionTest) { + verifyExceptionHierarchy(); + + const std::string existingGroup = "Group1"; + const std::string newGroup = "Group2"; + const ComponentType conflictComponent = 5; + + verifyExceptionMessage(existingGroup, + existingGroup, newGroup, conflictComponent); + verifyExceptionMessage(newGroup, + existingGroup, newGroup, conflictComponent); + verifyExceptionMessage(std::to_string(conflictComponent), + existingGroup, newGroup, conflictComponent); + verifyExceptionMessage("overlapping owned component", + existingGroup, newGroup, conflictComponent); + + // Test with different component types + verifyExceptionMessage("component #0", + "GroupA", "GroupB", ComponentType(0)); + verifyExceptionMessage("component #31", + "GroupX", "GroupY", ComponentType(31)); + } + + // Test GroupNotFound exception + TEST_F(ECSExceptionsTest, GroupNotFoundTest) { + verifyExceptionHierarchy(); + + const std::string groupKey = "TestGroup"; + verifyExceptionMessage(groupKey, groupKey); + verifyExceptionMessage("Group not found", groupKey); + + // Test with empty key + verifyExceptionMessage("", ""); + } + + // Test ComponentNotRegistered exception + TEST_F(ECSExceptionsTest, ComponentNotRegisteredTest) { + verifyExceptionHierarchy(); + + verifyExceptionMessage("Component has not been registered"); + + // Test that no parameters are needed + try { + throw ComponentNotRegistered(); + FAIL() << "Exception was not thrown"; + } catch (const ComponentNotRegistered&) { + SUCCEED(); + } catch (...) { + FAIL() << "Wrong exception type caught"; + } + } + + // Test SingletonComponentNotRegistered exception + TEST_F(ECSExceptionsTest, SingletonComponentNotRegisteredTest) { + verifyExceptionHierarchy(); + + verifyExceptionMessage("Singleton component"); + verifyExceptionMessage("not been registered"); + + // Make sure it's distinct from ComponentNotRegistered + try { + throw SingletonComponentNotRegistered(); + } catch (const ComponentNotRegistered&) { + FAIL() << "SingletonComponentNotRegistered should not be caught as ComponentNotRegistered"; + } catch (const SingletonComponentNotRegistered&) { + SUCCEED(); + } catch (...) { + FAIL() << "Wrong exception type caught"; + } + } + + // Test SystemNotRegistered exception + TEST_F(ECSExceptionsTest, SystemNotRegisteredTest) { + verifyExceptionHierarchy(); + + verifyExceptionMessage("System has not been registered"); + } + + // Test TooManyEntities exception + TEST_F(ECSExceptionsTest, TooManyEntitiesTest) { + verifyExceptionHierarchy(); + + verifyExceptionMessage("Too many living entities"); + verifyExceptionMessage(std::to_string(MAX_ENTITIES)); + } + + // Test OutOfRange exception + TEST_F(ECSExceptionsTest, OutOfRangeTest) { + verifyExceptionHierarchy(); + + const unsigned int testIndex = 999; + verifyExceptionMessage(std::to_string(testIndex), testIndex); + verifyExceptionMessage("out of range", testIndex); + + // Test with different index values + verifyExceptionMessage("0", 0u); + verifyExceptionMessage(std::to_string(UINT_MAX), UINT_MAX); + } + + // Test that all exceptions can be caught polymorphically as Exception + TEST_F(ECSExceptionsTest, PolymorphicExceptionHandlingTest) { + int caughtCount = 0; + + try { + // Randomly choose one exception to throw + int choice = rand() % 8; + switch (choice) { + case 0: throw InternalError("Test"); + case 1: throw ComponentNotFound(5); + case 2: throw OverlappingGroupsException("G1", "G2", 3); + case 3: throw GroupNotFound("Key"); + case 4: throw ComponentNotRegistered(); + case 5: throw SingletonComponentNotRegistered(); + case 6: throw SystemNotRegistered(); + case 7: throw TooManyEntities(); + default: throw OutOfRange(10); + } + } catch (const Exception& e) { + // All exceptions should be caught here + caughtCount++; + } catch (...) { + FAIL() << "Exception not caught polymorphically"; + } + + EXPECT_EQ(caughtCount, 1) << "Exception should be caught exactly once"; + } } diff --git a/tests/ecs/Group.test.cpp b/tests/ecs/Group.test.cpp index a3667b32b..8206d54a3 100644 --- a/tests/ecs/Group.test.cpp +++ b/tests/ecs/Group.test.cpp @@ -1,4 +1,4 @@ -//// Group.test.cpp /////////////////////////////////////////////////////////// +//// Group.test.cpp ///////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -6,755 +6,617 @@ // zzz zzz zzz z zzzz zzzz zzzz zzzz // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // -// Author: iMeaNz -// Date: 03/04/2025 -// Description: Test file for the ECS Group class +// Author: Mehdy MORVAN +// Date: 09/04/2025 +// Description: Test file for the group class // /////////////////////////////////////////////////////////////////////////////// -#include #include -#include -#include "ecs/Definitions.hpp" -#include "ecs/ECSExceptions.hpp" -#include "ecs/ComponentArray.hpp" -#include "ecs/Components.hpp" -#include "ecs/Group.hpp" +#include "Group.hpp" +#include "ComponentArray.hpp" +#include "ECSExceptions.hpp" +#include +#include +#include +#include namespace nexo::ecs { - // Define test components - struct Position { - float x; - float y; - - bool operator==(const Position& other) const { - return x == other.x && y == other.y; - } - }; - - struct Velocity { - float x; - float y; - - bool operator==(const Velocity& other) const { - return x == other.x && y == other.y; - } - }; - - struct Health { - int value; - - bool operator==(const Health& other) const { - return value == other.value; - } - }; - - struct Tag { - std::string name; - - bool operator==(const Tag& other) const { - return name == other.name; - } - }; - - class GroupTest : public ::testing::Test { - protected: - void SetUp() override { - // Create component arrays - positionArray = std::make_shared>(); - velocityArray = std::make_shared>(); - healthArray = std::make_shared>(); - tagArray = std::make_shared>(); - - // Insert test data - for (Entity i = 0; i < 10; ++i) { - positionArray->insertData(i, Position{static_cast(i), static_cast(i * 2)}); - velocityArray->insertData(i, Velocity{static_cast(i + 0.5f), static_cast((i + 1) * 2)}); - healthArray->insertData(i, Health{100 - static_cast(i * 10)}); - tagArray->insertData(i, Tag{"Entity" + std::to_string(i)}); - - // Add entities 0-4 to the group section - if (i < 5) { - positionArray->addToGroup(i); - velocityArray->addToGroup(i); - healthArray->addToGroup(i); - tagArray->addToGroup(i); - } - } - } - - // Helper method to set up health values for partition tests - void groupHealthData() { - // Modify health values to have distinct groups - // Entity 0: Health 90 (High) - // Entity 1: Health 60 (Medium) - // Entity 2: Health 30 (Low) - // Entity 3: Health 80 (High) - // Entity 4: Health 20 (Low) - healthArray->getData(0).value = 90; - healthArray->getData(1).value = 60; - healthArray->getData(2).value = 30; - healthArray->getData(3).value = 80; - healthArray->getData(4).value = 20; - } - - // Helper method to create groups with different configurations - template - auto createGroup(const get_t<>& nonOwned = get<>()) { - using OwnedTuple = std::tuple>...>; - using NonOwnedTuple = std::tuple<>; - auto ownedArrays = std::make_tuple(getComponentArray()...); - auto nonOwnedArrays = std::make_tuple(); - return std::make_shared>(ownedArrays, nonOwnedArrays); - } - - template - auto createGroup(const get_t& nonOwned) { - using OwnedTuple = std::tuple>...>; - using NonOwnedTuple = std::tuple>...>; - auto ownedArrays = std::make_tuple(getComponentArray()...); - auto nonOwnedArrays = std::make_tuple(getComponentArray()...); - return std::make_shared>(ownedArrays, nonOwnedArrays); - } - - - - template - std::shared_ptr> getComponentArray() { - if constexpr (std::is_same_v) { - return positionArray; - } else if constexpr (std::is_same_v) { - return velocityArray; - } else if constexpr (std::is_same_v) { - return healthArray; - } else if constexpr (std::is_same_v) { - return tagArray; - } - } - - std::shared_ptr> positionArray; - std::shared_ptr> velocityArray; - std::shared_ptr> healthArray; - std::shared_ptr> tagArray; - }; - - // ======================================================================== - // Basic Functionality Tests - // ======================================================================== - - TEST_F(GroupTest, Construction) { - auto group = createGroup(get()); - - // Check that signatures are correctly set - EXPECT_TRUE(group->ownedSignature().test(getComponentTypeID())); - EXPECT_TRUE(group->ownedSignature().test(getComponentTypeID())); - EXPECT_FALSE(group->ownedSignature().test(getComponentTypeID())); - EXPECT_FALSE(group->ownedSignature().test(getComponentTypeID())); - - // Check all signature - EXPECT_TRUE(group->allSignature().test(getComponentTypeID())); - EXPECT_TRUE(group->allSignature().test(getComponentTypeID())); - EXPECT_TRUE(group->allSignature().test(getComponentTypeID())); - EXPECT_TRUE(group->allSignature().test(getComponentTypeID())); - - // Verify size - EXPECT_EQ(group->size(), 5); - } - - TEST_F(GroupTest, AddToGroup) { - auto group = createGroup(); - - // Initially the group has entities 0-4 - EXPECT_EQ(group->size(), 5); - - // Add entity 5 to the group - group->addToGroup(5); - EXPECT_EQ(group->size(), 6); - - // Check that entity 5 is in the group using the entities() method - auto entities = group->entities(); - EXPECT_EQ(entities.size(), 6); - EXPECT_NE(std::find(entities.begin(), entities.end(), 5), entities.end()); - } - - TEST_F(GroupTest, RemoveFromGroup) { - auto group = createGroup(); - - // Initially the group has entities 0-4 - EXPECT_EQ(group->size(), 5); - - // Remove entity 2 from the group - group->removeFromGroup(2); - EXPECT_EQ(group->size(), 4); - - // Check that entity 2 is no longer in the group - auto entities = group->entities(); - EXPECT_EQ(entities.size(), 4); - EXPECT_EQ(std::find(entities.begin(), entities.end(), 2), entities.end()); - } - - TEST_F(GroupTest, EntitiesMethod) { - auto group = createGroup(); - - // Check that entities() returns the correct entities - auto entities = group->entities(); - EXPECT_EQ(entities.size(), 5); - - // Entities should be 0-4 - for (Entity i = 0; i < 5; ++i) { - EXPECT_NE(std::find(entities.begin(), entities.end(), i), entities.end()); - } - - // Entity 5 should not be in the group - EXPECT_EQ(std::find(entities.begin(), entities.end(), 5), entities.end()); - } - - TEST_F(GroupTest, SortingInvalidatedFlag) { - auto group = createGroup(); - - // Initially sorting should be invalidated - EXPECT_TRUE(group->sortingInvalidated()); - - // Sort the group - group->sortBy([](const Position& p) { return p.x; }); - - // Sorting should no longer be invalidated - EXPECT_FALSE(group->sortingInvalidated()); - - // Adding a new entity should invalidate sorting - group->addToGroup(5); - EXPECT_TRUE(group->sortingInvalidated()); - - // Sort again - group->sortBy([](const Position& p) { return p.x; }); - EXPECT_FALSE(group->sortingInvalidated()); - - // Removing an entity should invalidate sorting - group->removeFromGroup(0); - EXPECT_TRUE(group->sortingInvalidated()); - } - - // ======================================================================== - // Iterator Tests - // ======================================================================== - - TEST_F(GroupTest, IteratorBasics) { - auto group = createGroup(); - - // Check begin() and end() - auto it = group->begin(); - auto end = group->end(); - - EXPECT_NE(it, end); - - // Advance iterator manually and check it eventually reaches end - std::size_t count = 0; - while (it != end) { - ++it; - ++count; - } - - EXPECT_EQ(count, 5); - } - - TEST_F(GroupTest, IteratorDereference) { - auto group = createGroup(); - - // Dereference the first iterator and check entity and position - auto it = group->begin(); - auto [entity, position] = *it; - - EXPECT_EQ(entity, 0); - EXPECT_FLOAT_EQ(position.x, 0.0f); - EXPECT_FLOAT_EQ(position.y, 0.0f); - - // Move to the next entity - ++it; - auto [entity2, position2] = *it; - - EXPECT_EQ(entity2, 1); - EXPECT_FLOAT_EQ(position2.x, 1.0f); - EXPECT_FLOAT_EQ(position2.y, 2.0f); - } - - TEST_F(GroupTest, RangeBasedFor) { - auto group = createGroup(); - - std::vector entities; - std::vector positions; - std::vector velocities; - - // Use range-based for loop to collect all entities and components - for (auto [entity, position, velocity] : *group) { - entities.push_back(entity); - positions.push_back(position); - velocities.push_back(velocity); - } - - // Check that we got all 5 entities - EXPECT_EQ(entities.size(), 5); - - // Check that the entities and components match - for (std::size_t i = 0; i < entities.size(); ++i) { - EXPECT_EQ(entities[i], i); - EXPECT_FLOAT_EQ(positions[i].x, i); - EXPECT_FLOAT_EQ(positions[i].y, i * 2); - EXPECT_FLOAT_EQ(velocities[i].x, i + 0.5f); - EXPECT_FLOAT_EQ(velocities[i].y, (i + 1) * 2); - } - } - - TEST_F(GroupTest, EachMethod) { - auto group = createGroup(get()); - - std::vector entities; - std::vector positions; - std::vector velocities; - std::vector healths; - - // Use each method to collect all entities and components - group->each([&](Entity entity, Position& position, Velocity& velocity, Health& health) { - entities.push_back(entity); - positions.push_back(position); - velocities.push_back(velocity); - healths.push_back(health); - }); - - // Check that we got all 5 entities - EXPECT_EQ(entities.size(), 5); - - // Check that the entities and components match - for (std::size_t i = 0; i < entities.size(); ++i) { - EXPECT_EQ(entities[i], i); - EXPECT_FLOAT_EQ(positions[i].x, i); - EXPECT_FLOAT_EQ(positions[i].y, i * 2); - EXPECT_FLOAT_EQ(velocities[i].x, i + 0.5f); - EXPECT_FLOAT_EQ(velocities[i].y, (i + 1) * 2); - EXPECT_EQ(healths[i], Health{100 - static_cast(i * 10)}); - } - } - - TEST_F(GroupTest, EachInRangeMethod) { - auto group = createGroup(); - - std::vector entities; - - // Use eachInRange to collect entities from index 1 to 3 - group->eachInRange(1, 3, [&](Entity entity, Position& position, Velocity& velocity) { - entities.push_back(entity); - }); - - // Check that we got entities 1, 2, and 3 - EXPECT_EQ(entities.size(), 3); - EXPECT_EQ(entities[0], 1); - EXPECT_EQ(entities[1], 2); - EXPECT_EQ(entities[2], 3); - } - - TEST_F(GroupTest, EachInRangeOutOfBounds) { - auto group = createGroup(); - - std::vector entities; - - // Start at a valid index but request too many elements - group->eachInRange(3, 10, [&](Entity entity, Position& position, Velocity& velocity) { - entities.push_back(entity); - }); - - // We should only get entities 3 and 4 (not out of bounds) - EXPECT_EQ(entities.size(), 2); - EXPECT_EQ(entities[0], 3); - EXPECT_EQ(entities[1], 4); - } - - // ======================================================================== - // Component Access Tests - // ======================================================================== - - TEST_F(GroupTest, GetMethod) { - auto group = createGroup(); - - // Get all positions - auto positions = group->get(); - - // Check that the positions are correct - EXPECT_EQ(positions.size(), 5); - for (std::size_t i = 0; i < positions.size(); ++i) { - EXPECT_FLOAT_EQ(positions[i].x, i); - EXPECT_FLOAT_EQ(positions[i].y, i * 2); - } - - // Get all velocities - auto velocities = group->get(); - - // Check that the velocities are correct - EXPECT_EQ(velocities.size(), 5); - for (std::size_t i = 0; i < velocities.size(); ++i) { - EXPECT_FLOAT_EQ(velocities[i].x, i + 0.5f); - EXPECT_FLOAT_EQ(velocities[i].y, (i + 1) * 2); - } - } - - TEST_F(GroupTest, GetMethodWithNonOwnedComponent) { - auto group = createGroup(get()); - - // Get all positions (owned) - auto positions = group->get(); - - // Check that the positions are correct - EXPECT_EQ(positions.size(), 5); - for (std::size_t i = 0; i < positions.size(); ++i) { - EXPECT_FLOAT_EQ(positions[i].x, i); - EXPECT_FLOAT_EQ(positions[i].y, i * 2); - } - - // Try to get velocities (non-owned) - this should compile but might have different behavior - // For our implementation, we still want to access the velocity components - auto velocities = group->get(); - - // The non-owned components should be accessible through the get method - EXPECT_GT(velocities->size(), 0); - } - - // ======================================================================== - // Sorting Tests - // ======================================================================== - - TEST_F(GroupTest, SortByPositionXAscending) { - auto group = createGroup(); - - // Add entities in reverse order to ensure sorting will change the order - for (Entity i = 9; i >= 5; --i) { - positionArray->addToGroup(i); - velocityArray->addToGroup(i); - } - - // Sort by position.x in ascending order - group->sortBy([](const Position& p) { return p.x; }); - - // Check that entities are sorted correctly - auto entities = group->entities(); - - // Entities should be in order 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - for (std::size_t i = 0; i < entities.size(); ++i) { - EXPECT_EQ(entities[i], i); - } - } - - TEST_F(GroupTest, SortByPositionXDescending) { - auto group = createGroup(); - - // Add more entities to ensure we have enough for a good test - for (Entity i = 5; i < 10; ++i) { - positionArray->addToGroup(i); - velocityArray->addToGroup(i); - } - - // Sort by position.x in descending order - group->sortBy([](const Position& p) { return p.x; }, false); - - // Check that entities are sorted correctly - auto entities = group->entities(); - - // Entities should be in reverse order 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 - for (std::size_t i = 0; i < entities.size(); ++i) { - EXPECT_EQ(entities[i], 9 - i); - } - } - - TEST_F(GroupTest, SortByNonOwnedComponent) { - auto group = createGroup(get()); - - // Add more entities to ensure we have enough for a good test - for (Entity i = 5; i < 10; ++i) { - positionArray->addToGroup(i); - } - - // Sort by health value (non-owned) in ascending order (lower health first) - group->sortBy([](const Health& h) { return h.value; }); - - // Check that entities are sorted correctly - auto entities = group->entities(); - - // Health values are 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 - // So entities should be in reverse order 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 - for (std::size_t i = 0; i < entities.size(); ++i) { - EXPECT_EQ(entities[i], 9 - i); - } - } - - TEST_F(GroupTest, SortSaveWithNoInvalidation) { - auto group = createGroup(); - - // Sort the group - group->sortBy([](const Position& p) { return p.x; }); - - // Get the current order - auto initialEntities = group->entities(); - - // Count function calls to check if sorting is skipped - int callCount = 0; - auto countingExtractor = [&callCount](const Position& p) { - callCount++; - return p.x; - }; - - // Call sort again - it should be skipped since sorting is not invalidated - group->sortBy(countingExtractor); - - // No extraction function calls should happen since sorting was skipped - EXPECT_EQ(callCount, 0); - - // Order should remain the same - auto newEntities = group->entities(); - EXPECT_TRUE(std::equal(initialEntities.begin(), initialEntities.end(), newEntities.begin())); - } - - // ======================================================================== - // Partitioning Tests - // ======================================================================== - - TEST_F(GroupTest, GetPartitionViewBasic) { - auto group = createGroup(); - - // Create a partition view based on tag name first character - auto partitionView = group->getPartitionView([](const Tag& tag) { - return tag.name.empty() ? ' ' : tag.name[0]; - }); - - // All entities have tags starting with 'E', so there should be one partition - EXPECT_EQ(partitionView.partitionCount(), 1); - - // Get the keys (should be 'E') - auto keys = partitionView.getPartitionKeys(); - EXPECT_EQ(keys.size(), 1); - EXPECT_EQ(keys[0], 'E'); - - // Get the partition for 'E' - const auto* partition = partitionView.getPartition('E'); - ASSERT_NE(partition, nullptr); - EXPECT_EQ(partition->key, 'E'); - EXPECT_EQ(partition->count, 5); - EXPECT_EQ(partition->startIndex, 0); - } - - TEST_F(GroupTest, MultiplePartitions) { - auto group = createGroup(); - - // Modify health values to create multiple partitions - // Group health values into three categories: Low (0-30), Medium (31-70), High (71-100) - groupHealthData(); - - // Create a partition view based on health category - auto partitionView = group->getPartitionView([](const Health& health) { - if (health.value <= 30) return "Low"; - else if (health.value <= 70) return "Medium"; - else return "High"; - }); - - // There should be three partitions - EXPECT_EQ(partitionView.partitionCount(), 3); - - // Get the keys - auto keys = partitionView.getPartitionKeys(); - EXPECT_EQ(keys.size(), 3); - EXPECT_NE(std::find(keys.begin(), keys.end(), "Low"), keys.end()); - EXPECT_NE(std::find(keys.begin(), keys.end(), "Medium"), keys.end()); - EXPECT_NE(std::find(keys.begin(), keys.end(), "High"), keys.end()); - - // Check each partition - const auto* lowPartition = partitionView.getPartition("Low"); - ASSERT_NE(lowPartition, nullptr); - - const auto* mediumPartition = partitionView.getPartition("Medium"); - ASSERT_NE(mediumPartition, nullptr); - - const auto* highPartition = partitionView.getPartition("High"); - ASSERT_NE(highPartition, nullptr); - } - - TEST_F(GroupTest, PartitionEachMethod) { - auto group = createGroup(); - - // Modify health values to create multiple partitions - groupHealthData(); - - // Create a partition view - auto partitionView = group->getPartitionView([](const Health& health) { - if (health.value <= 30) return "Low"; - else if (health.value <= 70) return "Medium"; - else return "High"; - }); - - // Collect entities in the "Low" partition - std::vector lowEntities; - partitionView.each("Low", [&](Entity entity, Position& position, Health& health) { - lowEntities.push_back(entity); - // Verify the health value is in the low range - EXPECT_LE(health.value, 30); - }); - - // Collect entities in the "High" partition - std::vector highEntities; - partitionView.each("High", [&](Entity entity, Position& position, Health& health) { - highEntities.push_back(entity); - // Verify the health value is in the high range - EXPECT_GE(health.value, 71); - }); - - // Check that we collected the right number of entities - const auto* lowPartition = partitionView.getPartition("Low"); - ASSERT_NE(lowPartition, nullptr); - EXPECT_EQ(lowEntities.size(), lowPartition->count); - - const auto* highPartition = partitionView.getPartition("High"); - ASSERT_NE(highPartition, nullptr); - EXPECT_EQ(highEntities.size(), highPartition->count); - } - - TEST_F(GroupTest, EntityPartitionView) { - auto group = createGroup(); - - // Create a partition view based directly on entity ID parity - auto partitionView = group->getEntityPartitionView( - "parity", - [](Entity e) { return e % 2 == 0 ? "Even" : "Odd"; } - ); - - // There should be two partitions: Even and Odd - EXPECT_EQ(partitionView.partitionCount(), 2); - - // Get the keys - auto keys = partitionView.getPartitionKeys(); - EXPECT_EQ(keys.size(), 2); - EXPECT_NE(std::find(keys.begin(), keys.end(), "Even"), keys.end()); - EXPECT_NE(std::find(keys.begin(), keys.end(), "Odd"), keys.end()); - - // Check each partition - std::vector evenEntities; - partitionView.each("Even", [&](Entity entity, Position& position, Velocity& velocity) { - evenEntities.push_back(entity); - // Verify the entity ID is even - EXPECT_EQ(entity % 2, 0); - }); - - std::vector oddEntities; - partitionView.each("Odd", [&](Entity entity, Position& position, Velocity& velocity) { - oddEntities.push_back(entity); - // Verify the entity ID is odd - EXPECT_EQ(entity % 2, 1); - }); - - // In our initial setup, we have entities 0, 1, 2, 3, 4 in the group - EXPECT_EQ(evenEntities.size(), 3); // 0, 2, 4 - EXPECT_EQ(oddEntities.size(), 2); // 1, 3 - } - - TEST_F(GroupTest, PartitionInvalidation) { - auto group = createGroup(); - - // Create a partition view - auto partitionView = group->getEntityPartitionView( - "parity", - [](Entity e) { return e % 2 == 0 ? "Even" : "Odd"; } - ); - - // Initial counts - auto* evenPartition = partitionView.getPartition("Even"); - auto* oddPartition = partitionView.getPartition("Odd"); - ASSERT_NE(evenPartition, nullptr); - ASSERT_NE(oddPartition, nullptr); - - size_t initialEvenCount = evenPartition->count; - size_t initialOddCount = oddPartition->count; - - // Add a new entity to the group - group->addToGroup(5); // Odd entity - - // Get the partition view again - should rebuild - auto updatedPartitionView = group->getEntityPartitionView( - "parity", - [](Entity e) { return e % 2 == 0 ? "Even" : "Odd"; } - ); - - // Check the new counts - evenPartition = updatedPartitionView.getPartition("Even"); - oddPartition = updatedPartitionView.getPartition("Odd"); - ASSERT_NE(evenPartition, nullptr); - ASSERT_NE(oddPartition, nullptr); - - // Even count should be the same, odd count should increase by 1 - EXPECT_EQ(evenPartition->count, initialEvenCount); - EXPECT_EQ(oddPartition->count, initialOddCount + 1); - } - - // ======================================================================== - // Edge Cases and Error Handling - // ======================================================================== - - TEST_F(GroupTest, EmptyGroup) { - // Clear all entities from component arrays - positionArray = std::make_shared>(); - velocityArray = std::make_shared>(); - - // Create an empty group - auto group = createGroup(); - - // Check size and entities - EXPECT_EQ(group->size(), 0); - EXPECT_EQ(group->entities().size(), 0); - - // Check iterator - EXPECT_EQ(group->begin(), group->end()); - - // Ensure each method doesn't error on empty group - int callCount = 0; - group->each([&](Entity, Position&, Velocity&) { callCount++; }); - EXPECT_EQ(callCount, 0); - - // Ensure get method returns empty span - auto positions = group->get(); - EXPECT_EQ(positions.size(), 0); - - // Ensure sorting doesn't error - group->sortBy([](const Position& p) { return p.x; }); - - // Create partition view - auto partitionView = group->getPartitionView([](const Position& p) { return p.x; }); - - // Should have no partitions - EXPECT_EQ(partitionView.partitionCount(), 0); - } - - TEST_F(GroupTest, RemoveNonExistingEntity) { - auto group = createGroup(); - - // Initially the group has entities 0-4 - EXPECT_EQ(group->size(), 5); - - // Try to remove an entity that's not in the group - EXPECT_THROW(group->removeFromGroup(100), ComponentNotFound); - - // Size should remain the same - EXPECT_EQ(group->size(), 5); - } - - TEST_F(GroupTest, FindNonExistingPartition) { - auto group = createGroup(); - - // Create a partition view - auto partitionView = group->getPartitionView([](const Tag& tag) { - return tag.name.empty() ? ' ' : tag.name[0]; - }); - - // Try to get a partition that doesn't exist - const auto* nonExistingPartition = partitionView.getPartition('Z'); - EXPECT_EQ(nonExistingPartition, nullptr); - - // Try to iterate over a non-existing partition - int callCount = 0; - partitionView.each('Z', [&](Entity, Position&, Tag&) { callCount++; }); - EXPECT_EQ(callCount, 0); - } -}; + // Test component types + struct PositionComponent { + float x, y, z; + + PositionComponent(float x = 0.0f, float y = 0.0f, float z = 0.0f) + : x(x), y(y), z(z) {} + + bool operator==(const PositionComponent& other) const { + return x == other.x && y == other.y && z == other.z; + } + }; + + struct VelocityComponent { + float vx, vy, vz; + + VelocityComponent(float vx = 0.0f, float vy = 0.0f, float vz = 0.0f) + : vx(vx), vy(vy), vz(vz) {} + + bool operator==(const VelocityComponent& other) const { + return vx == other.vx && vy == other.vy && vz == other.vz; + } + }; + + struct TagComponent { + std::string tag; + int category; + + TagComponent(const std::string& tag = "", int category = 0) + : tag(tag), category(category) {} + + bool operator==(const TagComponent& other) const { + return tag == other.tag && category == other.category; + } + }; + + struct HealthComponent { + int health; + int maxHealth; + + HealthComponent(int health = 100, int maxHealth = 100) + : health(health), maxHealth(maxHealth) {} + + bool operator==(const HealthComponent& other) const { + return health == other.health && maxHealth == other.maxHealth; + } + }; + + // Helper functions to setup component arrays and test entities + class GroupTest : public ::testing::Test { + protected: + // Component arrays + std::shared_ptr> positionArray; + std::shared_ptr> velocityArray; + std::shared_ptr> tagArray; + std::shared_ptr> healthArray; + + // Test entities + std::vector entities; + + void SetUp() override { + // Create component arrays + positionArray = std::make_shared>(); + velocityArray = std::make_shared>(); + tagArray = std::make_shared>(); + healthArray = std::make_shared>(); + + // Initialize with test entities + for (Entity i = 0; i < 5; ++i) { + entities.push_back(i); + + // Add components with test data + positionArray->insert(i, PositionComponent(i * 1.0f, i * 2.0f, i * 3.0f)); + velocityArray->insert(i, VelocityComponent(i * 0.5f, i * 1.0f, i * 1.5f)); + tagArray->insert(i, TagComponent("Entity_" + std::to_string(i), i % 3)); + healthArray->insert(i, HealthComponent(100 - i * 10, 100)); + } + } + + // Helper to create a group with owned and non-owned components + template + auto createGroup(auto nonOwned) { + using OwnedTuple = std::tuple>...>; + using NonOwnedTuple = decltype(std::make_tuple(nonOwned)); + + auto ownedArrays = std::make_tuple(std::static_pointer_cast>(getNthArray())...); + auto nonOwnedArrays = nonOwned; + + return std::make_shared>(ownedArrays, nonOwnedArrays); + } + + // Helper to get component array by type + template + std::shared_ptr getNthArray() { + if constexpr (std::is_same_v) { + return positionArray; + } else if constexpr (std::is_same_v) { + return velocityArray; + } else if constexpr (std::is_same_v) { + return tagArray; + } else if constexpr (std::is_same_v) { + return healthArray; + } else { + static_assert(dependent_false::value, "Unknown component type"); + return nullptr; + } + } + }; + + TEST_F(GroupTest, ConstructorInitializesCorrectly) { + // Create a group with position as owned, velocity as non-owned + auto group = createGroup(std::make_tuple(velocityArray)); + + // Check signatures + auto ownedSignature = group->ownedSignature(); + auto allSignature = group->allSignature(); + + // Owned signature should only have position bit set + EXPECT_TRUE(ownedSignature.test(getComponentTypeID())); + EXPECT_FALSE(ownedSignature.test(getComponentTypeID())); + + // All signature should have both position and velocity bits set + EXPECT_TRUE(allSignature.test(getComponentTypeID())); + EXPECT_TRUE(allSignature.test(getComponentTypeID())); + } + + TEST_F(GroupTest, AddToGroupAddsEntities) { + auto group = createGroup(std::make_tuple(tagArray)); + + // Add a few entities to the group + for (Entity i = 0; i < 3; ++i) { + group->addToGroup(entities[i]); + } + + // Check size + EXPECT_EQ(group->size(), 3); + + // Check entities + auto groupEntities = group->entities(); + EXPECT_EQ(groupEntities.size(), 3); + + // Verify the entities are the ones we added + std::set expectedEntities = {0, 1, 2}; + std::set actualEntities(groupEntities.begin(), groupEntities.end()); + EXPECT_EQ(actualEntities, expectedEntities); + } + + TEST_F(GroupTest, RemoveFromGroupRemovesEntities) { + auto group = createGroup(std::make_tuple(tagArray)); + + // Add all entities to the group + for (Entity i = 0; i < 5; ++i) { + group->addToGroup(entities[i]); + } + + // Check initial size + EXPECT_EQ(group->size(), 5); + + // Remove some entities + group->removeFromGroup(entities[1]); + group->removeFromGroup(entities[3]); + + // Check updated size + EXPECT_EQ(group->size(), 3); + + // Check remaining entities + auto groupEntities = group->entities(); + EXPECT_EQ(groupEntities.size(), 3); + + // Verify the remaining entities + std::set expectedEntities = {0, 2, 4}; + std::set actualEntities(groupEntities.begin(), groupEntities.end()); + EXPECT_EQ(actualEntities, expectedEntities); + } + + TEST_F(GroupTest, GetReturnsSpanOfComponents) { + auto group = createGroup(std::make_tuple(velocityArray)); + + // Add some entities to the group + for (Entity i = 0; i < 3; ++i) { + group->addToGroup(entities[i]); + } + + // Get owned components + auto positions = group->get(); + // Compile-time check: positions should be a span type since it is owned. + static_assert(std::is_same_v>, + "positions should be a span of PositionComponent."); + EXPECT_EQ(positions.size(), 3); + + // Get non-owned components + auto velocities = group->get(); + // Compile-time check: velocities should be a component array type since it is not owned. + static_assert(std::is_same_v>>, + "velocities should be a component array of VelocityComponent."); + + // Check access to component data + for (size_t i = 0; i < 3; ++i) { + EXPECT_FLOAT_EQ(positions[i].x, i * 1.0f); + EXPECT_FLOAT_EQ(positions[i].y, i * 2.0f); + EXPECT_FLOAT_EQ(positions[i].z, i * 3.0f); + } + } + + TEST_F(GroupTest, IteratorBasicFunctionality) { + auto group = createGroup(std::make_tuple(tagArray)); + + // Add entities to the group + for (Entity i = 0; i < 3; ++i) { + group->addToGroup(entities[i]); + } + + // Use iterators to access entities and components + size_t count = 0; + for (auto [entity, position, velocity] : *group) { + EXPECT_TRUE(entity < 3); // Should be one of our first 3 entities + EXPECT_FLOAT_EQ(position.x, entity * 1.0f); + EXPECT_FLOAT_EQ(velocity.vx, entity * 0.5f); + count++; + } + + EXPECT_EQ(count, 3); + } + + TEST_F(GroupTest, IteratorEmptyGroup) { + auto group = createGroup(std::make_tuple(velocityArray)); + + // Iterate - should not execute the loop body + size_t count = 0; + for (auto [entity, position] : *group) { + count++; + } + + EXPECT_EQ(count, 0); + } + + TEST_F(GroupTest, EachMethodCallsFunction) { + auto group = createGroup(std::make_tuple(tagArray)); + + // Add entities to the group + for (Entity i = 0; i < 3; ++i) { + group->addToGroup(entities[i]); + } + + // Use each method to process entities + int callCount = 0; + group->each([&callCount](Entity e, PositionComponent& pos, VelocityComponent& vel, TagComponent& tag) { + EXPECT_EQ(pos.x, e * 1.0f); + EXPECT_EQ(vel.vx, e * 0.5f); + EXPECT_EQ(tag.tag, "Entity_" + std::to_string(e)); + callCount++; + }); + + EXPECT_EQ(callCount, 3); + } + + TEST_F(GroupTest, EachInRangeMethodCallsFunction) { + auto group = createGroup(std::make_tuple(tagArray)); + + // Add entities to the group + for (Entity i = 0; i < 5; ++i) { + group->addToGroup(entities[i]); + } + + // Use eachInRange to process a subset of entities + int callCount = 0; + group->eachInRange(1, 2, [&callCount](Entity e, PositionComponent& pos, VelocityComponent& vel, TagComponent& tag) { + EXPECT_GE(e, 1); + EXPECT_LE(e, 3); + callCount++; + }); + + EXPECT_EQ(callCount, 2); + } + + TEST_F(GroupTest, SortByOwnedComponent) { + auto group = createGroup(std::make_tuple(tagArray)); + + // Add entities to the group in a specific order + group->addToGroup(entities[0]); // health = 100 + group->addToGroup(entities[2]); // health = 80 + group->addToGroup(entities[1]); // health = 90 + group->addToGroup(entities[4]); // health = 60 + group->addToGroup(entities[3]); // health = 70 + + // Sort by health (ascending) + group->sortBy([](const HealthComponent& h) { return h.health; }); + + // Check the new order + auto healthComponents = group->get(); + EXPECT_EQ(healthComponents.size(), 5); + + // Should be in ascending order of health: 60, 70, 80, 90, 100 + EXPECT_EQ(healthComponents[0].health, 60); + EXPECT_EQ(healthComponents[1].health, 70); + EXPECT_EQ(healthComponents[2].health, 80); + EXPECT_EQ(healthComponents[3].health, 90); + EXPECT_EQ(healthComponents[4].health, 100); + + // Sort by health (descending) + group->sortBy( + [](const HealthComponent& h) { return h.health; }, + false // descending + ); + + // Check the new order + healthComponents = group->get(); + + // Should be in descending order of health: 100, 90, 80, 70, 60 + EXPECT_EQ(healthComponents[0].health, 100); + EXPECT_EQ(healthComponents[1].health, 90); + EXPECT_EQ(healthComponents[2].health, 80); + EXPECT_EQ(healthComponents[3].health, 70); + EXPECT_EQ(healthComponents[4].health, 60); + + // Verify sorting invalidated flag is managed correctly + EXPECT_FALSE(group->sortingInvalidated()); + + // Add a new entity to invalidate sorting + group->addToGroup(entities[0]); // This is already in the group, but will still mark as invalidated + EXPECT_TRUE(group->sortingInvalidated()); + } + + TEST_F(GroupTest, SortByNonOwnedComponent) { + auto group = createGroup(std::make_tuple(tagArray, healthArray)); + + // Add entities to the group + for (Entity i = 0; i < 5; ++i) { + group->addToGroup(entities[i]); + } + + // Sort by a non-owned component (health) + group->sortBy([](const HealthComponent& h) { return h.health; }); + + // Check that the sort was successful + auto groupEntities = group->entities(); + EXPECT_EQ(groupEntities[0], 4); // Entity 4 has lowest health (60) + EXPECT_EQ(groupEntities[4], 0); // Entity 0 has highest health (100) + } + + TEST_F(GroupTest, InvalidateSorting) { + auto group = createGroup(std::make_tuple(healthArray)); + + // Add entities to the group + for (Entity i = 0; i < 5; ++i) { + group->addToGroup(entities[i]); + } + + // Sort by health (ascending) + group->sortBy([](const HealthComponent& h) { return h.health; }); + + // Now sort is not invalidated + EXPECT_FALSE(group->sortingInvalidated()); + + // Modify health values directly (this won't invalidate sorting in the Group) + for (int i = 4; i >= 0; --i) { + healthArray->get(i).health = 100 + i * 10; // Reverse the order + } + + group->invalidateSorting(); + + // Sort + group->sortBy([](const HealthComponent& h) { return h.health; }); + + // The original order should still be there, not the new values + auto groupEntities = group->entities(); + EXPECT_EQ(groupEntities[0], 0); + } + + ////////////////////////////////////////////////////////////////////////// + // Partition Tests + ////////////////////////////////////////////////////////////////////////// + + TEST_F(GroupTest, PartitionByComponentField) { + auto group = createGroup(std::make_tuple(healthArray)); + + // Add entities to the group + for (Entity i = 0; i < 5; ++i) { + group->addToGroup(entities[i]); + } + + // Create a partition by category (0, 1, 2) + auto partitionView = group->getPartitionView( + [](const TagComponent& tag) { return tag.category; } + ); + + // Check the number of partitions + EXPECT_EQ(partitionView.partitionCount(), 3); + + // Get partition keys + auto keys = partitionView.getPartitionKeys(); + std::sort(keys.begin(), keys.end()); + EXPECT_EQ(keys[0], 0); + EXPECT_EQ(keys[1], 1); + EXPECT_EQ(keys[2], 2); + + // Check entities in each partition + int countCategory0 = 0; + partitionView.each(0, [&countCategory0](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + EXPECT_EQ(tag.category, 0); + countCategory0++; + }); + EXPECT_EQ(countCategory0, 2); // Entities 0 and 3 have category 0 + + int countCategory1 = 0; + partitionView.each(1, [&countCategory1](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + EXPECT_EQ(tag.category, 1); + countCategory1++; + }); + EXPECT_EQ(countCategory1, 2); // Entities 1 and 4 have category 1 + + int countCategory2 = 0; + partitionView.each(2, [&countCategory2](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + EXPECT_EQ(tag.category, 2); + countCategory2++; + }); + EXPECT_EQ(countCategory2, 1); // Only entity 2 has category 2 + } + + TEST_F(GroupTest, PartitionInvalidation) { + auto group = createGroup(std::make_tuple(healthArray)); + + // Add entities to the group + for (Entity i = 0; i < 5; ++i) { + group->addToGroup(entities[i]); + } + + // Create a partition + auto partitionView = group->getPartitionView( + [](const TagComponent& tag) { return tag.category; } + ); + + // Initial check + EXPECT_EQ(partitionView.partitionCount(), 3); + + // Modify tag category for entity 0 (from 0 to 3) + tagArray->get(0).category = 3; + + group->invalidatePartitions(); + + // Get the view again + auto newView = group->getPartitionView( + [](const TagComponent& tag) { return tag.category; } + ); + + // Should now have 4 partitions (0,1,2,3) + EXPECT_EQ(newView.partitionCount(), 4); + + // Check new partition + auto keys = newView.getPartitionKeys(); + EXPECT_TRUE(std::find(keys.begin(), keys.end(), 3) != keys.end()); + } + + TEST_F(GroupTest, PartitionWithNonExistentKey) { + auto group = createGroup(std::make_tuple(healthArray)); + + // Add entities to the group + for (Entity i = 0; i < 5; ++i) { + group->addToGroup(entities[i]); + } + + // Create a partition + auto partitionView = group->getPartitionView( + [](const TagComponent& tag) { return tag.category; } + ); + + // Try to get a partition with a non-existent key + const auto* partition = partitionView.getPartition(99); + EXPECT_EQ(partition, nullptr); + + // Try to iterate through a non-existent partition + int callCount = 0; + partitionView.each(99, [&callCount](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + callCount++; + }); + + // Should not call the function + EXPECT_EQ(callCount, 0); + } + + TEST_F(GroupTest, EntityPartitionView) { + auto group = createGroup(std::make_tuple(healthArray)); + + // Add entities to the group + for (Entity i = 0; i < 5; ++i) { + group->addToGroup(entities[i]); + } + + // Create a partition based directly on entity IDs + auto partitionView = group->getEntityPartitionView( + "test_partition", + [](Entity e) { return e % 2; } // Partition by even/odd + ); + + // Should have 2 partitions (0 for even, 1 for odd) + EXPECT_EQ(partitionView.partitionCount(), 2); + + // Check each partition + int evenCount = 0; + partitionView.each(0, [&evenCount](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + EXPECT_EQ(e % 2, 0); // Should be even + evenCount++; + }); + EXPECT_EQ(evenCount, 3); // Entities 0, 2, 4 + + int oddCount = 0; + partitionView.each(1, [&oddCount](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + EXPECT_EQ(e % 2, 1); // Should be odd + oddCount++; + }); + EXPECT_EQ(oddCount, 2); // Entities 1, 3 + } + + TEST_F(GroupTest, EmptyGroup) { + auto group = createGroup(std::make_tuple(velocityArray)); + + // Check size + EXPECT_EQ(group->size(), 0); + + // Check entities + auto groupEntities = group->entities(); + EXPECT_EQ(groupEntities.size(), 0); + + // Try using each method + int callCount = 0; + group->each([&callCount](Entity e, PositionComponent& pos, VelocityComponent& vel) { + callCount++; + }); + EXPECT_EQ(callCount, 0); + + // Try sorting - shouldn't crash + group->sortBy([](const PositionComponent& p) { return p.x; }); + + // Try partitioning + auto partitionView = group->getPartitionView( + [](const PositionComponent& p) { return static_cast(p.x); } + ); + EXPECT_EQ(partitionView.partitionCount(), 0); + } + + TEST_F(GroupTest, RemoveNonExistentEntity) { + auto group = createGroup(std::make_tuple(velocityArray)); + + // Add some entities + group->addToGroup(entities[0]); + group->addToGroup(entities[1]); + + // Try removing an entity that's not in the group + group->removeFromGroup(entities[3]); + + // Size should remain unchanged + EXPECT_EQ(group->size(), 2); + } + + TEST_F(GroupTest, AddEntityTwice) { + auto group = createGroup(std::make_tuple(velocityArray)); + + // Add an entity + group->addToGroup(entities[0]); + EXPECT_EQ(group->size(), 1); + + // Add the same entity again + group->addToGroup(entities[0]); + + // Size should remain the same + EXPECT_EQ(group->size(), 1); + } + + TEST_F(GroupTest, ModifyComponentsViaSpan) { + auto group = createGroup(std::make_tuple(velocityArray)); + + // Add entities + group->addToGroup(entities[0]); + group->addToGroup(entities[1]); + + // Get span of position components + auto positions = group->get(); + + // Modify positions through the span + positions[0].x = 100.0f; + positions[1].x = 200.0f; + + // Verify changes were applied to the original component array + EXPECT_FLOAT_EQ(positionArray->get(entities[0]).x, 100.0f); + EXPECT_FLOAT_EQ(positionArray->get(entities[1]).x, 200.0f); + } + + TEST_F(GroupTest, GroupIteratorOutOfBounds) { + auto group = createGroup(std::make_tuple(velocityArray)); + group->addToGroup(entities[0]); + + auto it = group->begin(); + ASSERT_NO_THROW(*it); // First element is valid + + it = group->end(); + ASSERT_THROW(*it, OutOfRange); // Dereferencing end iterator should throw + } +} diff --git a/tests/ecs/GroupSystem.test.cpp b/tests/ecs/GroupSystem.test.cpp new file mode 100644 index 000000000..5f77f0b60 --- /dev/null +++ b/tests/ecs/GroupSystem.test.cpp @@ -0,0 +1,371 @@ +//// GroupSystem.test.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: 09/04/2025 +// Description: Test file for the GroupSystem class +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "GroupSystem.hpp" +#include "Coordinator.hpp" +#include "Access.hpp" +#include "SingletonComponent.hpp" +#include +#include +#include + +namespace nexo::ecs { + + // Define test components + struct Position { + float x, y, z; + + Position(float x = 0.0f, float y = 0.0f, float z = 0.0f) + : x(x), y(y), z(z) {} + + bool operator==(const Position& other) const { + return x == other.x && y == other.y && z == other.z; + } + }; + + struct Velocity { + float vx, vy, vz; + + Velocity(float vx = 0.0f, float vy = 0.0f, float vz = 0.0f) + : vx(vx), vy(vy), vz(vz) {} + + bool operator==(const Velocity& other) const { + return vx == other.vx && vy == other.vy && vz == other.vz; + } + }; + + struct Tag { + std::string name; + int category; + + Tag(const std::string& name = "", int category = 0) + : name(name), category(category) {} + + bool operator==(const Tag& other) const { + return name == other.name && category == other.category; + } + }; + + // Define singleton components + class GameSettings { + public: + bool debugMode = false; + float gameSpeed = 1.0f; + + GameSettings() = default; + GameSettings(bool debug, float speed) : debugMode(debug), gameSpeed(speed) {} + + GameSettings(const GameSettings&) = delete; + GameSettings& operator=(const GameSettings&) = delete; + }; + + // Test fixture + class GroupSystemTest : public ::testing::Test { + protected: + std::shared_ptr coordinator; + std::vector entities; + + void SetUp() override { + // Initialize coordinator + coordinator = std::make_shared(); + coordinator->init(); + System::coord = coordinator; + + // Register components + coordinator->registerComponent(); + coordinator->registerComponent(); + coordinator->registerComponent(); + + // Register singleton + coordinator->registerSingletonComponent(true, 2.0f); + + // Create test entities + for (int i = 0; i < 5; ++i) { + Entity entity = coordinator->createEntity(); + entities.push_back(entity); + + // Add components + coordinator->addComponent(entity, Position(i * 1.0f, i * 2.0f, i * 3.0f)); + coordinator->addComponent(entity, Velocity(i * 0.5f, i * 1.0f, i * 1.5f)); + coordinator->addComponent(entity, Tag("Entity_" + std::to_string(i), i % 3)); + } + } + + void TearDown() override { + // Clean up entities + for (auto entity : entities) { + coordinator->destroyEntity(entity); + } + + // Reset coordinator + System::coord = nullptr; + } + }; + + // Test system classes + + // System with owned Position and read-only access to Velocity + class PositionSystem : public GroupSystem>, NonOwned>> { + public: + void updatePositions() { + auto positions = get(); + auto velocities = get(); + auto entities = getEntities(); + + for (size_t i = 0; i < positions.size(); ++i) { + const auto &vel = velocities->get(entities[i]); + positions[i].x += vel.vx; + positions[i].y += vel.vy; + positions[i].z += vel.vz; + } + } + }; + + // 2. System with read-only access to Position and Tag + class ReadOnlySystem : public GroupSystem>, NonOwned>> { + public: + int countEntitiesAboveThreshold(float threshold) { + auto positions = get(); + auto tags = get(); + int count = 0; + + for (size_t i = 0; i < positions.size(); ++i) { + if (positions[i].x > threshold) { + count++; + } + } + + return count; + } + }; + + // 3. System with singleton component access + class SystemWithSingleton : public GroupSystem< + Owned>, + NonOwned>, + ReadSingleton> { + public: + void scaleVelocities() { + auto positions = get(); + auto velocities = get(); + const auto entities = getEntities(); + const auto& settings = getSingleton(); + + for (size_t i = 0; i < positions.size(); ++i) { + const auto &vel = velocities->get(entities[i]); + positions[i].x += vel.vx * settings.gameSpeed; + positions[i].y += vel.vy * settings.gameSpeed; + positions[i].z += vel.vz * settings.gameSpeed; + } + } + }; + + // 4. System with both read and write access to different components + class MixedAccessSystem : public GroupSystem< + Owned, Read>, + NonOwned>> { + public: + void updatePositionsByCategory(int category, float multiplier) { + auto positions = get(); + auto velocities = get(); + auto tags = get(); + auto entities = getEntities(); + + for (size_t i = 0; i < positions.size(); ++i) { + if (tags[i].category == category) { + positions[i].x += velocities->get(entities[i]).vx * multiplier; + positions[i].y += velocities->get(entities[i]).vy * multiplier; + positions[i].z += velocities->get(entities[i]).vz * multiplier; + } + } + } + }; + + TEST_F(GroupSystemTest, SystemCreation) { + auto system = coordinator->registerGroupSystem(); + ASSERT_NE(system, nullptr); + + // Check static type checking for owned components + EXPECT_TRUE(PositionSystem::isOwnedComponent()); + EXPECT_FALSE(PositionSystem::isOwnedComponent()); + } + + TEST_F(GroupSystemTest, WriteAccessToOwnedComponents) { + auto system = coordinator->registerGroupSystem(); + + // Update positions + system->updatePositions(); + + // Verify changes were applied + for (size_t i = 0; i < entities.size(); ++i) { + Position& pos = coordinator->getComponent(entities[i]); + EXPECT_FLOAT_EQ(pos.x, i * 1.0f + i * 0.5f); + EXPECT_FLOAT_EQ(pos.y, i * 2.0f + i * 1.0f); + EXPECT_FLOAT_EQ(pos.z, i * 3.0f + i * 1.5f); + } + } + + TEST_F(GroupSystemTest, ReadOnlyComponents) { + auto system = coordinator->registerGroupSystem(); + + // Count entities above threshold + int count = system->countEntitiesAboveThreshold(2.0f); + + // Verify count + EXPECT_EQ(count, 2); // Entities 3 and 4 have x > 2.0 + + // This would cause a compilation error if uncommented - verifying access control: + // auto positions = system->get(); + // positions[0].x = 999.0f; // Cannot modify a read-only component + } + + ////////////////////////////////////////////////////////////////////////// + // Singleton Component Tests + ////////////////////////////////////////////////////////////////////////// + + TEST_F(GroupSystemTest, SingletonComponentAccess) { + auto system = coordinator->registerGroupSystem(); + + // Test accessing singleton component + system->scaleVelocities(); + + // Verify changes reflect the game speed setting (2.0) + for (size_t i = 0; i < entities.size(); ++i) { + Position& pos = coordinator->getComponent(entities[i]); + EXPECT_FLOAT_EQ(pos.x, i * 1.0f + i * 0.5f * 2.0f); + EXPECT_FLOAT_EQ(pos.y, i * 2.0f + i * 1.0f * 2.0f); + EXPECT_FLOAT_EQ(pos.z, i * 3.0f + i * 1.5f * 2.0f); + } + + // Update singleton and verify changes are reflected + auto& settings = coordinator->getSingletonComponent(); + settings.gameSpeed = 3.0f; + + // Reset positions + for (size_t i = 0; i < entities.size(); ++i) { + Position& pos = coordinator->getComponent(entities[i]); + pos.x = i * 1.0f; + pos.y = i * 2.0f; + pos.z = i * 3.0f; + } + + system->scaleVelocities(); + + // Verify changes reflect the updated game speed (3.0) + for (size_t i = 0; i < entities.size(); ++i) { + Position& pos = coordinator->getComponent(entities[i]); + EXPECT_FLOAT_EQ(pos.x, i * 1.0f + i * 0.5f * 3.0f); + EXPECT_FLOAT_EQ(pos.y, i * 2.0f + i * 1.0f * 3.0f); + EXPECT_FLOAT_EQ(pos.z, i * 3.0f + i * 1.5f * 3.0f); + } + } + + TEST_F(GroupSystemTest, MixedAccessToComponents) { + auto system = coordinator->registerGroupSystem(); + + // Update positions for category 1 with multiplier 2.0 + system->updatePositionsByCategory(1, 2.0f); + + // Verify changes for entities in category 1 + for (size_t i = 0; i < entities.size(); ++i) { + Position& pos = coordinator->getComponent(entities[i]); + Tag& tag = coordinator->getComponent(entities[i]); + + if (tag.category == 1) { + EXPECT_FLOAT_EQ(pos.x, i * 1.0f + i * 0.5f * 2.0f); + EXPECT_FLOAT_EQ(pos.y, i * 2.0f + i * 1.0f * 2.0f); + EXPECT_FLOAT_EQ(pos.z, i * 3.0f + i * 1.5f * 2.0f); + } else { + EXPECT_FLOAT_EQ(pos.x, i * 1.0f); + EXPECT_FLOAT_EQ(pos.y, i * 2.0f); + EXPECT_FLOAT_EQ(pos.z, i * 3.0f); + } + } + } + + TEST_F(GroupSystemTest, EntityRetrieval) { + auto system = coordinator->registerGroupSystem(); + + // Get entities + auto systemEntities = system->getEntities(); + + // Verify all entities are accessible + EXPECT_EQ(systemEntities.size(), entities.size()); + + // Verify the entities match + std::vector entityVec(systemEntities.begin(), systemEntities.end()); + std::sort(entityVec.begin(), entityVec.end()); + std::sort(entities.begin(), entities.end()); + + for (size_t i = 0; i < entities.size(); ++i) { + EXPECT_EQ(entityVec[i], entities[i]); + } + } + + TEST_F(GroupSystemTest, EntityRemoval) { + auto system = coordinator->registerGroupSystem(); + + // Initial entity count + EXPECT_EQ(system->getEntities().size(), entities.size()); + + // Remove a component from an entity + coordinator->removeComponent(entities[0]); + + // Verify entity was removed from the group + EXPECT_EQ(system->getEntities().size(), entities.size() - 1); + + // Verify the right entity was removed + bool found = false; + for (auto entity : system->getEntities()) { + if (entity == entities[0]) { + found = true; + break; + } + } + EXPECT_FALSE(found); + } + + TEST_F(GroupSystemTest, EmptyGroup) { + auto system = coordinator->registerGroupSystem(); + + // Remove all Position components + for (auto entity : entities) { + coordinator->removeComponent(entity); + } + + // Verify empty group + EXPECT_EQ(system->getEntities().size(), 0); + + // Verify component spans are empty + auto positions = system->get(); + EXPECT_EQ(positions.size(), 0); + + // Test operations on empty group - should not crash + system->updatePositions(); + } + + // Test with system that accesses non-registered component + struct Unregistered { + int value = 0; + }; + + class SystemWithUnregisteredComponent : public GroupSystem>> { + }; + + TEST_F(GroupSystemTest, UnregisteredComponentAccess) { + // This should fail at runtime + EXPECT_THROW(coordinator->registerGroupSystem(), ComponentNotRegistered); + } +} diff --git a/tests/ecs/QuerySystem.test.cpp b/tests/ecs/QuerySystem.test.cpp new file mode 100644 index 000000000..4deb2e667 --- /dev/null +++ b/tests/ecs/QuerySystem.test.cpp @@ -0,0 +1,302 @@ +//// QuerySystem.test.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: 09/04/2025 +// Description: Test file for the QuerySystem class +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "QuerySystem.hpp" +#include "Coordinator.hpp" +#include "Access.hpp" +#include "../utils/comparison.hpp" +#include "SingletonComponent.hpp" +#include +#include +#include + +namespace nexo::ecs { + + // Define test components + struct Position { + float x, y, z; + + Position(float x = 0.0f, float y = 0.0f, float z = 0.0f) + : x(x), y(y), z(z) {} + + bool operator==(const Position& other) const { + return x == other.x && y == other.y && z == other.z; + } + }; + + struct Velocity { + float vx, vy, vz; + + Velocity(float vx = 0.0f, float vy = 0.0f, float vz = 0.0f) + : vx(vx), vy(vy), vz(vz) {} + + bool operator==(const Velocity& other) const { + return vx == other.vx && vy == other.vy && vz == other.vz; + } + }; + + struct Tag { + std::string name; + int category; + + Tag(const std::string& name = "", int category = 0) + : name(name), category(category) {} + + bool operator==(const Tag& other) const { + return name == other.name && category == other.category; + } + }; + + // Define singleton components + class GameSettings { + public: + bool debugMode = false; + float gameSpeed = 1.0f; + + GameSettings() = default; + GameSettings(bool debug, float speed) : debugMode(debug), gameSpeed(speed) {} + + GameSettings(const GameSettings&) = delete; + GameSettings& operator=(const GameSettings&) = delete; + }; + + // Test fixture + class QuerySystemTest : public ::testing::Test { + protected: + std::shared_ptr coordinator; + std::vector entities; + + void SetUp() override { + // Initialize coordinator + coordinator = std::make_shared(); + coordinator->init(); + System::coord = coordinator; + + // Register components + coordinator->registerComponent(); + coordinator->registerComponent(); + coordinator->registerComponent(); + + // Register singleton + coordinator->registerSingletonComponent(true, 2.0f); + + // Create test entities + for (int i = 0; i < 5; ++i) { + Entity entity = coordinator->createEntity(); + entities.push_back(entity); + + // Add components + coordinator->addComponent(entity, Position(i * 1.0f, i * 2.0f, i * 3.0f)); + coordinator->addComponent(entity, Velocity(i * 0.5f, i * 1.0f, i * 1.5f)); + coordinator->addComponent(entity, Tag("Entity_" + std::to_string(i), i % 3)); + } + + // Create an entity with only Position and Tag + Entity posTagEntity = coordinator->createEntity(); + coordinator->addComponent(posTagEntity, Position(10.0f, 20.0f, 30.0f)); + coordinator->addComponent(posTagEntity, Tag("PosTagOnly", 99)); + entities.push_back(posTagEntity); + } + + void TearDown() override { + // Clean up entities + for (auto entity : entities) { + coordinator->destroyEntity(entity); + } + + // Reset coordinator + System::coord = nullptr; + } + }; + + // Test system classes + + // 1. System with read access to Position and write access to Velocity + class MovementSystem : public QuerySystem, Write> { + public: + void applyGravity(float gravity) { + for (Entity entity : entities) { + const Position& pos = getComponent(entity); + Velocity& vel = getComponent(entity); + + // Apply gravity based on height + vel.vy -= gravity * (pos.y / 10.0f); + } + } + }; + + // 2. System with singleton component access + class PhysicsSystem : public QuerySystem< + Read, + Write, + ReadSingleton> { + public: + void updateVelocities() { + const auto& settings = getSingleton(); + + for (Entity entity : entities) { + const Position& pos = getComponent(entity); + Velocity& vel = getComponent(entity); + + // Apply game speed scaling + vel.vx *= settings.gameSpeed; + vel.vy *= settings.gameSpeed; + vel.vz *= settings.gameSpeed; + } + } + }; + + TEST_F(QuerySystemTest, SystemCreation) { + auto system = coordinator->registerQuerySystem(); + ASSERT_NE(system, nullptr); + + // Verify system signature is set correctly + Signature expectedSignature; + expectedSignature.set(coordinator->getComponentType()); + expectedSignature.set(coordinator->getComponentType()); + + EXPECT_EQ(system->getSignature(), expectedSignature); + + // Verify system's entity set gets populated + EXPECT_EQ(system->entities.size(), 5); // Only first 5 entities have Position and Velocity + } + + TEST_F(QuerySystemTest, ComponentAccess) { + auto system = coordinator->registerQuerySystem(); + + // Apply gravity (0.5) + system->applyGravity(0.5f); + + // Verify velocities were modified + for (size_t i = 0; i < 5; ++i) { + Velocity& vel = coordinator->getComponent(entities[i]); + + // Expected: original vy - (gravity * height / 10.0f) + float expectedVy = i * 1.0f - 0.5f * (i * 2.0f / 10.0f); + EXPECT_FLOAT_EQ(vel.vy, expectedVy); + + // vx and vz should be unchanged + EXPECT_FLOAT_EQ(vel.vx, i * 0.5f); + EXPECT_FLOAT_EQ(vel.vz, i * 1.5f); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Singleton Component Tests + ////////////////////////////////////////////////////////////////////////// + + TEST_F(QuerySystemTest, SingletonComponentAccess) { + auto system = coordinator->registerQuerySystem(); + + // Update velocities using game speed from singleton + system->updateVelocities(); + + // Verify velocities were scaled by game speed (2.0) + for (size_t i = 0; i < 5; ++i) { + Velocity& vel = coordinator->getComponent(entities[i]); + + EXPECT_FLOAT_EQ(vel.vx, i * 0.5f * 2.0f); + EXPECT_FLOAT_EQ(vel.vy, i * 1.0f * 2.0f); + EXPECT_FLOAT_EQ(vel.vz, i * 1.5f * 2.0f); + } + + // Update singleton and verify changes are reflected + auto& settings = coordinator->getSingletonComponent(); + settings.gameSpeed = 3.0f; + + // Reset velocities + for (size_t i = 0; i < 5; ++i) { + auto &vel = coordinator->getComponent(entities[i]); + vel.vx = i * 0.5f; + vel.vy = i * 1.0f; + vel.vz = i * 1.5f; + } + + system->updateVelocities(); + + // Verify velocities were scaled by the new game speed (3.0) + for (size_t i = 0; i < 5; ++i) { + Velocity& vel = coordinator->getComponent(entities[i]); + + EXPECT_FLOAT_EQ(vel.vx, i * 0.5f * 3.0f); + EXPECT_FLOAT_EQ(vel.vy, i * 1.0f * 3.0f); + EXPECT_FLOAT_EQ(vel.vz, i * 1.5f * 3.0f); + } + } + + TEST_F(QuerySystemTest, EntityUpdates) { + auto system = coordinator->registerQuerySystem(); + + // Initially system should have 5 entities + EXPECT_EQ(system->entities.size(), 5); + + // Remove a component from an entity + coordinator->removeComponent(entities[0]); + + // Verify entity was removed from the system + EXPECT_EQ(system->entities.size(), 4); + + // Add the component back + coordinator->addComponent(entities[0], Velocity(99.0f, 99.0f, 99.0f)); + + // Verify entity was added back to the system + EXPECT_EQ(system->entities.size(), 5); + + // Apply gravity again and verify it works for all entities + system->applyGravity(1.0f); + + // Check the newly re-added entity + Velocity& vel = coordinator->getComponent(entities[0]); + EXPECT_FLOAT_EQ(vel.vy, 99.0f - 1.0f * (0.0f / 10.0f)); + } + + TEST_F(QuerySystemTest, AccessingMissingComponent) { + auto system = coordinator->registerQuerySystem(); + + // Remove a component + coordinator->removeComponent(entities[0]); + + // Trying to access the removed component should throw + EXPECT_THROW(system->getComponent(entities[0]), InternalError); + } + + TEST_F(QuerySystemTest, EmptySystem) { + // Remove all relevant components + for (size_t i = 0; i < 5; ++i) { + coordinator->removeComponent(entities[i]); + } + + auto system = coordinator->registerQuerySystem(); + + // Verify system has no entities + EXPECT_EQ(system->entities.size(), 0); + + // Operations on empty system should not crash + EXPECT_NO_THROW(system->applyGravity(1.0f)); + } + + // Define a system with non-registered component + struct Unregistered { + int value = 0; + }; + + class SystemWithUnregisteredComponent : public QuerySystem> { + }; + + TEST_F(QuerySystemTest, UnregisteredComponentAccess) { + // Creating system with unregistered component should fail + EXPECT_THROW(coordinator->registerQuerySystem(), ComponentNotRegistered); + } +} diff --git a/tests/ecs/SingletonComponent.test.cpp b/tests/ecs/SingletonComponent.test.cpp index a13ba38f6..25ac4b93b 100644 --- a/tests/ecs/SingletonComponent.test.cpp +++ b/tests/ecs/SingletonComponent.test.cpp @@ -18,260 +18,205 @@ namespace nexo::ecs { -// Test components -struct TestComponent { - int value; - std::string name; + // Test components + struct TestComponent { + int value; + std::string name; + + TestComponent(int v) : value(v), name("default") {} + TestComponent(int v, std::string n) : value(v), name(n) {} + + TestComponent(const TestComponent&) = delete; + TestComponent& operator=(const TestComponent&) = delete; - TestComponent(int v) : value(v), name("default") {} - TestComponent(int v, std::string n) : value(v), name(n) {} -}; + }; + + struct ComplexComponent { + std::vector data; + bool flag; -struct ComplexComponent { - std::vector data; - bool flag; + ComplexComponent() : flag(false) {} + ComplexComponent(const std::vector& d, bool f) : data(d), flag(f) {} + + ComplexComponent(const ComplexComponent&) = delete; + ComplexComponent& operator=(const ComplexComponent&) = delete; + }; + + class SingletonComponentTest : public ::testing::Test {}; - ComplexComponent() : flag(false) {} - ComplexComponent(const std::vector& d, bool f) : data(d), flag(f) {} -}; + class SingletonComponentManagerTest : public ::testing::Test { + protected: + void SetUp() override { + manager = std::make_unique(); + } + + std::unique_ptr manager; + }; -class SingletonComponentTest : public ::testing::Test {}; - -class SingletonComponentManagerTest : public ::testing::Test { -protected: - void SetUp() override { - manager = std::make_unique(); - } - - std::unique_ptr manager; -}; - -// SingletonComponent Tests - -TEST_F(SingletonComponentTest, ConstructWithSingleArgument) { - SingletonComponent component(42); - EXPECT_EQ(component.getInstance().value, 42); - EXPECT_EQ(component.getInstance().name, "default"); -} - -TEST_F(SingletonComponentTest, ConstructWithMultipleArguments) { - SingletonComponent component(42, "test"); - EXPECT_EQ(component.getInstance().value, 42); - EXPECT_EQ(component.getInstance().name, "test"); -} - -TEST_F(SingletonComponentTest, GetInstanceReturnsReference) { - SingletonComponent component(42); - TestComponent& instance = component.getInstance(); - - // Modify through reference - instance.value = 100; - instance.name = "modified"; - - // Verify changes - EXPECT_EQ(component.getInstance().value, 100); - EXPECT_EQ(component.getInstance().name, "modified"); -} - -TEST_F(SingletonComponentTest, ComplexComponentConstruction) { - std::vector testData = {1, 2, 3, 4, 5}; - SingletonComponent component(testData, true); - - EXPECT_EQ(component.getInstance().data, testData); - EXPECT_TRUE(component.getInstance().flag); -} - -TEST_F(SingletonComponentTest, DefaultConstructible) { - SingletonComponent component; - EXPECT_TRUE(component.getInstance().data.empty()); - EXPECT_FALSE(component.getInstance().flag); -} - -// SingletonComponentManager Tests - -TEST_F(SingletonComponentManagerTest, RegisterAndGetSingletonComponent) { - manager->registerSingletonComponent(42); - - TestComponent& component = manager->getSingletonComponent(); - EXPECT_EQ(component.value, 42); - EXPECT_EQ(component.name, "default"); -} - -TEST_F(SingletonComponentManagerTest, RegisterWithMultipleArguments) { - manager->registerSingletonComponent(42, "test"); - - TestComponent& component = manager->getSingletonComponent(); - EXPECT_EQ(component.value, 42); - EXPECT_EQ(component.name, "test"); -} - -TEST_F(SingletonComponentManagerTest, GetNonexistentComponent) { - EXPECT_THROW(manager->getSingletonComponent(), SingletonComponentNotRegistered); -} - -TEST_F(SingletonComponentManagerTest, UnregisterComponent) { - // Register - manager->registerSingletonComponent(42); - EXPECT_NO_THROW(manager->getSingletonComponent()); - - // Unregister - manager->unregisterSingletonComponent(); - - // Should now throw - EXPECT_THROW(manager->getSingletonComponent(), SingletonComponentNotRegistered); -} - -TEST_F(SingletonComponentManagerTest, UnregisterNonexistentComponent) { - EXPECT_THROW(manager->unregisterSingletonComponent(), SingletonComponentNotRegistered); -} - -TEST_F(SingletonComponentManagerTest, RegisterSameComponentTwice) { - // First registration - manager->registerSingletonComponent(42); - - // Second registration should log warning but not throw - EXPECT_NO_THROW(manager->registerSingletonComponent(100)); - - // Should still have the original value - TestComponent& component = manager->getSingletonComponent(); - EXPECT_EQ(component.value, 42); -} - -TEST_F(SingletonComponentManagerTest, RegisterMultipleComponentTypes) { - manager->registerSingletonComponent(42); - manager->registerSingletonComponent(std::vector{1, 2, 3}, true); - - // Both should be retrievable - TestComponent& comp1 = manager->getSingletonComponent(); - ComplexComponent& comp2 = manager->getSingletonComponent(); - - EXPECT_EQ(comp1.value, 42); - EXPECT_EQ(comp2.data, std::vector({1, 2, 3})); - EXPECT_TRUE(comp2.flag); -} - -TEST_F(SingletonComponentManagerTest, ModifyComponent) { - manager->registerSingletonComponent(42); - - // Get and modify - TestComponent& component = manager->getSingletonComponent(); - component.value = 100; - component.name = "modified"; - - // Should reflect changes when retrieved again - TestComponent& retrieved = manager->getSingletonComponent(); - EXPECT_EQ(retrieved.value, 100); - EXPECT_EQ(retrieved.name, "modified"); -} - -TEST_F(SingletonComponentManagerTest, RegisterAfterUnregister) { - // Register - manager->registerSingletonComponent(42); - - // Unregister - manager->unregisterSingletonComponent(); - - // Register again with different value - manager->registerSingletonComponent(100); - - // Should have new value - TestComponent& component = manager->getSingletonComponent(); - EXPECT_EQ(component.value, 100); -} - -TEST_F(SingletonComponentManagerTest, ComplexComponentCycle) { - // Register complex component - std::vector originalData = {1, 2, 3}; - manager->registerSingletonComponent(originalData, true); - - // Get and modify - ComplexComponent& comp = manager->getSingletonComponent(); - comp.data.push_back(4); - comp.flag = false; - - // Verify modifications - ComplexComponent& modified = manager->getSingletonComponent(); - std::vector expectedData = {1, 2, 3, 4}; - EXPECT_EQ(modified.data, expectedData); - EXPECT_FALSE(modified.flag); - - // Unregister - manager->unregisterSingletonComponent(); - - // Register new instance - std::vector newData = {5, 6, 7}; - manager->registerSingletonComponent(newData, true); - - // Verify new instance - ComplexComponent& newComp = manager->getSingletonComponent(); - EXPECT_EQ(newComp.data, newData); - EXPECT_TRUE(newComp.flag); -} - -// Test edge cases - -TEST_F(SingletonComponentManagerTest, EmptyComponent) { - struct EmptyComponent {}; - - manager->registerSingletonComponent(); - EXPECT_NO_THROW(manager->getSingletonComponent()); -} - -TEST_F(SingletonComponentManagerTest, NonDefaultConstructibleComponent) { - struct NonDefaultComponent { - int value; - explicit NonDefaultComponent(int v) : value(v) {} - NonDefaultComponent() = delete; - }; - - manager->registerSingletonComponent(42); - EXPECT_EQ(manager->getSingletonComponent().value, 42); -} - -TEST_F(SingletonComponentManagerTest, MoveOnlyComponent) { - struct MoveOnlyComponent { - std::unique_ptr ptr; - - explicit MoveOnlyComponent(int v) : ptr(std::make_unique(v)) {} - MoveOnlyComponent(const MoveOnlyComponent&) = delete; - MoveOnlyComponent& operator=(const MoveOnlyComponent&) = delete; - MoveOnlyComponent(MoveOnlyComponent&&) = default; - MoveOnlyComponent& operator=(MoveOnlyComponent&&) = default; - }; - - manager->registerSingletonComponent(42); - EXPECT_EQ(*manager->getSingletonComponent().ptr, 42); -} - -TEST_F(SingletonComponentManagerTest, MultipleUnregistrations) { - manager->registerSingletonComponent(42); - manager->unregisterSingletonComponent(); - - // Second unregistration should throw - EXPECT_THROW(manager->unregisterSingletonComponent(), SingletonComponentNotRegistered); -} - -class InheritanceTestBase { -public: - virtual ~InheritanceTestBase() = default; - int baseValue = 0; -}; - -class InheritanceTestDerived : public InheritanceTestBase { -public: - InheritanceTestDerived(int base, int derived) { - baseValue = base; - derivedValue = derived; - } - int derivedValue = 0; -}; - -TEST_F(SingletonComponentManagerTest, InheritedComponent) { - manager->registerSingletonComponent(10, 20); - - InheritanceTestDerived& component = manager->getSingletonComponent(); - EXPECT_EQ(component.baseValue, 10); - EXPECT_EQ(component.derivedValue, 20); -} + TEST_F(SingletonComponentTest, ConstructWithSingleArgument) { + SingletonComponent component(42); + EXPECT_EQ(component.getInstance().value, 42); + EXPECT_EQ(component.getInstance().name, "default"); + } + + TEST_F(SingletonComponentTest, ConstructWithMultipleArguments) { + SingletonComponent component(42, "test"); + EXPECT_EQ(component.getInstance().value, 42); + EXPECT_EQ(component.getInstance().name, "test"); + } + + TEST_F(SingletonComponentTest, GetInstanceReturnsReference) { + SingletonComponent component(42); + TestComponent& instance = component.getInstance(); + + // Modify through reference + instance.value = 100; + instance.name = "modified"; + + // Verify changes + EXPECT_EQ(component.getInstance().value, 100); + EXPECT_EQ(component.getInstance().name, "modified"); + } + + TEST_F(SingletonComponentTest, ComplexComponentConstruction) { + std::vector testData = {1, 2, 3, 4, 5}; + SingletonComponent component(testData, true); + + EXPECT_EQ(component.getInstance().data, testData); + EXPECT_TRUE(component.getInstance().flag); + } + + TEST_F(SingletonComponentTest, DefaultConstructible) { + SingletonComponent component; + EXPECT_TRUE(component.getInstance().data.empty()); + EXPECT_FALSE(component.getInstance().flag); + } + + TEST_F(SingletonComponentManagerTest, RegisterAndGetSingletonComponent) { + manager->registerSingletonComponent(42); + + TestComponent& component = manager->getSingletonComponent(); + EXPECT_EQ(component.value, 42); + EXPECT_EQ(component.name, "default"); + } + + TEST_F(SingletonComponentManagerTest, RegisterWithMultipleArguments) { + manager->registerSingletonComponent(42, "test"); + + TestComponent& component = manager->getSingletonComponent(); + EXPECT_EQ(component.value, 42); + EXPECT_EQ(component.name, "test"); + } + + TEST_F(SingletonComponentManagerTest, GetNonexistentComponent) { + EXPECT_THROW(manager->getSingletonComponent(), SingletonComponentNotRegistered); + } + + TEST_F(SingletonComponentManagerTest, UnregisterComponent) { + // Register + manager->registerSingletonComponent(42); + EXPECT_NO_THROW(manager->getSingletonComponent()); + + // Unregister + manager->unregisterSingletonComponent(); + + // Should now throw + EXPECT_THROW(manager->getSingletonComponent(), SingletonComponentNotRegistered); + } + + TEST_F(SingletonComponentManagerTest, UnregisterNonexistentComponent) { + EXPECT_THROW(manager->unregisterSingletonComponent(), SingletonComponentNotRegistered); + } + + TEST_F(SingletonComponentManagerTest, RegisterSameComponentTwice) { + // First registration + manager->registerSingletonComponent(42); + + // Second registration should log warning but not throw + EXPECT_NO_THROW(manager->registerSingletonComponent(100)); + + // Should still have the original value + TestComponent& component = manager->getSingletonComponent(); + EXPECT_EQ(component.value, 42); + } + + TEST_F(SingletonComponentManagerTest, RegisterMultipleComponentTypes) { + manager->registerSingletonComponent(42); + manager->registerSingletonComponent(std::vector{1, 2, 3}, true); + + // Both should be retrievable + TestComponent& comp1 = manager->getSingletonComponent(); + ComplexComponent& comp2 = manager->getSingletonComponent(); + + EXPECT_EQ(comp1.value, 42); + EXPECT_EQ(comp2.data, std::vector({1, 2, 3})); + EXPECT_TRUE(comp2.flag); + } + + TEST_F(SingletonComponentManagerTest, ModifyComponent) { + manager->registerSingletonComponent(42); + + // Get and modify + TestComponent& component = manager->getSingletonComponent(); + component.value = 100; + component.name = "modified"; + + // Should reflect changes when retrieved again + TestComponent& retrieved = manager->getSingletonComponent(); + EXPECT_EQ(retrieved.value, 100); + EXPECT_EQ(retrieved.name, "modified"); + } + + TEST_F(SingletonComponentManagerTest, RegisterAfterUnregister) { + // Register + manager->registerSingletonComponent(42); + + // Unregister + manager->unregisterSingletonComponent(); + + // Register again with different value + manager->registerSingletonComponent(100); + + // Should have new value + TestComponent& component = manager->getSingletonComponent(); + EXPECT_EQ(component.value, 100); + } + + TEST_F(SingletonComponentManagerTest, ComplexComponentCycle) { + // Register complex component + std::vector originalData = {1, 2, 3}; + manager->registerSingletonComponent(originalData, true); + + // Get and modify + ComplexComponent& comp = manager->getSingletonComponent(); + comp.data.push_back(4); + comp.flag = false; + + // Verify modifications + ComplexComponent& modified = manager->getSingletonComponent(); + std::vector expectedData = {1, 2, 3, 4}; + EXPECT_EQ(modified.data, expectedData); + EXPECT_FALSE(modified.flag); + + // Unregister + manager->unregisterSingletonComponent(); + + // Register new instance + std::vector newData = {5, 6, 7}; + manager->registerSingletonComponent(newData, true); + + // Verify new instance + ComplexComponent& newComp = manager->getSingletonComponent(); + EXPECT_EQ(newComp.data, newData); + EXPECT_TRUE(newComp.flag); + } + + TEST_F(SingletonComponentManagerTest, MultipleUnregistrations) { + manager->registerSingletonComponent(42); + manager->unregisterSingletonComponent(); + + // Second unregistration should throw + EXPECT_THROW(manager->unregisterSingletonComponent(), SingletonComponentNotRegistered); + } } diff --git a/tests/ecs/System.test.cpp b/tests/ecs/System.test.cpp index 750930a38..95278c899 100644 --- a/tests/ecs/System.test.cpp +++ b/tests/ecs/System.test.cpp @@ -1,4 +1,4 @@ -//// System.test.cpp ////////////////////////////////////////////////////////// +//// System.test.cpp /////////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -7,195 +7,215 @@ // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // // Author: Mehdy MORVAN -// Date: 26/11/2024 +// Date: 09/04/2025 // Description: Test file for the system manager // /////////////////////////////////////////////////////////////////////////////// - #include -#include -#include "ecs/System.hpp" -#include "ecs/Definitions.hpp" +#include "System.hpp" +#include "Coordinator.hpp" namespace nexo::ecs { - // Mock system for testing - class MockSystem : public System { - public: - MOCK_METHOD(void, update, (), (const)); - }; - - class MockSystem2 : public System { - public: - MOCK_METHOD(void, update, (), (const)); - }; + class MockCoordinator : public Coordinator {}; - class SystemManagerTest : public ::testing::Test { - protected: - void SetUp() override { - systemManager = std::make_unique(); + // Mock systems for testing + class MockQuerySystem : public AQuerySystem { + public: + const Signature& getSignature() const override { + return signature; } - std::unique_ptr systemManager; + Signature signature; }; - TEST_F(SystemManagerTest, RegisterSystem) { - auto mockSystem = systemManager->registerSystem(); - - EXPECT_NE(mockSystem, nullptr); - EXPECT_TRUE(mockSystem->entities.empty()); - } + class MockGroupSystem : public AGroupSystem {}; - TEST_F(SystemManagerTest, RegisterSystemTwice) { - systemManager->registerSystem(); + // Invalid system that doesn't inherit correctly + class InvalidSystem {}; - // Attempting to register the same system twice - EXPECT_NO_THROW(systemManager->registerSystem()); - } - - TEST_F(SystemManagerTest, SetSignatureForSystem) { - auto mockSystem = systemManager->registerSystem(); - - Signature signature; - signature.set(1); // Set the 1st bit + class SystemTest : public ::testing::Test { + protected: + void SetUp() override { + // Initialize the static coordinator + System::coord = std::make_shared(); + } - EXPECT_NO_THROW(systemManager->setSignature(signature)); - } + void TearDown() override { + // Clean up + System::coord.reset(); + } - TEST_F(SystemManagerTest, SetSignatureForUnregisteredSystem) { - Signature signature; - signature.set(1); + SystemManager systemManager; + }; - EXPECT_THROW(systemManager->setSignature(signature), SystemNotRegistered); + // System Base Class Tests + TEST_F(SystemTest, CoordinatorInitialization) { + ASSERT_NE(System::coord, nullptr); } - TEST_F(SystemManagerTest, EntityDestroyed) { - auto mockSystem = systemManager->registerSystem(); - - Signature signature; - signature.set(1); // Set the 1st bit - systemManager->setSignature(signature); - - // Simulate an entity with the matching signature - Entity entity = 1; - mockSystem->entities.insert(entity); + // AQuerySystem Tests + TEST_F(SystemTest, QuerySystemSignature) { + auto mockSystem = std::make_shared(); - systemManager->entityDestroyed(entity, signature); + // Test initial signature + Signature emptySignature; + EXPECT_EQ(mockSystem->getSignature(), emptySignature); - EXPECT_TRUE(mockSystem->entities.empty()); + // Test modified signature + Signature newSignature; + newSignature.set(1, true); // Set some bit + mockSystem->signature = newSignature; + EXPECT_EQ(mockSystem->getSignature(), newSignature); } - TEST_F(SystemManagerTest, EntitySignatureChanged_AddEntityToSystem) { - auto mockSystem = systemManager->registerSystem(); - - Signature systemSignature; - systemSignature.set(1); // Set the 1st bit - systemManager->setSignature(systemSignature); + TEST_F(SystemTest, QuerySystemEntities) { + auto mockSystem = std::make_shared(); - Entity entity = 1; - Signature entitySignature; - Signature oldSignature; - entitySignature.set(1); // Matches the system's signature + // Test initial state + EXPECT_TRUE(mockSystem->entities.empty()); - systemManager->entitySignatureChanged(entity, oldSignature, entitySignature); + // Test adding entities + Entity entity1 = 1; + mockSystem->entities.insert(entity1); + EXPECT_EQ(mockSystem->entities.size(), 1); + EXPECT_TRUE(mockSystem->entities.contains(entity1)); - EXPECT_TRUE(mockSystem->entities.contains(entity)); + // Test adding more entities + Entity entity2 = 2; + mockSystem->entities.insert(entity2); + EXPECT_EQ(mockSystem->entities.size(), 2); + EXPECT_TRUE(mockSystem->entities.contains(entity2)); + + // Test removing entities + mockSystem->entities.erase(entity1); + EXPECT_EQ(mockSystem->entities.size(), 1); + EXPECT_FALSE(mockSystem->entities.contains(entity1)); + EXPECT_TRUE(mockSystem->entities.contains(entity2)); } - TEST_F(SystemManagerTest, EntitySignatureChanged_RemoveEntityFromSystem) { - auto mockSystem = systemManager->registerSystem(); + // SystemManager Tests + TEST_F(SystemTest, RegisterQuerySystem) { + // Register a system + auto system = systemManager.registerQuerySystem(); + ASSERT_NE(system, nullptr); - Signature systemSignature; - systemSignature.set(1); // Set the 1st bit - systemManager->setSignature(systemSignature); - - Entity entity = 1; - Signature entitySignature; - Signature oldSignature; - entitySignature.set(1); // Matches the system's signature - - // Add the entity - systemManager->entitySignatureChanged(entity, oldSignature, entitySignature); - EXPECT_TRUE(mockSystem->entities.contains(entity)); - - // Change signature to no longer match - oldSignature = entitySignature; - entitySignature.reset(1); // Clear the 1st bit - systemManager->entitySignatureChanged(entity, oldSignature, entitySignature); - - EXPECT_FALSE(mockSystem->entities.contains(entity)); + // Try to register the same system again + auto duplicateSystem = systemManager.registerQuerySystem(); + EXPECT_EQ(duplicateSystem, nullptr); } - TEST_F(SystemManagerTest, EntitySignatureChanged_IrrelevantEntity) { - auto mockSystem = systemManager->registerSystem(); + TEST_F(SystemTest, RegisterGroupSystem) { + // Register a system + auto system = systemManager.registerGroupSystem(); + ASSERT_NE(system, nullptr); - Signature systemSignature; - systemSignature.set(1); // Set the 1st bit - systemManager->setSignature(systemSignature); + // Try to register the same system again + auto duplicateSystem = systemManager.registerGroupSystem(); + EXPECT_EQ(duplicateSystem, nullptr); + } - Entity entity = 1; - Signature entitySignature; - Signature oldSignature; - entitySignature.set(2); // Does not match the system's signature + class SystemImplementationTest : public ::testing::Test { + protected: + void SetUp() override { + // Setup code + nexo::ecs::System::coord = std::make_shared(); - systemManager->entitySignatureChanged(entity, oldSignature, entitySignature); + // Register systems + querySystem = systemManager.registerQuerySystem(); + groupSystem = systemManager.registerGroupSystem(); - EXPECT_FALSE(mockSystem->entities.contains(entity)); - } + // Set signatures + querySignature.set(0, true); // System requires component 0 + querySystem->signature = querySignature; + systemManager.setSignature(querySignature); + } - TEST_F(SystemManagerTest, EntityDestroyedTwice) { - auto mockSystem = systemManager->registerSystem(); + void TearDown() override { + nexo::ecs::System::coord.reset(); + } - Signature signature; - signature.set(1); // Set the 1st bit - systemManager->setSignature(signature); + nexo::ecs::SystemManager systemManager; + std::shared_ptr querySystem; + std::shared_ptr groupSystem; + nexo::ecs::Signature querySignature; + }; - Entity entity = 1; - Signature entitySignature; - entitySignature.set(1); - mockSystem->entities.insert(entity); + TEST_F(SystemImplementationTest, EntityDestroyedRemovesFromAllSystems) { + // Add entity to the system + nexo::ecs::Entity entity = 1; + querySystem->entities.insert(entity); - systemManager->entityDestroyed(entity, entitySignature); - EXPECT_TRUE(mockSystem->entities.empty()); + // Destroy entity + systemManager.entityDestroyed(entity, querySignature); - // Destroying the same entity again should not throw an error - EXPECT_NO_THROW(systemManager->entityDestroyed(entity, entitySignature)); + // Verify entity was removed + EXPECT_FALSE(querySystem->entities.contains(entity)); } - TEST_F(SystemManagerTest, AddAndRemoveEntitiesFromMultipleSystems) { - auto system1 = systemManager->registerSystem(); - auto system2 = systemManager->registerSystem(); + TEST_F(SystemImplementationTest, EntitySignatureChangedAddsToMatchingSystems) { + nexo::ecs::Entity entity = 1; + nexo::ecs::Signature oldSignature; // Empty + nexo::ecs::Signature newSignature; + newSignature.set(0, true); // Now matches querySystem - Signature signature1; - signature1.set(1); // Matches entities with component 1 - systemManager->setSignature(signature1); + // Initially not in system + ASSERT_FALSE(querySystem->entities.contains(entity)); - Signature signature2; - signature2.set(2); // Matches entities with component 2 - systemManager->setSignature(signature2); + // Change signature + systemManager.entitySignatureChanged(entity, oldSignature, newSignature); - // Add entities with different signatures - Entity entity1 = 1; - Entity entity2 = 2; + // Should be added to system + EXPECT_TRUE(querySystem->entities.contains(entity)); + } - Signature entitySignature1; - Signature oldSignature1; - entitySignature1.set(1); // Matches system1 + TEST_F(SystemImplementationTest, EntitySignatureChangedRemovesFromNonMatchingSystems) { + nexo::ecs::Entity entity = 1; + nexo::ecs::Signature oldSignature; + oldSignature.set(0, true); // Initially matches querySystem + nexo::ecs::Signature newSignature; // Empty, no longer matches - Signature entitySignature2; - Signature oldSignature2; - entitySignature2.set(2); // Matches system2 + // Add to system + querySystem->entities.insert(entity); - systemManager->entitySignatureChanged(entity1, oldSignature1, entitySignature1); - systemManager->entitySignatureChanged(entity2, oldSignature2, entitySignature2); + // Change signature + systemManager.entitySignatureChanged(entity, oldSignature, newSignature); - EXPECT_TRUE(system1->entities.contains(entity1)); - EXPECT_FALSE(system1->entities.contains(entity2)); - EXPECT_FALSE(system2->entities.contains(entity1)); - EXPECT_TRUE(system2->entities.contains(entity2)); + // Should be removed from system + EXPECT_FALSE(querySystem->entities.contains(entity)); + } - // Change signature of entity1 to match system2 - systemManager->entitySignatureChanged(entity1, entitySignature1, entitySignature2); - EXPECT_FALSE(system1->entities.contains(entity1)); - EXPECT_TRUE(system2->entities.contains(entity1)); + TEST_F(SystemImplementationTest, EntitySignatureChangedHandlesMultipleSystems) { + // Register another system with different signature + class AnotherMockQuerySystem : public AQuerySystem { + public: + const Signature& getSignature() const override { + return signature; + } + + Signature signature; + }; + auto otherSystem = systemManager.registerQuerySystem(); + nexo::ecs::Signature otherSignature; + otherSignature.set(1, true); // This system requires component 1 + otherSystem->signature = otherSignature; + + nexo::ecs::Entity entity = 1; + nexo::ecs::Signature oldSignature; + oldSignature.set(0, true); // Matches only querySystem + + nexo::ecs::Signature newSignature; + newSignature.set(1, true); // Matches only otherSystem + + // Add to first system + querySystem->entities.insert(entity); + ASSERT_TRUE(querySystem->entities.contains(entity)); + ASSERT_FALSE(otherSystem->entities.contains(entity)); + + // Change signature + systemManager.entitySignatureChanged(entity, oldSignature, newSignature); + + // Should move from first to second system + EXPECT_FALSE(querySystem->entities.contains(entity)); + EXPECT_TRUE(otherSystem->entities.contains(entity)); } } From 271dc1f05f1214200b6b2bf3c5ec3ecd3e134171 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 9 Apr 2025 21:33:33 +0900 Subject: [PATCH 053/450] chore(ecs-opti): remove examples from coverage and sonar checker --- sonar-project.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index d14439098..5c94fd60d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,11 +3,11 @@ sonar.organization=nexoengine sonar.projectVersion=1.0 # Path to sources (source files for coverage and analysis) -sonar.sources=engine/,editor/,common/,examples/ +sonar.sources=engine/,editor/,common/ sonar.sourceEncoding=UTF-8 # Exclude these folders from coverage calculations -sonar.coverage.exclusions=**/editor/**, **/tests/** +sonar.coverage.exclusions=**/editor/**, **/tests/**, **/examples/** # Path to tests (for test file classification, not used in coverage by default) sonar.tests=tests/ From 85be530abfda01032c1c22e3183fea1084acc7b3 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 9 Apr 2025 21:33:48 +0900 Subject: [PATCH 054/450] tests(ecs-opti): fix component array test for windows --- tests/ecs/ComponentArray.test.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/ecs/ComponentArray.test.cpp b/tests/ecs/ComponentArray.test.cpp index d3c4a6489..bcab62358 100644 --- a/tests/ecs/ComponentArray.test.cpp +++ b/tests/ecs/ComponentArray.test.cpp @@ -49,7 +49,7 @@ namespace nexo::ecs { TEST_F(ComponentArrayTest, InsertAddsComponentCorrectly) { const Entity testEntity = 10; - const TestComponent testComponent(100); + const TestComponent testComponent{100}; componentArray->insert(testEntity, testComponent); @@ -60,7 +60,7 @@ namespace nexo::ecs { TEST_F(ComponentArrayTest, InsertDuplicateEntityIsIgnored) { const Entity testEntity = 1; // Already exists from setup - const TestComponent newComponent(999); + const TestComponent newComponent{999}; // Original component should be preserved TestComponent originalComponent = componentArray->get(testEntity); @@ -264,7 +264,9 @@ namespace nexo::ecs { TEST_F(ComponentArrayTest, ArrayShrinksWhenManyElementsRemoved) { // First, add more elements to increase capacity for (Entity i = 5; i < 20; ++i) { - componentArray->insert(i, TestComponent(i * 10)); + TestComponent newComp; + newComp.value = i * 10; + componentArray->insert(i, newComp); } size_t largeSize = componentArray->size(); @@ -282,9 +284,9 @@ namespace nexo::ecs { TEST_F(ComponentArrayTest, HandlesSparseEntityDistribution) { // Insert components with large gaps between entity IDs - componentArray->insert(100, TestComponent(100)); - componentArray->insert(1000, TestComponent(1000)); - componentArray->insert(10000, TestComponent(10000)); + componentArray->insert(100, TestComponent{100}); + componentArray->insert(1000, TestComponent{1000}); + componentArray->insert(10000, TestComponent{10000}); // Verify components were stored correctly EXPECT_TRUE(componentArray->hasComponent(100)); @@ -324,8 +326,8 @@ namespace nexo::ecs { EXPECT_EQ(componentArray->groupSize(), 1); // Add new entities - componentArray->insert(6, TestComponent(60)); - componentArray->insert(7, TestComponent(70)); + componentArray->insert(6, TestComponent{60}); + componentArray->insert(7, TestComponent{70}); EXPECT_EQ(componentArray->size(), 5); // Destroy an entity via entityDestroyed From 28ec3d09bb3dc6ed6663520e593bdd79a693712f Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 9 Apr 2025 21:45:54 +0900 Subject: [PATCH 055/450] fix(ecs-opti): removed alignas since it seems to cause problems on windows --- engine/src/ecs/ComponentArray.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 2bc08ea5a..8d869d5b5 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -68,7 +68,7 @@ namespace nexo::ecs { */ template= 1)>> - class alignas(64) ComponentArray : public IComponentArray { + class ComponentArray : public IComponentArray { public: /** * @brief Type alias for the component type From d93314d487335e60493151d0be099be8cfe4e992 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 9 Apr 2025 21:57:06 +0900 Subject: [PATCH 056/450] fix(ecs-opti): removed forwarding component in insert --- engine/src/ecs/ComponentArray.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 8d869d5b5..dd1b07b92 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -126,8 +126,7 @@ namespace nexo::ecs { * * @pre The entity must be a valid entity ID */ - template - void insert(Entity entity, U&& component) + void insert(Entity entity, T component) { if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); @@ -143,7 +142,7 @@ namespace nexo::ecs { const size_t newIndex = m_size; m_sparse[entity] = newIndex; m_dense.push_back(entity); - m_componentArray.push_back(std::forward(component)); + m_componentArray.push_back(component); ++m_size; } From 94bd5b645763415156fddd24b310e19f4cfd26ad Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 9 Apr 2025 23:04:29 +0900 Subject: [PATCH 057/450] fix(ecs-opti): maybe fix for windows --- engine/src/ecs/ComponentArray.hpp | 31 ++----------------------------- tests/ecs/ComponentArray.test.cpp | 3 --- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index dd1b07b92..11b3f2975 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -68,7 +68,7 @@ namespace nexo::ecs { */ template= 1)>> - class ComponentArray : public IComponentArray { + class alignas(64) ComponentArray : public IComponentArray { public: /** * @brief Type alias for the component type @@ -88,34 +88,6 @@ namespace nexo::ecs { m_componentArray.reserve(capacity); } - // Move constructor - ComponentArray(ComponentArray&& other) noexcept - : m_componentArray(std::move(other.m_componentArray)) - , m_sparse(std::move(other.m_sparse)) - , m_dense(std::move(other.m_dense)) - , m_size(other.m_size) - , m_groupSize(other.m_groupSize) - { - other.m_size = 0; - other.m_groupSize = 0; - } - - // Move assignment - ComponentArray& operator=(ComponentArray&& other) noexcept - { - if (this != &other) { - m_componentArray = std::move(other.m_componentArray); - m_sparse = std::move(other.m_sparse); - m_dense = std::move(other.m_dense); - m_size = other.m_size; - m_groupSize = other.m_groupSize; - - other.m_size = 0; - other.m_groupSize = 0; - } - return *this; - } - /** * @brief Inserts a new component for the given entity. * @@ -143,6 +115,7 @@ namespace nexo::ecs { m_sparse[entity] = newIndex; m_dense.push_back(entity); m_componentArray.push_back(component); + ++m_size; } diff --git a/tests/ecs/ComponentArray.test.cpp b/tests/ecs/ComponentArray.test.cpp index bcab62358..5831fac91 100644 --- a/tests/ecs/ComponentArray.test.cpp +++ b/tests/ecs/ComponentArray.test.cpp @@ -21,9 +21,6 @@ namespace nexo::ecs { struct TestComponent { int value; - TestComponent() = default; - TestComponent(int v) : value(v) {} - bool operator==(const TestComponent& other) const { return value == other.value; } From 7d43d6119db80de1d45a8ff0b6d432f13834611e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 10 Apr 2025 01:49:58 +0900 Subject: [PATCH 058/450] fix(ecs-opti): mostly some minor style fixes and warnings --- engine/src/ecs/ComponentArray.hpp | 71 +- engine/src/ecs/Components.cpp | 16 +- engine/src/ecs/Components.hpp | 75 +- engine/src/ecs/Coordinator.cpp | 2 +- engine/src/ecs/Coordinator.hpp | 63 +- engine/src/ecs/ECSExceptions.hpp | 37 +- engine/src/ecs/Entity.cpp | 7 +- engine/src/ecs/Entity.hpp | 5 +- engine/src/ecs/Group.hpp | 747 +++++++++--------- engine/src/ecs/GroupSystem.hpp | 321 ++++---- engine/src/ecs/QuerySystem.hpp | 140 ++-- engine/src/ecs/SingletonComponent.hpp | 156 ++-- engine/src/ecs/SingletonComponentMixin.hpp | 143 ++-- engine/src/ecs/System.cpp | 52 +- engine/src/ecs/System.hpp | 15 +- engine/src/systems/CameraSystem.cpp | 236 +++--- engine/src/systems/CameraSystem.hpp | 38 +- engine/src/systems/LightSystem.cpp | 2 +- engine/src/systems/LightSystem.hpp | 2 +- engine/src/systems/RenderSystem.cpp | 29 +- engine/src/systems/RenderSystem.hpp | 3 +- .../src/systems/lights/AmbientLightSystem.cpp | 11 +- .../src/systems/lights/AmbientLightSystem.hpp | 3 +- .../lights/DirectionalLightsSystem.cpp | 9 +- .../lights/DirectionalLightsSystem.hpp | 2 +- .../src/systems/lights/PointLightsSystem.cpp | 9 +- .../src/systems/lights/PointLightsSystem.hpp | 2 +- .../src/systems/lights/SpotLightsSystem.cpp | 10 +- .../src/systems/lights/SpotLightsSystem.hpp | 4 +- tests/ecs/ComponentArray.test.cpp | 1 - tests/ecs/Group.test.cpp | 23 +- tests/ecs/QuerySystem.test.cpp | 1 - tests/engine/components/Camera.test.cpp | 10 +- tests/renderer/Buffer.test.cpp | 2 +- tests/renderer/Renderer2D.test.cpp | 2 +- 35 files changed, 1110 insertions(+), 1139 deletions(-) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 11b3f2975..de74545a8 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -21,7 +21,6 @@ #include #include #include -#include namespace nexo::ecs { /** @@ -43,7 +42,7 @@ namespace nexo::ecs { * @param entity The entity to check * @return true if the entity has a component, false otherwise */ - virtual bool hasComponent(Entity entity) const = 0; + [[nodiscard]] virtual bool hasComponent(Entity entity) const = 0; /** * @brief Handles cleanup when an entity is destroyed @@ -68,7 +67,7 @@ namespace nexo::ecs { */ template= 1)>> - class alignas(64) ComponentArray : public IComponentArray { + class alignas(64) ComponentArray final : public IComponentArray { public: /** * @brief Type alias for the component type @@ -101,7 +100,7 @@ namespace nexo::ecs { void insert(Entity entity, T component) { if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); + THROW_EXCEPTION(OutOfRange, entity); // Ensure m_sparse can hold this entity index. ensureSparseCapacity(entity); @@ -130,7 +129,7 @@ namespace nexo::ecs { * * @pre The entity must have the component */ - void remove(Entity entity) + void remove(const Entity entity) { if (!hasComponent(entity)) THROW_EXCEPTION(ComponentNotFound, entity); @@ -176,10 +175,10 @@ namespace nexo::ecs { * * @pre The entity must have the component */ - [[nodiscard]] T& get(Entity entity) + [[nodiscard]] T& get(const Entity entity) { if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + THROW_EXCEPTION(ComponentNotFound, entity); return m_componentArray[m_sparse[entity]]; } @@ -192,10 +191,10 @@ namespace nexo::ecs { * * @pre The entity must have the component */ - [[nodiscard]] const T& get(Entity entity) const + [[nodiscard]] const T& get(const Entity entity) const { if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + THROW_EXCEPTION(ComponentNotFound, entity); return m_componentArray[m_sparse[entity]]; } @@ -205,7 +204,7 @@ namespace nexo::ecs { * @param entity The entity to check * @return true if the entity has a component, false otherwise */ - [[nodiscard]] bool hasComponent(Entity entity) const override + [[nodiscard]] bool hasComponent(const Entity entity) const override { return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); } @@ -215,7 +214,7 @@ namespace nexo::ecs { * * @param entity The entity being destroyed */ - void entityDestroyed(Entity entity) override + void entityDestroyed(const Entity entity) override { if (hasComponent(entity)) remove(entity); @@ -240,10 +239,10 @@ namespace nexo::ecs { * * @pre The index must be less than the array size */ - [[nodiscard]] Entity getEntityAtIndex(size_t index) const + [[nodiscard]] Entity getEntityAtIndex(const size_t index) const { if (index >= m_size) - THROW_EXCEPTION(OutOfRange, index); + THROW_EXCEPTION(OutOfRange, index); return m_dense[index]; } @@ -274,7 +273,7 @@ namespace nexo::ecs { */ [[nodiscard]] std::span entities() const { - return std::span(m_dense.data(), m_size); + return {m_dense.data(), m_size}; } /** @@ -288,10 +287,10 @@ namespace nexo::ecs { * * @pre The entity must have the component */ - void addToGroup(Entity entity) + void addToGroup(const Entity entity) { if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + THROW_EXCEPTION(ComponentNotFound, entity); size_t index = m_sparse[entity]; if (index < m_groupSize) @@ -318,10 +317,10 @@ namespace nexo::ecs { * * @pre The entity must have the component */ - void removeFromGroup(Entity entity) + void removeFromGroup(const Entity entity) { if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); + THROW_EXCEPTION(ComponentNotFound, entity); size_t index = m_sparse[entity]; if (index >= m_groupSize) @@ -346,20 +345,20 @@ namespace nexo::ecs { * @param component The component data to set * @throws OutOfRange if the index is invalid */ - void forceSetComponentAt(size_t index, Entity entity, T component) - { - if (index >= m_size) - THROW_EXCEPTION(OutOfRange, index); + void forceSetComponentAt(size_t index, const Entity entity, T component) + { + if (index >= m_size) + THROW_EXCEPTION(OutOfRange, index); - // Update the sparse mapping - m_sparse[entity] = index; + // Update the sparse mapping + m_sparse[entity] = index; - // Update the dense array - m_dense[index] = entity; + // Update the dense array + m_dense[index] = entity; - // Update the component - m_componentArray[index] = std::move(component); - } + // Update the component + m_componentArray[index] = std::move(component); + } /** * @brief Batch insertion of multiple components @@ -376,8 +375,8 @@ namespace nexo::ecs { template void insertBatch(EntityIt entitiesBegin, EntityIt entitiesEnd, CompIt componentsBegin) { - auto compIt = componentsBegin; - for (auto entityIt = entitiesBegin; entityIt != entitiesEnd; ++entityIt, ++compIt) { + CompIt compIt = componentsBegin; + for (EntityIt entityIt = entitiesBegin; entityIt != entitiesEnd; ++entityIt, ++compIt) { insert(*entityIt, *compIt); } } @@ -427,9 +426,9 @@ 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: @@ -449,7 +448,7 @@ namespace nexo::ecs { * * @param entity The entity to ensure capacity for */ - void ensureSparseCapacity(Entity entity) + void ensureSparseCapacity(const Entity entity) { if (entity >= m_sparse.size()) { size_t newSize = m_sparse.size(); @@ -471,7 +470,7 @@ namespace nexo::ecs { { if (m_size < m_componentArray.capacity() / 2 && m_componentArray.capacity() > capacity) { // Only shrink if vectors are significantly oversized to avoid frequent reallocations - size_t newCapacity = static_cast(m_size * 1.5); + auto newCapacity = static_cast(m_size * 1.5); if (newCapacity < capacity) newCapacity = capacity; diff --git a/engine/src/ecs/Components.cpp b/engine/src/ecs/Components.cpp index e11b90b15..84e5072e4 100644 --- a/engine/src/ecs/Components.cpp +++ b/engine/src/ecs/Components.cpp @@ -18,17 +18,13 @@ namespace nexo::ecs { void ComponentManager::entityDestroyed(const Entity entity, const Signature &entitySignature) { - for (auto &[_, group] : m_groupRegistry) - { - if ((entitySignature & group->allSignature()) == group->allSignature()) - { - group->removeFromGroup(entity); - } - } - for (auto& componentArray : m_componentArrays) { - if (componentArray) { + 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); - } } } diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 462b854b4..62282d5e6 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "ECSExceptions.hpp" #include "Definitions.hpp" @@ -57,7 +58,7 @@ namespace nexo::ecs { template get_t get() { - return {}; + return {}; } /** @@ -115,7 +116,7 @@ namespace nexo::ecs { * * @return String describing the components */ - std::string toString() const { + [[nodiscard]] std::string toString() const { std::stringstream ss; ss << "Owned: {"; bool first = true; @@ -123,7 +124,8 @@ namespace nexo::ecs { // Add owned component IDs for (ComponentType i = 0; i < MAX_COMPONENT_TYPE; ++i) { if (ownedSignature.test(i)) { - if (!first) ss << ", "; + if (!first) + ss << ", "; ss << "Component#" << i; first = false; } @@ -135,7 +137,8 @@ namespace nexo::ecs { // Add non-owned component IDs for (ComponentType i = 0; i < MAX_COMPONENT_TYPE; ++i) { if (nonOwnedSignature.test(i)) { - if (!first) ss << ", "; + if (!first) + ss << ", "; ss << "Component#" << i; first = false; } @@ -156,10 +159,11 @@ namespace std { */ template<> struct hash { - size_t operator()(const nexo::ecs::GroupKey& key) const { + size_t operator()(const nexo::ecs::GroupKey& key) const noexcept + { // Create a combined hash of both signatures - size_t h1 = std::hash()(key.ownedSignature); - size_t h2 = std::hash()(key.nonOwnedSignature); + const size_t h1 = std::hash()(key.ownedSignature); + const size_t h2 = std::hash()(key.nonOwnedSignature); return h1 ^ (h2 << 1); // Combine hashes with a bit shift } }; @@ -262,14 +266,14 @@ namespace nexo::ecs { * @param signature The entity's current component signature */ template - void addComponent(Entity entity, T component, Signature signature) + void addComponent(Entity entity, T component, const Signature signature) { getComponentArray()->insert(entity, std::move(component)); - for (auto& [key, group] : m_groupRegistry) + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { - if ((signature & group->allSignature()) == group->allSignature()) - group->addToGroup(entity); + if ((signature & group->allSignature()) == group->allSignature()) + group->addToGroup(entity); } } @@ -284,9 +288,9 @@ namespace nexo::ecs { * @param previousSignature The entity's signature before removal */ template - void removeComponent(Entity entity, Signature previousSignature) + void removeComponent(Entity entity, const Signature previousSignature) { - for (auto& [key, group] : m_groupRegistry) + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { if ((previousSignature & group->allSignature()) == group->allSignature()) group->removeFromGroup(entity); @@ -306,13 +310,13 @@ namespace nexo::ecs { * @return true if the component was removed, false if it didn't exist */ template - bool tryRemoveComponent(Entity entity, Signature previousSignature) + bool tryRemoveComponent(Entity entity, const Signature previousSignature) { auto componentArray = getComponentArray(); if (!componentArray->hasComponent(entity)) return false; - for (auto& [key, group] : m_groupRegistry) + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { if ((previousSignature & group->allSignature()) == group->allSignature()) group->removeFromGroup(entity); @@ -347,7 +351,7 @@ namespace nexo::ecs { { const ComponentType typeID = getComponentTypeID(); - auto& componentArray = m_componentArrays[typeID]; + const auto& componentArray = m_componentArrays[typeID]; if (componentArray == nullptr) THROW_EXCEPTION(ComponentNotRegistered); @@ -396,6 +400,7 @@ namespace nexo::ecs { * 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); @@ -418,7 +423,7 @@ namespace nexo::ecs { auto registerGroup(const auto& nonOwned) { // Generate a unique key for this group type combination - GroupKey newGroupKey = generateGroupKey(nonOwned); + const GroupKey newGroupKey = generateGroupKey(nonOwned); // Check if this exact group already exists auto it = m_groupRegistry.find(newGroupKey); @@ -429,18 +434,18 @@ namespace nexo::ecs { } // Check for conflicts with existing groups - for (const auto& [existingKey, existingGroup] : 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); - } - } - } - } + 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); + } + } + } + } // No conflicts found, create the new group auto group = createNewGroup(nonOwned); @@ -462,9 +467,9 @@ namespace nexo::ecs { template auto getGroup(const auto& nonOwned) { - GroupKey groupKey = generateGroupKey(nonOwned); + const GroupKey groupKey = generateGroupKey(nonOwned); - auto it = m_groupRegistry.find(groupKey); + const auto it = m_groupRegistry.find(groupKey); if (it == m_groupRegistry.end()) THROW_EXCEPTION(GroupNotFound, "Group not found"); @@ -484,7 +489,7 @@ namespace nexo::ecs { * @param key2 Second group key to compare * @return true if the keys share at least one owned component, false otherwise */ - [[nodiscard]] bool hasCommonOwnedComponents(const GroupKey& key1, const GroupKey& key2) const + [[nodiscard]] static bool hasCommonOwnedComponents(const GroupKey& key1, const GroupKey& key2) { // If there's any bit that's set in both owned signatures, they share components return (key1.ownedSignature & key2.ownedSignature).any(); @@ -512,7 +517,7 @@ namespace nexo::ecs { * @return Tuple of non-owned component arrays */ template - auto getNonOwnedTuple(const get_t& nonOwned) + auto getNonOwnedTuple(const get_t&) { return std::make_tuple(getComponentArray()...); } @@ -535,7 +540,7 @@ namespace nexo::ecs { // Find entities that should be in this group auto driver = std::get<0>(ownedArrays); - std::size_t minSize = std::apply([](auto&&... arrays) -> std::size_t { + const std::size_t minSize = std::apply([](auto&&... arrays) -> std::size_t { return std::min({ static_cast(arrays->size())... }); }, ownedArrays); @@ -596,7 +601,7 @@ namespace nexo::ecs { * @param nonOwned The non-owned components tag */ template - void setNonOwnedBits(Signature& signature, const get_t& nonOwned) + static void setNonOwnedBits(Signature& signature, const get_t&) { ((signature.set(getComponentTypeID())), ...); } diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index 3a74bccb8..605bca397 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -37,7 +37,7 @@ namespace nexo::ecs { void Coordinator::destroyEntity(const Entity entity) const { - auto signature = m_entityManager->getSignature(entity); + const Signature signature = m_entityManager->getSignature(entity); m_entityManager->destroyEntity(entity); m_componentManager->entityDestroyed(entity, signature); m_systemManager->entityDestroyed(entity, signature); diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 7d5a1ddc4..544e664e2 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -16,7 +16,6 @@ #include #include -#include #include "Components.hpp" #include "System.hpp" @@ -78,13 +77,13 @@ namespace nexo::ecs { * @brief Registers a new singleton component * * @tparam T Class that should inherit from SingletonComponent class - * @param component + * @param args Optional argument to forward to the singleton component constructor */ - template - void registerSingletonComponent(Args&&... args) - { - m_singletonComponentManager->registerSingletonComponent(std::forward(args)...); - } + template + void registerSingletonComponent(Args&&... args) + { + m_singletonComponentManager->registerSingletonComponent(std::forward(args)...); + } /** * @brief Adds a component to an entity, updates its signature, and notifies systems. @@ -95,9 +94,9 @@ namespace nexo::ecs { template void addComponent(const Entity entity, T component) { - auto signature = m_entityManager->getSignature(entity); - auto oldSignature = signature; - signature.set(m_componentManager->getComponentType(), true); + Signature signature = m_entityManager->getSignature(entity); + const Signature oldSignature = signature; + signature.set(m_componentManager->getComponentType(), true); m_componentManager->addComponent(entity, component, signature); m_entityManager->setSignature(entity, signature); @@ -113,9 +112,9 @@ namespace nexo::ecs { template void removeComponent(const Entity entity) const { - auto signature = m_entityManager->getSignature(entity); - auto oldSignature = signature; - signature.set(m_componentManager->getComponentType(), false); + Signature signature = m_entityManager->getSignature(entity); + const Signature oldSignature = signature; + signature.set(m_componentManager->getComponentType(), false); m_componentManager->removeComponent(entity, oldSignature); @@ -135,10 +134,10 @@ namespace nexo::ecs { template void tryRemoveComponent(const Entity entity) const { - auto signature = m_entityManager->getSignature(entity); + Signature signature = m_entityManager->getSignature(entity); if (m_componentManager->tryRemoveComponent(entity, signature)) { - auto oldSignature = signature; + Signature oldSignature = signature; signature.set(m_componentManager->getComponentType(), false); m_entityManager->setSignature(entity, signature); @@ -207,7 +206,7 @@ namespace nexo::ecs { * @return std::shared_ptr The pointer to the desired singleton component */ template - std::shared_ptr getRawSingletonComponent() + std::shared_ptr getRawSingletonComponent() const { return m_singletonComponentManager->getRawSingletonComponent(); } @@ -237,19 +236,19 @@ namespace nexo::ecs { template std::vector getAllEntitiesWith() const { - Signature requiredSignature; - (requiredSignature.set(m_componentManager->getComponentType(), true), ...); - - std::span livingEntities = m_entityManager->getLivingEntities(); - std::vector result; - result.reserve(livingEntities.size()); - for (auto entity : livingEntities) - { - Signature entitySignature = m_entityManager->getSignature(entity); - if ((entitySignature & requiredSignature) == requiredSignature) - result.push_back(entity); - } - return result; + Signature requiredSignature; + (requiredSignature.set(m_componentManager->getComponentType(), true), ...); + + std::span livingEntities = m_entityManager->getLivingEntities(); + std::vector result; + result.reserve(livingEntities.size()); + for (Entity entity : livingEntities) + { + const Signature entitySignature = m_entityManager->getSignature(entity); + if ((entitySignature & requiredSignature) == requiredSignature) + result.push_back(entity); + } + return result; } /** @@ -274,9 +273,9 @@ namespace nexo::ecs { std::shared_ptr registerQuerySystem(Args&&... args) { auto newQuerySystem = m_systemManager->registerQuerySystem(std::forward(args)...); std::span livingEntities = m_entityManager->getLivingEntities(); - Signature querySystemSignature = newQuerySystem->getSignature(); + const Signature querySystemSignature = newQuerySystem->getSignature(); for (Entity entity : livingEntities) { - Signature entitySignature = m_entityManager->getSignature(entity); + const Signature entitySignature = m_entityManager->getSignature(entity); if ((entitySignature & querySystemSignature) == querySystemSignature) { newQuerySystem->entities.insert(entity); } @@ -330,7 +329,7 @@ namespace nexo::ecs { template bool entityHasComponent(const Entity entity) const { - const auto signature = m_entityManager->getSignature(entity); + const Signature signature = m_entityManager->getSignature(entity); const ComponentType componentType = m_componentManager->getComponentType(); return signature.test(componentType); } diff --git a/engine/src/ecs/ECSExceptions.hpp b/engine/src/ecs/ECSExceptions.hpp index 3a6e1e846..01ed25ffe 100644 --- a/engine/src/ecs/ECSExceptions.hpp +++ b/engine/src/ecs/ECSExceptions.hpp @@ -21,36 +21,39 @@ namespace nexo::ecs { - class InternalError final : public Exception { - public: - explicit InternalError(const std::string& message, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Internal error: {}", message), loc) {} - }; + class InternalError final : public Exception { + public: + explicit InternalError(const std::string& message, + 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) {} + 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) {} + 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) {} + 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) {} + 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 { @@ -79,7 +82,7 @@ namespace nexo::ecs { class OutOfRange final : public Exception { public: - explicit OutOfRange(unsigned int index, const std::source_location loc = std::source_location::current()) - : Exception(std::format("Index {} is out of range", index), loc) {} + explicit OutOfRange(unsigned int index, const std::source_location loc = std::source_location::current()) + : Exception(std::format("Index {} is out of range", index), loc) {} }; } diff --git a/engine/src/ecs/Entity.cpp b/engine/src/ecs/Entity.cpp index a9a8b63c2..ebf70293b 100644 --- a/engine/src/ecs/Entity.cpp +++ b/engine/src/ecs/Entity.cpp @@ -16,6 +16,7 @@ #include #include +#include namespace nexo::ecs { @@ -42,11 +43,11 @@ namespace nexo::ecs { if (entity >= MAX_ENTITIES) THROW_EXCEPTION(OutOfRange, entity); - auto it = std::find(m_livingEntities.begin(), m_livingEntities.end(), entity); + const auto it = std::ranges::find(m_livingEntities, entity); if (it != m_livingEntities.end()) m_livingEntities.erase(it); else - return; + return; m_signatures[entity].reset(); m_availableEntities.push_front(entity); @@ -76,7 +77,7 @@ namespace nexo::ecs { std::span EntityManager::getLivingEntities() const { - return std::span(m_livingEntities); + return {m_livingEntities}; } } diff --git a/engine/src/ecs/Entity.hpp b/engine/src/ecs/Entity.hpp index 1c0e34050..3e83fd65a 100644 --- a/engine/src/ecs/Entity.hpp +++ b/engine/src/ecs/Entity.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include "Definitions.hpp" @@ -74,8 +73,8 @@ namespace nexo::ecs { */ [[nodiscard]] Signature getSignature(Entity entity) const; - std::uint32_t getLivingEntityCount() const; - std::span getLivingEntities() const; + [[nodiscard]] std::uint32_t getLivingEntityCount() const; + [[nodiscard]] std::span getLivingEntities() const; private: std::deque m_availableEntities{}; diff --git a/engine/src/ecs/Group.hpp b/engine/src/ecs/Group.hpp index 878b339ec..e5f287a89 100644 --- a/engine/src/ecs/Group.hpp +++ b/engine/src/ecs/Group.hpp @@ -45,7 +45,7 @@ namespace nexo::ecs { * * @return const Signature& Combined signature. */ - virtual const Signature& allSignature() const = 0; + [[nodiscard]] virtual const Signature& allSignature() const = 0; /** * @brief Adds an entity to the group. * @@ -142,49 +142,49 @@ namespace nexo::ecs { * @tparam NonOwnedTuple Tuple of pointers to non‑owned component arrays. */ template - class Group : public IGroup { + 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 - Group(OwnedTuple ownedArrays, NonOwnedTuple nonOwnedArrays) - : m_ownedArrays(std::move(ownedArrays)) - , m_nonOwnedArrays(std::move(nonOwnedArrays)) - { - if (std::tuple_size_v == 0) - THROW_EXCEPTION(InternalError, "Group must have at least one owned component"); - m_ownedSignature = std::apply([](auto&&... arrays) -> Signature { - Signature signature; - ((signature.set(getComponentTypeID::component_type>())), ...); - return signature; - }, m_ownedArrays); - - Signature nonOwnedSignature = std::apply([](auto&&... arrays) -> Signature { - Signature signature; - ((signature.set(getComponentTypeID::component_type>())), ...); - return signature; - }, m_nonOwnedArrays); - - m_allSignature = m_ownedSignature | nonOwnedSignature; - } + /** + * @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 + Group(OwnedTuple ownedArrays, NonOwnedTuple nonOwnedArrays) + : m_ownedArrays(std::move(ownedArrays)) + , m_nonOwnedArrays(std::move(nonOwnedArrays)) + { + if (std::tuple_size_v == 0) + THROW_EXCEPTION(InternalError, "Group must have at least one owned component"); + m_ownedSignature = std::apply([]([[maybe_unused]] auto&&... arrays) -> Signature { + Signature signature; + ((signature.set(getComponentTypeID::component_type>())), ...); + return signature; + }, m_ownedArrays); + + const Signature nonOwnedSignature = std::apply([]([[maybe_unused]] auto&&... arrays) -> Signature { + 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. - */ - std::size_t size() const + /** + * @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) { @@ -199,21 +199,21 @@ namespace nexo::ecs { * @return true If sorting is invalidated. * @return false Otherwise. */ - bool sortingInvalidated() const { return m_sortingInvalidated; } + [[nodiscard]] bool sortingInvalidated() const { return m_sortingInvalidated; } /** * @brief Returns the signature for owned components. * * @return const Signature& Owned signature. */ - const Signature& ownedSignature() const { return m_ownedSignature; } + [[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. */ - const Signature& allSignature() const override { return m_allSignature; } + [[nodiscard]] const Signature& allSignature() const override { return m_allSignature; } /** * @brief Iterator for Group. @@ -308,17 +308,16 @@ namespace nexo::ecs { template void each(Func func) const { - using FirstOwned = std::tuple_element_t<0, OwnedTuple>; - auto firstArray = std::get<0>(m_ownedArrays); + 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>{}); - } + 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>{}); + } } /** @@ -330,7 +329,7 @@ namespace nexo::ecs { * @param func Function to call for each entity in range. */ template - void eachInRange(size_t startIndex, size_t count, Func func) const + void eachInRange(size_t startIndex, const size_t count, Func func) const { auto firstArray = std::get<0>(m_ownedArrays); if (!firstArray) { @@ -342,12 +341,12 @@ namespace nexo::ecs { } 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>{}); - } + for (size_t i = startIndex; i < endIndex; i++) { + Entity e = firstArray->getEntityAtIndex(i); + callFunc(func, e, + std::make_index_sequence>{}, + std::make_index_sequence>{}); + } } /** @@ -359,9 +358,9 @@ namespace nexo::ecs { */ void addToGroup(Entity e) override { - std::apply([e](auto&&... arrays) { - ((arrays->addToGroup(e)), ...); - }, m_ownedArrays); + std::apply([e](auto&&... arrays) { + ((arrays->addToGroup(e)), ...); + }, m_ownedArrays); m_sortingInvalidated = true; invalidatePartitions(); } @@ -375,9 +374,9 @@ namespace nexo::ecs { */ void removeFromGroup(Entity e) override { - std::apply([e](auto&&... arrays) { - ((arrays->removeFromGroup(e)), ...); - }, m_ownedArrays); + std::apply([e](auto&&... arrays) { + ((arrays->removeFromGroup(e)), ...); + }, m_ownedArrays); m_sortingInvalidated = true; invalidatePartitions(); } @@ -390,11 +389,11 @@ namespace nexo::ecs { * * @return std::span Span of entity IDs. */ - std::span entities() const - { - auto entities = std::get<0>(m_ownedArrays)->entities(); - return entities.subspan(0, std::get<0>(m_ownedArrays)->groupSize()); - } + [[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. @@ -407,16 +406,16 @@ namespace nexo::ecs { template auto get() const { - if constexpr (tuple_contains_component_v) { - auto compArray = getOwnedImpl(); // internal lookup in owned tuple + 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"); + 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"); } /** @@ -430,16 +429,16 @@ namespace nexo::ecs { template auto get() { - if constexpr (tuple_contains_component_v) { - auto compArray = getOwnedImpl(); // internal lookup in owned tuple + 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"); + 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"); } // ======================================= @@ -484,7 +483,7 @@ namespace nexo::ecs { // Get the driving array (always the first owned array) auto drivingArray = std::get<0>(m_ownedArrays); - auto groupSize = drivingArray->groupSize(); + const size_t groupSize = drivingArray->groupSize(); // Create a vector of entities to sort std::vector entities; @@ -501,8 +500,7 @@ namespace nexo::ecs { const auto& compB = compArray->get(b); if (ascending) return extractor(compA) < extractor(compB); - else - return extractor(compA) > extractor(compB); + return extractor(compA) > extractor(compB); }); // Reorder only the owned component arrays according to the new order @@ -521,75 +519,75 @@ namespace nexo::ecs { */ 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; - } + 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; + /** + * @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); - } + 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 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. - */ - size_t partitionCount() const - { - return m_partitions.size(); - } + /** + * @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. + private: + Group* m_group; ///< Pointer to the group. + const std::vector>& m_partitions; ///< Reference to partitions. }; /** @@ -603,70 +601,69 @@ namespace nexo::ecs { template PartitionView getPartitionView(FieldExtractor keyExtractor) { - std::string typeId = typeid(KeyType).name(); - typeId += "_" + std::string(typeid(CompType).name()); - - // Create entity-based key extractor that uses the component extractor - EntityKeyExtractor entityKeyExtractor = [this, keyExtractor](Entity e) { - // Get the component and extract the key - if constexpr (tuple_contains_component_v) { - auto compArray = getOwnedImpl(); - return keyExtractor(compArray->get(e)); - } else if constexpr (tuple_contains_component_v) { - auto compArray = getNonOwnedImpl(); - return keyExtractor(compArray->get(e)); - } else - static_assert(dependent_false::value, "Component type not found in group"); - }; - - return getEntityPartitionView(typeId, entityKeyExtractor); + std::string typeId = typeid(KeyType).name(); + typeId += "_" + std::string(typeid(CompType).name()); + + // Create entity-based key extractor that uses the component extractor + EntityKeyExtractor entityKeyExtractor = [this, keyExtractor](Entity e) { + // Get the component and extract the key + if constexpr (tuple_contains_component_v) { + auto compArray = getOwnedImpl(); + return keyExtractor(compArray->get(e)); + } else if constexpr (tuple_contains_component_v) { + auto compArray = getNonOwnedImpl(); + 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. - */ + * @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) + 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()) { - // Create a new partition storage - auto storage = std::make_unique>(this, keyExtractor); - auto* storagePtr = storage.get(); - m_partitionStorageMap[partitionId] = std::move(storage); - - // Rebuild the partitions - storagePtr->rebuild(); - - return PartitionView(this, storagePtr->getPartitions()); - } + // Check if we already have this partition view + auto it = m_partitionStorageMap.find(partitionId); + if (it == m_partitionStorageMap.end()) { + // Create a new partition storage + auto storage = std::make_unique>(this, keyExtractor); + auto* storagePtr = storage.get(); + m_partitionStorageMap[partitionId] = std::move(storage); + + // Rebuild the partitions + storagePtr->rebuild(); + + return PartitionView(this, storagePtr->getPartitions()); + } - // Get the existing storage and cast to the right type - auto* storage = static_cast*>(it->second.get()); + // Get the existing storage and cast to the right type + auto* storage = static_cast*>(it->second.get()); - // Rebuild if needed - if (storage->isDirty()) - storage->rebuild(); + // Rebuild if needed + if (storage->isDirty()) + storage->rebuild(); - // Return a view to the partitions - return PartitionView(this, storage->getPartitions()); + // Return a view to the partitions + return PartitionView(this, storage->getPartitions()); } /** - * @brief Invalidates all partition caches. - */ + * @brief Invalidates all partition caches. + */ void invalidatePartitions() { - for (auto& [_, storage] : m_partitionStorageMap) - storage->markDirty(); + for (auto& [_, storage] : m_partitionStorageMap) + storage->markDirty(); } private: @@ -676,28 +673,28 @@ namespace nexo::ecs { // ======================================= /** - * @brief Interface for type-erased partition storage. - * - * This allows handling partition storage for different key types uniformly. - */ + * @brief Interface for type-erased partition storage. + * + * This allows handling partition storage for different key types uniformly. + */ struct IPartitionStorage { - /// Virtual destructor. - virtual ~IPartitionStorage() = default; - /** - * @brief Checks if the partition storage is dirty (needs rebuilding). - * - * @return true If dirty. - * @return false Otherwise. - */ - 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; + /// Virtual destructor. + 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; }; /** @@ -706,143 +703,143 @@ namespace nexo::ecs { * @tparam KeyType Type of the partition key. */ template - class PartitionStorage : 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)), m_isDirty(true) {} - - 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; + 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)), m_isDirty(true) {} + + [[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); - auto groupSize = drivingArray->groupSize(); + 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; - } + // Skip if no entities + if (groupSize == 0) { + m_partitions.clear(); + m_isDirty = false; + return; + } - // Collect all entity keys - std::unordered_map> keyToEntities; + // Collect all entity keys + 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); - } + for (size_t i = 0; i < groupSize; i++) { + Entity e = drivingArray->getEntityAtIndex(i); + KeyType key = m_keyExtractor(e); + keyToEntities[key].push_back(e); + } - // Create the partitions - m_partitions.clear(); - m_partitions.reserve(keyToEntities.size()); + // Create the partitions + m_partitions.clear(); + m_partitions.reserve(keyToEntities.size()); - std::vector newOrder; - newOrder.reserve(groupSize); + 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); + 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()); + // Add these entities to the new order + newOrder.insert(newOrder.end(), entities.begin(), entities.end()); - currentIndex += entities.size(); - } + currentIndex += entities.size(); + } - // Reorder the entities according to partitions - m_group->reorderGroup(newOrder); + // Reorder the entities according to partitions + m_group->reorderGroup(newOrder); - m_isDirty = false; - } + m_isDirty = false; + } - /** - * @brief Gets the current partitions. - * - * @return const std::vector>& Reference to the partitions. - */ - const std::vector>& getPartitions() const - { - return m_partitions; - } + /** + * @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; ///< Flag indicating if partitions need rebuilding. + 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; ///< Flag indicating if partitions need rebuilding. }; /** - * @brief Reorders the group entities based on a new order. - * - * @param newOrder New order of entities. - */ + * @brief Reorders the group entities based on a new order. + * + * @param newOrder New order of entities. + */ void reorderGroup(const std::vector& newOrder) { - // Implementation to rearrange entities in all owned arrays - std::apply([&](auto&&... arrays) { - ((reorderArray(arrays, newOrder)), ...); - }, m_ownedArrays); + // Implementation to rearrange entities in all owned arrays + 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. - */ + * @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) { - size_t groupSize = array->groupSize(); - if (newOrder.size() != groupSize) - THROW_EXCEPTION(InternalError, "New order size doesn't match group size"); - - if (groupSize == 0) - return; // Nothing to reorder - - // Create a temporary storage for components - using CompType = typename std::decay_t::component_type; - std::vector tempComponents; - tempComponents.reserve(groupSize); - - // Copy components in the new order - try { - for (Entity e : newOrder) - tempComponents.push_back(array->get(e)); - - // Update the sparse-to-dense mapping and components - for (size_t i = 0; i < groupSize; i++) { - Entity e = newOrder[i]; - array->forceSetComponentAt(i, e, std::move(tempComponents[i])); - } - } catch (...) { - // If anything goes wrong, don't leave the array in a partially-updated state - THROW_EXCEPTION(InternalError, "Reordering failed, array may be in an inconsistent state"); - } + size_t groupSize = array->groupSize(); + if (newOrder.size() != groupSize) + THROW_EXCEPTION(InternalError, "New order size doesn't match group size"); + + if (groupSize == 0) + return; // Nothing to reorder + + // Create a temporary storage for components + using CompType = typename std::decay_t::component_type; + std::vector tempComponents; + tempComponents.reserve(groupSize); + + // Copy components in the new order + try { + for (Entity e : newOrder) + tempComponents.push_back(array->get(e)); + + // Update the sparse-to-dense mapping and components + for (size_t i = 0; i < groupSize; i++) { + Entity e = newOrder[i]; + array->forceSetComponentAt(i, e, std::move(tempComponents[i])); + } + } catch (...) { + // If anything goes wrong, don't leave the array in a partially-updated state + THROW_EXCEPTION(InternalError, "Reordering failed, array may be in an inconsistent state"); + } } /** @@ -853,14 +850,14 @@ namespace nexo::ecs { */ auto dereference(std::size_t index) const { - // Retrieve the entity from the first owned array. - auto 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); + // Retrieve the entity from the first owned array. + 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); } /** @@ -870,19 +867,20 @@ namespace nexo::ecs { * @tparam I Current index in the tuple. * @return std::shared_ptr> Pointer to the component array. */ - template - auto getNonOwnedImpl() const -> std::shared_ptr> - { - if constexpr (I < std::tuple_size_v) { - 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(); - } else - static_assert(I < std::tuple_size_v, "Component type not found in group non‑owned arrays"); - } + template + auto getNonOwnedImpl() const -> std::shared_ptr> + { + if constexpr (I < std::tuple_size_v) { + 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(); + } else + static_assert(I < std::tuple_size_v, "Component type not found in group non‑owned arrays"); + return nullptr; + } /** * @brief Helper: Recursively search the owned tuple for the ComponentArray with component_type == T. @@ -891,19 +889,20 @@ namespace nexo::ecs { * @tparam I Current index in the tuple. * @return std::shared_ptr> Pointer to the component array. */ - template - auto getOwnedImpl() const -> std::shared_ptr> - { - if constexpr (I < std::tuple_size_v) { - 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(); - } else - static_assert(I < std::tuple_size_v, "Component type not found in group owned arrays"); - } + template + auto getOwnedImpl() const -> std::shared_ptr> + { + if constexpr (I < std::tuple_size_v) { + 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(); + } else + static_assert(I < std::tuple_size_v, "Component type not found in group owned arrays"); + return nullptr; + } /** * @brief Helper function to call a function with component data. @@ -918,15 +917,11 @@ namespace nexo::ecs { * @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))...); - } + 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))...); + } enum class SortingOrder { ASCENDING, diff --git a/engine/src/ecs/GroupSystem.hpp b/engine/src/ecs/GroupSystem.hpp index 57eb3e9a6..b04ab4dee 100644 --- a/engine/src/ecs/GroupSystem.hpp +++ b/engine/src/ecs/GroupSystem.hpp @@ -35,159 +35,156 @@ namespace nexo::ecs { * @tparam SingletonAccessTypes Singleton component access types (ReadSingleton or WriteSingleton) */ template, typename... SingletonAccessTypes> - class GroupSystem : public AGroupSystem, public SingletonComponentMixin< - GroupSystem, - 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 component access types + using OwnedAccessTypes = typename OwnedAccess::ComponentTypes; + using NonOwnedAccessTypes = typename NonOwnedAccess::ComponentTypes; - // Extract raw component types for group creation - template - struct GetComponentTypes; + // Extract raw component types for group creation + template + struct GetComponentTypes; - template - struct GetComponentTypes> { - using Types = std::tuple; - }; + template + struct GetComponentTypes> { + using Types = std::tuple; + }; - using OwnedTypes = typename GetComponentTypes::Types; - using NonOwnedTypes = typename GetComponentTypes::Types; + using OwnedTypes = typename GetComponentTypes::Types; + using NonOwnedTypes = typename GetComponentTypes::Types; - // Helper to unpack tuple types to parameter pack - template - auto tupleToTypeList(std::index_sequence) + // Helper to unpack tuple types to parameter pack + template + auto tupleToTypeList(std::index_sequence) { - return std::tuple...>{}; - } + return std::tuple...>{}; + } - // Function to create a type list from a tuple type - template - auto tupleToTypeList() + // 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 AccessType accessType = AccessType::Read; - }; - - template - struct ComponentAccessTrait, - std::void_t || ...)>>> { - static constexpr bool found = true; - - template - static constexpr AccessType GetAccessTypeFromPack() { - AccessType 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; - }; + 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 */ - template - class ComponentSpan { - private: - std::span m_span; - - public: - explicit ComponentSpan(std::span span) : m_span(span) {} - - size_t size() const { return m_span.size(); } - - // Conditionally define operator[] based on access type - template - auto operator[](size_t index) -> std::conditional_t< - GetComponentAccess>::accessType == AccessType::Write, - std::remove_const_t&, - const std::remove_const_t& - > + template + class ComponentSpan { + private: + std::span m_span; + + public: + explicit ComponentSpan(std::span span) : m_span(span) {} + + [[nodiscard]] size_t size() const { return m_span.size(); } + + // Conditionally define operator[] based on access type + 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]; - } - } - - template - auto operator[](size_t index) const -> const std::remove_const_t& + if constexpr (GetComponentAccess>::accessType == AccessType::Write) { + return const_cast&>(m_span[index]); + } else { + return m_span[index]; + } + } + + template + auto operator[](size_t index) const -> const std::remove_const_t& { - return m_span[index]; - } + return m_span[index]; + } - // Iterator support - auto begin() { return m_span.begin(); } - auto end() { return m_span.end(); } - auto begin() const { return m_span.begin(); } - auto end() const { return m_span.end(); } - }; + // Iterator support + auto begin() { return m_span.begin(); } + auto end() { return m_span.end(); } + auto begin() const { return m_span.begin(); } + auto end() const { return m_span.end(); } + }; public: // Make the base class a friend to access protected members - friend class SingletonComponentMixin< - GroupSystem, - SingletonAccessTypes...>; + friend class SingletonComponentMixin< + GroupSystem, + SingletonAccessTypes...>; - GroupSystem() + GroupSystem() { - static_assert(std::tuple_size_v > 0, - "GroupSystem must have at least one owned component type"); + 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"); - return; - } + THROW_EXCEPTION(InternalError, "Coordinator is null in GroupSystem constructor"); + } - // Create the group - m_group = createGroupImpl( - tupleToTypeList(), - tupleToTypeList() - ); + // Create the group + m_group = createGroupImpl( + tupleToTypeList(), + tupleToTypeList() + ); - // Initialize singleton components - this->initializeSingletonComponents(); - } + // Initialize singleton components + this->initializeSingletonComponents(); + } /** * @brief Get component array with correct access permissions @@ -195,12 +192,12 @@ namespace nexo::ecs { * @tparam T The component type * @return ComponentSpan with enforced access control */ - template - auto get() + template + auto get() { - if (!m_group) { - THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); - } + if (!m_group) { + THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); + } constexpr bool isOwned = isOwnedComponent(); if constexpr (isOwned) { // Get the span from the group @@ -208,9 +205,9 @@ namespace nexo::ecs { // Wrap it in our access-controlled span return ComponentSpan::accessType == AccessType::Read, - const T, - T + GetComponentAccess::accessType == AccessType::Read, + const T, + T >>(baseSpan); } else { // For non-owned components, return the component array itself @@ -220,10 +217,10 @@ namespace nexo::ecs { if constexpr (GetComponentAccess::accessType == AccessType::Read) { return std::shared_ptr>(m_group->template get()); } else { - return componentArray; - } + return componentArray; + } } - } + } /** * @brief Check if a component type is owned by this system @@ -234,8 +231,8 @@ namespace nexo::ecs { template static constexpr bool isOwnedComponent() { - using OwnedTraitResult = ComponentAccessTrait; - return OwnedTraitResult::found; + using OwnedTraitResult = ComponentAccessTrait; + return OwnedTraitResult::found; } /** @@ -243,13 +240,13 @@ namespace nexo::ecs { * * @return Span of entities in the group */ - auto getEntities() const + [[nodiscard]] std::span getEntities() const { - if (!m_group) { - THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); - } - return m_group->entities(); - } + if (!m_group) { + THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); + } + return m_group->entities(); + } protected: std::shared_ptr m_group = nullptr; @@ -258,19 +255,19 @@ namespace nexo::ecs { /** * @brief Implementation to create a group with the extracted component types */ - template - std::shared_ptr createGroupImpl(std::tuple, std::tuple) + template + std::shared_ptr createGroupImpl(std::tuple, std::tuple) { - if constexpr (sizeof...(OT) > 0) { - if constexpr (sizeof...(NOT) > 0) { - auto group = coord->template registerGroup(nexo::ecs::get()); - return std::static_pointer_cast(group); - } else { - auto group = coord->template registerGroup(nexo::ecs::get<>()); - return std::static_pointer_cast(group); - } - } - return nullptr; - } + if constexpr (sizeof...(OT) > 0) { + if constexpr (sizeof...(NOT) > 0) { + auto group = coord->registerGroup(nexo::ecs::get()); + return std::static_pointer_cast(group); + } else { + auto group = coord->registerGroup(nexo::ecs::get<>()); + return std::static_pointer_cast(group); + } + } + return nullptr; + } }; } diff --git a/engine/src/ecs/QuerySystem.hpp b/engine/src/ecs/QuerySystem.hpp index 06f7caf3e..58be1c7f9 100644 --- a/engine/src/ecs/QuerySystem.hpp +++ b/engine/src/ecs/QuerySystem.hpp @@ -32,50 +32,49 @@ namespace nexo::ecs { */ template class QuerySystem : public AQuerySystem, public SingletonComponentMixin< - QuerySystem, - Components...> { - private: + QuerySystem, + Components...> { + private: // Helper template to check if a component type exists in the parameter pack with Read access - template - struct HasReadAccess { - static constexpr bool value = - (std::is_same_v> || - HasReadAccess::value); - }; - - // Base case for the template recursion - template - struct HasReadAccess { - static constexpr bool value = std::is_same_v>; - }; - - // Convenience function to check read access - template - static constexpr bool hasReadAccess() + template + struct HasReadAccess { + static constexpr bool value = + (std::is_same_v> || + HasReadAccess::value); + }; + + // Base case for the template recursion + template + struct HasReadAccess { + static constexpr bool value = std::is_same_v>; + }; + + // Convenience function to check read access + template + static constexpr bool hasReadAccess() { - return HasReadAccess::value; - } + return HasReadAccess::value; + } public: // Make the base class a friend to access protected members - friend class SingletonComponentMixin, Components...>; + friend class SingletonComponentMixin, Components...>; - QuerySystem() + QuerySystem() { if (!coord) { - THROW_EXCEPTION(InternalError, "Coordinator is null in QuerySystem constructor"); - return; - } + THROW_EXCEPTION(InternalError, "Coordinator is null in QuerySystem constructor"); + } - // Set system signature based on required components (ignore singleton components) - (setComponentSignatureIfRegular(m_signature), ...); + // Set system signature based on required components (ignore singleton components) + (setComponentSignatureIfRegular(m_signature), ...); - // Cache component arrays for faster access (ignore singleton components) - (cacheComponentArrayIfRegular(), ...); + // Cache component arrays for faster access (ignore singleton components) + (cacheComponentArrayIfRegular(), ...); - // Initialize singleton components - this->initializeSingletonComponents(); - } + // Initialize singleton components + this->initializeSingletonComponents(); + } /** * @brief Get a component for an entity with access type determined at compile time @@ -84,32 +83,27 @@ namespace nexo::ecs { * @param entity The entity to get the component from * @return Reference to the component with appropriate const-ness */ - template - typename std::conditional(), const T&, T&>::type - getComponent(Entity entity) + template + std::conditional_t(), const T&, T&> getComponent(Entity entity) { - auto typeIndex = getTypeIndex(); - auto it = m_componentArrays.find(typeIndex); + const std::type_index typeIndex = getTypeIndex(); + const auto it = m_componentArrays.find(typeIndex); - if (it == m_componentArrays.end()) - THROW_EXCEPTION(InternalError, "Component array not found"); + if (it == m_componentArrays.end()) + THROW_EXCEPTION(InternalError, "Component array not found"); - auto componentArray = std::static_pointer_cast>(it->second); + auto componentArray = std::static_pointer_cast>(it->second); - if (!componentArray) - THROW_EXCEPTION(InternalError, "Failed to cast component array"); + if (!componentArray) + THROW_EXCEPTION(InternalError, "Failed to cast component array"); - if (!componentArray->hasComponent(entity)) - THROW_EXCEPTION(InternalError, "Entity doesn't have requested component"); + if (!componentArray->hasComponent(entity)) + THROW_EXCEPTION(InternalError, "Entity doesn't have requested component"); + return componentArray->get(entity); + } - if constexpr (hasReadAccess()) - return componentArray->get(entity); - else - return componentArray->get(entity); - } - - const Signature& getSignature() const { return m_signature; } - Signature& getSignature() { return m_signature; } + const Signature& getSignature() const override { return m_signature; } + Signature& getSignature() { return m_signature; } protected: /** @@ -118,14 +112,14 @@ namespace nexo::ecs { * @tparam ComponentAccessType The component access type to cache */ template - void cacheComponentArrayIfRegular() - { - if constexpr (!IsSingleton::value) { - using T = typename ComponentAccessType::ComponentType; - auto componentArray = coord->template getComponentArray(); - m_componentArrays[getTypeIndex()] = componentArray; - } - } + void cacheComponentArrayIfRegular() + { + if constexpr (!IsSingleton::value) { + using T = typename ComponentAccessType::ComponentType; + auto componentArray = coord->getComponentArray(); + m_componentArrays[getTypeIndex()] = componentArray; + } + } /** * @brief Sets the component bit in the system signature (only for regular components) @@ -133,18 +127,18 @@ namespace nexo::ecs { * @tparam ComponentAccessType The component access type * @param signature The signature to modify */ - template - void setComponentSignatureIfRegular(Signature& signature) + template + void setComponentSignatureIfRegular(Signature& signature) { - if constexpr (!IsSingleton::value) { - using T = typename ComponentAccessType::ComponentType; - signature.set(coord->template getComponentType(), true); - } - } - - private: - // Cache of component arrays for faster access - std::unordered_map> m_componentArrays; - Signature m_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; + Signature m_signature; }; } diff --git a/engine/src/ecs/SingletonComponent.hpp b/engine/src/ecs/SingletonComponent.hpp index 6c2b59e09..ac30e7cf9 100644 --- a/engine/src/ecs/SingletonComponent.hpp +++ b/engine/src/ecs/SingletonComponent.hpp @@ -28,11 +28,10 @@ namespace nexo::ecs { * * All singleton components must derive from this interface. It ensures proper polymorphic destruction. */ - class ISingletonComponent { - public: - virtual ~ISingletonComponent() = default; - }; - + class ISingletonComponent { + public: + virtual ~ISingletonComponent() = default; + }; /** * @brief Template class representing a singleton component. @@ -41,11 +40,11 @@ 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"); + 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. * @@ -59,12 +58,12 @@ namespace nexo::ecs { template requires (std::is_constructible_v && (!std::is_same_v, T> && ...)) explicit SingletonComponent(Args&&... args) - : _instance(std::forward(args)...) + : _instance(std::forward(args)...) { } SingletonComponent() = default; - virtual ~SingletonComponent() = default; + ~SingletonComponent() override = default; // Prevent copying SingletonComponent(const SingletonComponent&) = delete; @@ -74,12 +73,11 @@ namespace nexo::ecs { SingletonComponent(SingletonComponent&&) = default; SingletonComponent& operator=(SingletonComponent&&) = default; - T &getInstance() { return _instance; } - - private: - T _instance; - }; + T &getInstance() { return _instance; } + private: + T _instance; + }; /** * @brief Manager for singleton components in the ECS. @@ -87,8 +85,8 @@ 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: + class SingletonComponentManager { + public: /** * @brief Registers a singleton component in place by forwarding constructor arguments. * @@ -104,65 +102,69 @@ namespace nexo::ecs { using Decayed = std::decay_t; std::type_index typeName(typeid(Decayed)); if (m_singletonComponents.contains(typeName)) { - LOG(NEXO_WARN, "ECS::SingletonComponentManager::registerSingletonComponent: trying to register a singleton component more than once"); - return; + 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)...)}); + 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 std::type_index typeName(typeid(T)); + if (!m_singletonComponents.contains(typeName)) + THROW_EXCEPTION(SingletonComponentNotRegistered); + + auto componentPtr = dynamic_cast*>(m_singletonComponents[typeName].get()); + + return componentPtr->getInstance(); } - /** - * @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 std::type_index typeName(typeid(T)); - 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 std::type_index typeName(typeid(T)); - 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 std::type_index typeName(typeid(T)); - if (!m_singletonComponents.contains(typeName)) - THROW_EXCEPTION(SingletonComponentNotRegistered); - m_singletonComponents.erase(typeName); - } - private: - std::unordered_map> m_singletonComponents{}; - }; + /** + * @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 std::type_index typeName(typeid(T)); + 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 std::type_index typeName(typeid(T)); + if (!m_singletonComponents.contains(typeName)) + THROW_EXCEPTION(SingletonComponentNotRegistered); + + m_singletonComponents.erase(typeName); + } + private: + std::unordered_map> m_singletonComponents{}; + }; } diff --git a/engine/src/ecs/SingletonComponentMixin.hpp b/engine/src/ecs/SingletonComponentMixin.hpp index f1f9a172d..74029b920 100644 --- a/engine/src/ecs/SingletonComponentMixin.hpp +++ b/engine/src/ecs/SingletonComponentMixin.hpp @@ -29,86 +29,85 @@ namespace nexo::ecs { */ template class SingletonComponentMixin { - private: - // Helper to check if a singleton component has read access in the parameter pack - template - struct HasReadSingletonAccessImpl { - static constexpr bool value = (... || (IsReadSingleton::value && - std::is_same_v)); - }; + private: + // Helper to check if a singleton component has read access in the parameter pack + template + struct HasReadSingletonAccessImpl { + static constexpr bool value = (... || (IsReadSingleton::value && + std::is_same_v)); + }; - protected: - // Cache of singleton components for faster access - std::unordered_map> m_singletonComponents; + protected: + // Cache of singleton components for faster access + std::unordered_map> m_singletonComponents; - /** - * @brief Initializes singleton components for this system - */ - void initializeSingletonComponents() - { - // Cache singleton components for faster access - (cacheSingletonComponent(), ...); - } + /** + * @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() - { - try { - auto* derived = static_cast(this); - std::shared_ptr instance = derived->coord->template getRawSingletonComponent(); - m_singletonComponents[getTypeIndex()] = instance; - } catch (const nexo::ecs::SingletonComponentNotRegistered&) { - // Singleton not registered yet, we'll try again when getSingleton is called - } - } + /** + * @brief Caches a specific singleton component + * + * @tparam T The singleton component type + */ + template + void cacheSingletonComponent() + { + try { + auto* derived = static_cast(this); + std::shared_ptr instance = derived->coord->template getRawSingletonComponent(); + m_singletonComponents[getTypeIndex()] = instance; + } catch (const nexo::ecs::SingletonComponentNotRegistered&) { + // Singleton not registered yet, we'll try again when getSingleton is called + } + } - public: - // Convenience function to check singleton read access - template - static constexpr bool hasReadSingletonAccess() - { - return HasReadSingletonAccessImpl::value; - } + public: + // Convenience function to check singleton read 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 - typename std::conditional(), const T&, T&>::type - getSingleton() - { - auto typeIndex = getTypeIndex(); + /** + * @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() + { + const std::type_index typeIndex = getTypeIndex(); - if (!m_singletonComponents.contains(typeIndex)) { - // Late binding in case the singleton was registered after system creation - cacheSingletonComponent(); - } + if (!m_singletonComponents.contains(typeIndex)) { + // Late binding in case the singleton was registered after system creation + cacheSingletonComponent(); + } - // Get the stored singleton component wrapper - auto& singletonComponentPtr = m_singletonComponents[typeIndex]; - auto* componentWrapper = dynamic_cast*>(singletonComponentPtr.get()); + // Get the stored singleton component wrapper + auto& singletonComponentPtr = m_singletonComponents[typeIndex]; + auto* componentWrapper = dynamic_cast*>(singletonComponentPtr.get()); - if (!componentWrapper) - THROW_EXCEPTION(SingletonComponentNotRegistered); + if (!componentWrapper) + THROW_EXCEPTION(SingletonComponentNotRegistered); - // Return the reference with appropriate constness - if constexpr (hasReadSingletonAccess()) { - // For read-only access, return const reference - return const_cast(componentWrapper->getInstance()); - } else { - // For read-write access, return non-const reference - return componentWrapper->getInstance(); - } - } + // Return the reference with appropriate constness + if constexpr (hasReadSingletonAccess()) { + // For read-only access, return const reference + return const_cast(componentWrapper->getInstance()); + } else { + // For read-write access, return non-const reference + return componentWrapper->getInstance(); + } + } }; } diff --git a/engine/src/ecs/System.cpp b/engine/src/ecs/System.cpp index e8b1462d0..7f9b4344c 100644 --- a/engine/src/ecs/System.cpp +++ b/engine/src/ecs/System.cpp @@ -14,45 +14,45 @@ #include "System.hpp" +#include + namespace nexo::ecs { - void SparseSet::insert(Entity entity) - { - if (contains(entity)) - { - LOG(NEXO_WARN, "Entity {} already added to the sparse set", entity); - return; - } + void SparseSet::insert(Entity entity) + { + if (contains(entity)) + { + LOG(NEXO_WARN, "Entity {} already added to the sparse set", entity); + return; + } sparse[entity] = dense.size(); dense.push_back(entity); - } + } - void SparseSet::erase(Entity entity) - { - if (!contains(entity)) - { - LOG(NEXO_WARN, "Entity {} does not exist in the sparse set", entity); - return; - } + void SparseSet::erase(Entity entity) + { + if (!contains(entity)) + { + LOG(NEXO_WARN, "Entity {} does not exist in the sparse set", entity); + return; + } - size_t index = sparse[entity]; - size_t lastIndex = dense.size() - 1; - Entity lastEntity = dense[lastIndex]; + const size_t index = sparse[entity]; + const size_t lastIndex = dense.size() - 1; + const Entity lastEntity = dense[lastIndex]; dense[index] = lastEntity; sparse[lastEntity] = index; dense.pop_back(); sparse.erase(entity); - } + } void SystemManager::entityDestroyed(const Entity entity, const Signature signature) const { - for (const auto &[fst, snd] : m_querySystems) { - auto const &type = fst; - auto const &system = snd; - if (auto const &systemSignature = system->getSignature(); (signature & systemSignature) == systemSignature) - system->entities.erase(entity); + for (const auto& system : std::ranges::views::values(m_querySystems)) { + if (const Signature &systemSignature = system->getSignature(); (signature & systemSignature) == systemSignature) + system->entities.erase(entity); } } @@ -60,8 +60,8 @@ namespace nexo::ecs { const Signature oldSignature, const Signature newSignature) { - for (auto& [type, system] : m_querySystems) { - const auto systemSignature = system->getSignature(); + for (const auto& system : std::ranges::views::values(m_querySystems)) { + const Signature systemSignature = system->getSignature(); // Check if entity qualifies now but did not qualify before. if (((oldSignature & systemSignature) != systemSignature) && ((newSignature & systemSignature) == systemSignature)) { diff --git a/engine/src/ecs/System.hpp b/engine/src/ecs/System.hpp index 142afff70..2b841547f 100644 --- a/engine/src/ecs/System.hpp +++ b/engine/src/ecs/System.hpp @@ -28,7 +28,7 @@ namespace nexo::ecs { namespace nexo::ecs { - class SparseSet { + class SparseSet { public: void insert(Entity entity); @@ -36,7 +36,7 @@ namespace nexo::ecs { bool empty() const { return dense.empty(); } - bool contains(Entity entity) const { return sparse.find(entity) != sparse.end(); } + bool contains(const Entity entity) const { return sparse.contains(entity); } size_t size() const { return dense.size(); } @@ -59,9 +59,8 @@ namespace nexo::ecs { */ class System { public: - virtual ~System() = default; + virtual ~System() = default; static std::shared_ptr coord; - }; /** @@ -70,7 +69,7 @@ namespace nexo::ecs { */ class AQuerySystem : public System { public: - virtual ~AQuerySystem() = default; + ~AQuerySystem() override = default; virtual const Signature &getSignature() const = 0; SparseSet entities; @@ -83,7 +82,7 @@ namespace nexo::ecs { */ class AGroupSystem : public System { public: - virtual ~AGroupSystem() = default; + ~AGroupSystem() override = default; }; /** @@ -105,7 +104,7 @@ namespace nexo::ecs { template std::shared_ptr registerQuerySystem(Args&&... args) { - static_assert(std::is_base_of::value, "T must derive from AQuerySystem"); + static_assert(std::is_base_of_v, "T must derive from AQuerySystem"); std::type_index typeName(typeid(T)); if (m_querySystems.contains(typeName)) @@ -122,7 +121,7 @@ namespace nexo::ecs { template std::shared_ptr registerGroupSystem(Args&&... args) { - static_assert(std::is_base_of::value, "T must derive from AGroupSystem"); + static_assert(std::is_base_of_v, "T must derive from AGroupSystem"); std::type_index typeName(typeid(T)); if (m_groupSystems.contains(typeName)) diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index 5a778be9d..508f0c68a 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -21,8 +21,6 @@ #include "core/event/KeyCodes.hpp" #include "Application.hpp" #include "core/event/WindowEvent.hpp" -#include "core/scene/Scene.hpp" -#include "math/Vector.hpp" #include #include @@ -35,11 +33,11 @@ namespace nexo::system { const auto sceneRendered = static_cast(renderContext.sceneRendered); - auto scenePartition = m_group->getPartitionView( + const auto scenePartition = m_group->getPartitionView( [](const components::SceneTag& tag) { return tag.id; } ); - const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); //TODO: Throw exception here ? if (!partition) @@ -48,36 +46,36 @@ namespace nexo::system { const auto transformComponentArray = get(); const auto entitySpan = m_group->entities(); - for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { const auto &cameraComponent = cameraSpan[i]; const auto &transformComponent = transformComponentArray->get(entitySpan[i]); glm::mat4 projectionMatrix = cameraComponent.getProjectionMatrix(); glm::mat4 viewMatrix = cameraComponent.getViewMatrix(transformComponent); - glm::mat4 viewProjectionMatrix = projectionMatrix * viewMatrix; + const glm::mat4 viewProjectionMatrix = projectionMatrix * viewMatrix; components::CameraContext context{viewProjectionMatrix, transformComponent.pos, cameraComponent.clearColor, cameraComponent.m_renderTarget}; renderContext.cameras.push(context); } } - PerspectiveCameraControllerSystem::PerspectiveCameraControllerSystem() : QuerySystem() + PerspectiveCameraControllerSystem::PerspectiveCameraControllerSystem() { - Application::getInstance().getEventManager()->registerListener(this); - Application::getInstance().getEventManager()->registerListener(this); + Application::getInstance().getEventManager()->registerListener(this); + Application::getInstance().getEventManager()->registerListener(this); } void PerspectiveCameraControllerSystem::update(const Timestep ts) - { - const auto &renderContext = getSingleton(); + { + const auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - const auto deltaTime = static_cast(ts); - constexpr float translationSpeed = 5.0f; + const auto deltaTime = static_cast(ts); - for (auto entity : entities) + for (const ecs::Entity entity : entities) { + constexpr float translationSpeed = 5.0f; auto &sceneTag = getComponent(entity); if (!sceneTag.isActive || sceneTag.id != sceneRendered) continue; @@ -87,82 +85,82 @@ namespace nexo::system { cameraComponent.resizing = false; 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 * translationSpeed * deltaTime; // Forward - if (event::isKeyPressed(NEXO_KEY_S)) - transform.pos -= front * translationSpeed * deltaTime; // Backward - if (event::isKeyPressed(NEXO_KEY_Q)) - transform.pos -= right * translationSpeed * deltaTime; // Left - if (event::isKeyPressed(NEXO_KEY_D)) - transform.pos += right * translationSpeed * deltaTime; // Right - if (event::isKeyPressed(NEXO_KEY_SPACE)) - transform.pos += up * translationSpeed * deltaTime; // Up - if (event::isKeyPressed(NEXO_KEY_TAB)) - transform.pos -= up * translationSpeed * deltaTime; // Down + 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 * translationSpeed * deltaTime; // Forward + if (event::isKeyPressed(NEXO_KEY_S)) + transform.pos -= front * translationSpeed * deltaTime; // Backward + if (event::isKeyPressed(NEXO_KEY_Q)) + transform.pos -= right * translationSpeed * deltaTime; // Left + if (event::isKeyPressed(NEXO_KEY_D)) + transform.pos += right * translationSpeed * deltaTime; // Right + if (event::isKeyPressed(NEXO_KEY_SPACE)) + transform.pos += up * translationSpeed * deltaTime; // Up + if (event::isKeyPressed(NEXO_KEY_TAB)) + transform.pos -= up * translationSpeed * deltaTime; // Down } - } + } - void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseScroll &event) - { - const auto &renderContext = getSingleton(); + void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseScroll &event) + { + const auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - constexpr float zoomSpeed = 0.5f; - for (auto entity : entities) + for (const ecs::Entity entity : entities) { + constexpr float zoomSpeed = 0.5f; auto &sceneTag = getComponent(entity); if (!sceneTag.isActive || sceneTag.id != sceneRendered) 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; + transform.pos += front * event.y * zoomSpeed; + event.consumed = true; } - } + } - void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseMove &event) - { - auto const &renderContext = getSingleton(); + void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseMove &event) + { + auto const &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - glm::vec2 currentMousePosition(event.x, event.y); - for (auto entity : entities) - { - auto &controller = getComponent(entity); - const glm::vec2 mouseDelta = (currentMousePosition - controller.lastMousePosition) * controller.mouseSensitivity; - controller.lastMousePosition = currentMousePosition; - const auto &sceneTag = getComponent(entity); + const glm::vec2 currentMousePosition(event.x, event.y); + for (const ecs::Entity entity : entities) + { + auto &controller = getComponent(entity); + const glm::vec2 mouseDelta = (currentMousePosition - controller.lastMousePosition) * controller.mouseSensitivity; + controller.lastMousePosition = currentMousePosition; + const auto &sceneTag = getComponent(entity); if (!sceneTag.isActive || sceneTag.id != sceneRendered) continue; - const auto &cameraComponent = getComponent(entity); - if (cameraComponent.resizing || !event::isMouseDown(NEXO_MOUSE_LEFT)) - continue; - - controller.yaw += -mouseDelta.x; - controller.pitch += -mouseDelta.y; - - // Clamp pitch to avoid flipping - if (controller.pitch > 89.0f) - controller.pitch = 89.0f; - if (controller.pitch < -89.0f) - controller.pitch = -89.0f; - - // Rebuild the quaternion from yaw and pitch. - glm::quat qPitch = glm::angleAxis(glm::radians(controller.pitch), glm::vec3(1.0f, 0.0f, 0.0f)); - glm::quat qYaw = glm::angleAxis(glm::radians(controller.yaw), glm::vec3(0.0f, 1.0f, 0.0f)); - - auto &transform = getComponent(entity); - transform.quat = glm::normalize(qYaw * qPitch); - event.consumed = true; + const auto &cameraComponent = getComponent(entity); + if (cameraComponent.resizing || !event::isMouseDown(NEXO_MOUSE_LEFT)) + continue; + + controller.yaw += -mouseDelta.x; + controller.pitch += -mouseDelta.y; + + // Clamp pitch to avoid flipping + if (controller.pitch > 89.0f) + controller.pitch = 89.0f; + if (controller.pitch < -89.0f) + controller.pitch = -89.0f; + + // Rebuild the quaternion from yaw and pitch. + glm::quat qPitch = glm::angleAxis(glm::radians(controller.pitch), glm::vec3(1.0f, 0.0f, 0.0f)); + glm::quat qYaw = glm::angleAxis(glm::radians(controller.yaw), glm::vec3(0.0f, 1.0f, 0.0f)); + + auto &transform = getComponent(entity); + transform.quat = glm::normalize(qYaw * qPitch); + event.consumed = true; } } @@ -179,49 +177,49 @@ namespace nexo::system { return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - constexpr float zoomSpeed = 0.5f; - for (auto entity : entities) - { - auto &tag = getComponent(entity); - if (!tag.isActive || sceneRendered != tag.id) - continue; - auto &target = getComponent(entity); - target.distance -= event.y * zoomSpeed; - if (target.distance < 0.1f) - target.distance = 0.1f; + for (const ecs::Entity entity : entities) + { + constexpr float zoomSpeed = 0.5f; + auto &tag = getComponent(entity); + if (!tag.isActive || sceneRendered != tag.id) + 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); + 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); + 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; + offset = glm::normalize(offset) * target.distance; - transformCamera.pos = transformTarget.pos + offset; + 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))); + glm::vec3 newFront = glm::normalize(transformTarget.pos - transformCamera.pos); + transformCamera.quat = glm::normalize(glm::quatLookAt(newFront, glm::vec3(0,1,0))); - event.consumed = true; + event.consumed = true; } } void PerspectiveCameraTargetSystem::handleEvent(event::EventMouseMove &event) { - const auto &renderContext = getSingleton(); + const auto &renderContext = getSingleton(); if (renderContext.sceneRendered == -1) return; const auto sceneRendered = static_cast(renderContext.sceneRendered); - glm::vec2 currentMousePosition(event.x, event.y); + glm::vec2 currentMousePosition(event.x, event.y); - for (auto entity : entities) - { + for (const ecs::Entity entity : entities) + { const auto &sceneTag = getComponent(entity); const auto &cameraComponent = getComponent(entity); auto &targetComponent = getComponent(entity); @@ -231,46 +229,46 @@ namespace nexo::system { continue; } - auto &transformCameraComponent = coord->getComponent(entity); - const auto &transformTargetComponent = coord->getComponent(targetComponent.targetEntity); + auto &transformCameraComponent = coord->getComponent(entity); + const auto &transformTargetComponent = coord->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 = [](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 = [](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; 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; + targetComponent.lastMousePosition = currentMousePosition; event.consumed = true; - } + } } } diff --git a/engine/src/systems/CameraSystem.hpp b/engine/src/systems/CameraSystem.hpp index 98340cc17..6930a1426 100644 --- a/engine/src/systems/CameraSystem.hpp +++ b/engine/src/systems/CameraSystem.hpp @@ -39,7 +39,7 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class CameraContextSystem : public ecs::GroupSystem< + class CameraContextSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read>, ecs::NonOwned< @@ -63,15 +63,15 @@ namespace nexo::system { * - components::CameraComponent * - components::TransformComponent */ - class PerspectiveCameraControllerSystem : public ecs::QuerySystem< - ecs::Write, - ecs::Write, - ecs::Read, - ecs::Write, - ecs::ReadSingleton>, - LISTENS_TO( - event::EventMouseScroll, - event::EventMouseMove) { + 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); @@ -93,15 +93,15 @@ namespace nexo::system { * - components::CameraComponent * - components::TransformComponent */ - class PerspectiveCameraTargetSystem : public ecs::QuerySystem< - ecs::Write, - ecs::Write, - ecs::Read, - ecs::Write, - ecs::ReadSingleton>, - LISTENS_TO( - event::EventMouseScroll, - event::EventMouseMove) { + 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; diff --git a/engine/src/systems/LightSystem.cpp b/engine/src/systems/LightSystem.cpp index 00c7b5d63..6df39e657 100644 --- a/engine/src/systems/LightSystem.cpp +++ b/engine/src/systems/LightSystem.cpp @@ -15,7 +15,7 @@ #include "LightSystem.hpp" namespace nexo::system { - void LightSystem::update() + void LightSystem::update() const { m_ambientLightSystem->update(); m_directionalLightSystem->update(); diff --git a/engine/src/systems/LightSystem.hpp b/engine/src/systems/LightSystem.hpp index 379d14a1a..8428e2429 100644 --- a/engine/src/systems/LightSystem.hpp +++ b/engine/src/systems/LightSystem.hpp @@ -43,7 +43,7 @@ namespace nexo::system { m_pointLightSystem(pointSystem), m_spotLightSystem(spotSystem) {} - void update(); + void update() const; private: std::shared_ptr m_ambientLightSystem = nullptr; std::shared_ptr m_directionalLightSystem = nullptr; diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index d85657757..89f8ccb97 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -22,7 +22,6 @@ #include "renderer/RenderCommand.hpp" #include "ecs/Coordinator.hpp" -#include #include @@ -43,7 +42,7 @@ namespace nexo::system { * - pointLights (and pointLightCount) * - spotLights (and spotLightCount) */ - static void setupLights(std::shared_ptr shader, const components::LightContext& lightContext) + static void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext) { shader->bind(); shader->setUniformFloat3("ambientLight", lightContext.ambientLight); @@ -93,11 +92,11 @@ namespace nexo::system { setupLights(renderContext.renderer3D.getShader(), renderContext.sceneLights); - auto scenePartition = m_group->getPartitionView( + const auto scenePartition = m_group->getPartitionView( [](const components::SceneTag& tag) { return tag.id; } ); - const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); //TODO: Throw exception here ? if (!partition) @@ -105,11 +104,11 @@ namespace nexo::system { const auto transformSpan = get(); const auto renderSpan = get(); - const auto entitySpan = m_group->entities(); + const std::span entitySpan = m_group->entities(); while (!renderContext.cameras.empty()) { - const auto &camera = renderContext.cameras.front(); + const components::CameraContext &camera = renderContext.cameras.front(); if (camera.renderTarget != nullptr) { camera.renderTarget->bind(); @@ -117,20 +116,19 @@ namespace nexo::system { renderer::RenderCommand::setClearColor(camera.clearColor); renderer::RenderCommand::clear(); camera.renderTarget->clearAttachment(1, -1); - } - for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { - const auto &transform = transformSpan[i]; - const auto &render = renderSpan[i]; - const ecs::Entity entity = entitySpan[i]; - if (render.isRendered) - { - renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition); + const auto &transform = transformSpan[i]; + const auto &render = renderSpan[i]; + const ecs::Entity entity = entitySpan[i]; + if (render.isRendered) + { + renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition); auto context = std::make_shared(); context->renderer3D = renderContext.renderer3D; - render.draw(context, transform, entity); + render.draw(context, transform, static_cast(entity)); renderContext.renderer3D.endScene(); } } @@ -142,6 +140,5 @@ namespace nexo::system { } renderContext.cameras.pop(); } - } } diff --git a/engine/src/systems/RenderSystem.hpp b/engine/src/systems/RenderSystem.hpp index a4a3325a8..eb3d93108 100644 --- a/engine/src/systems/RenderSystem.hpp +++ b/engine/src/systems/RenderSystem.hpp @@ -19,7 +19,6 @@ #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" #include "components/Transform.hpp" -#include "ecs/System.hpp" namespace nexo::system { @@ -40,7 +39,7 @@ namespace nexo::system { * - components::TransformComponent * - components::RenderComponent */ - class RenderSystem : public ecs::GroupSystem< + class RenderSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read, ecs::Read>, diff --git a/engine/src/systems/lights/AmbientLightSystem.cpp b/engine/src/systems/lights/AmbientLightSystem.cpp index b636fb03f..df4d31ae9 100644 --- a/engine/src/systems/lights/AmbientLightSystem.cpp +++ b/engine/src/systems/lights/AmbientLightSystem.cpp @@ -26,22 +26,19 @@ namespace nexo::system { const auto sceneRendered = static_cast(renderContext.sceneRendered); - auto scenePartition = m_group->getPartitionView( + const auto scenePartition = m_group->getPartitionView( [](const components::SceneTag& tag) { return tag.id; } ); - const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); //TODO: Throw exception here ? if (!partition) return; const auto ambientSpan = get(); + if (ambientSpan.size()) + renderContext.sceneLights.ambientLight = ambientSpan[0].color; - for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) - { - renderContext.sceneLights.ambientLight = ambientSpan[i].color; - break; - } } } diff --git a/engine/src/systems/lights/AmbientLightSystem.hpp b/engine/src/systems/lights/AmbientLightSystem.hpp index db437181f..c944ef1c8 100644 --- a/engine/src/systems/lights/AmbientLightSystem.hpp +++ b/engine/src/systems/lights/AmbientLightSystem.hpp @@ -14,7 +14,6 @@ #pragma once #include "GroupSystem.hpp" -#include "ecs/System.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" @@ -33,7 +32,7 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class AmbientLightSystem : public ecs::GroupSystem< + class AmbientLightSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read>, ecs::NonOwned< diff --git a/engine/src/systems/lights/DirectionalLightsSystem.cpp b/engine/src/systems/lights/DirectionalLightsSystem.cpp index 726250baf..ff64e20f1 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.cpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.cpp @@ -27,11 +27,11 @@ namespace nexo::system { const auto sceneRendered = static_cast(renderContext.sceneRendered); - auto scenePartition = m_group->getPartitionView( + const auto scenePartition = m_group->getPartitionView( [](const components::SceneTag& tag) { return tag.id; } ); - const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); //TODO: Throw exception here ? if (!partition) @@ -39,9 +39,10 @@ namespace nexo::system { const auto directionalLightSpan = get(); - for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { - renderContext.sceneLights.directionalLights[renderContext.sceneLights.directionalLightCount++] = directionalLightSpan[i]; + renderContext.sceneLights.directionalLights[renderContext.sceneLights.directionalLightCount] = directionalLightSpan[i]; + renderContext.sceneLights.directionalLightCount++; } } } diff --git a/engine/src/systems/lights/DirectionalLightsSystem.hpp b/engine/src/systems/lights/DirectionalLightsSystem.hpp index c103316aa..d19cb3b5c 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.hpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.hpp @@ -34,7 +34,7 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class DirectionalLightsSystem : public ecs::GroupSystem< + class DirectionalLightsSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read>, ecs::NonOwned< diff --git a/engine/src/systems/lights/PointLightsSystem.cpp b/engine/src/systems/lights/PointLightsSystem.cpp index 0fe2756bb..183ce063f 100644 --- a/engine/src/systems/lights/PointLightsSystem.cpp +++ b/engine/src/systems/lights/PointLightsSystem.cpp @@ -27,11 +27,11 @@ namespace nexo::system { const auto sceneRendered = static_cast(renderContext.sceneRendered); - auto scenePartition = m_group->getPartitionView( + const auto scenePartition = m_group->getPartitionView( [](const components::SceneTag& tag) { return tag.id; } ); - const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); //TODO: Throw exception here ? if (!partition) @@ -39,9 +39,10 @@ namespace nexo::system { const auto pointLightSpan = get(); - for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { - renderContext.sceneLights.pointLights[renderContext.sceneLights.pointLightCount++] = pointLightSpan[i]; + renderContext.sceneLights.pointLights[renderContext.sceneLights.pointLightCount] = pointLightSpan[i]; + renderContext.sceneLights.pointLightCount++; } } } diff --git a/engine/src/systems/lights/PointLightsSystem.hpp b/engine/src/systems/lights/PointLightsSystem.hpp index feca65170..89e3aa154 100644 --- a/engine/src/systems/lights/PointLightsSystem.hpp +++ b/engine/src/systems/lights/PointLightsSystem.hpp @@ -34,7 +34,7 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class PointLightsSystem : public ecs::GroupSystem< + class PointLightsSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read>, ecs::NonOwned< diff --git a/engine/src/systems/lights/SpotLightsSystem.cpp b/engine/src/systems/lights/SpotLightsSystem.cpp index 2030a2795..a37e01aff 100644 --- a/engine/src/systems/lights/SpotLightsSystem.cpp +++ b/engine/src/systems/lights/SpotLightsSystem.cpp @@ -18,7 +18,6 @@ #include "components/SceneComponents.hpp" #include "ecs/Coordinator.hpp" - namespace nexo::system { void SpotLightsSystem::update() { @@ -28,11 +27,11 @@ namespace nexo::system { const auto sceneRendered = static_cast(renderContext.sceneRendered); - auto scenePartition = m_group->getPartitionView( + const auto scenePartition = m_group->getPartitionView( [](const components::SceneTag& tag) { return tag.id; } ); - const auto *partition = scenePartition.getPartition(renderContext.sceneRendered); + const auto *partition = scenePartition.getPartition(sceneRendered); //TODO: Throw exception here ? if (!partition) @@ -40,9 +39,10 @@ namespace nexo::system { const auto spotLightSpan = get(); - for (unsigned int i = partition->startIndex; i < partition->startIndex + partition->count; ++i) + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { - renderContext.sceneLights.spotLights[renderContext.sceneLights.spotLightCount++] = spotLightSpan[i]; + renderContext.sceneLights.spotLights[renderContext.sceneLights.spotLightCount] = spotLightSpan[i]; + renderContext.sceneLights.spotLightCount++; } } } diff --git a/engine/src/systems/lights/SpotLightsSystem.hpp b/engine/src/systems/lights/SpotLightsSystem.hpp index 4e6b5f1cf..41ec0ee54 100644 --- a/engine/src/systems/lights/SpotLightsSystem.hpp +++ b/engine/src/systems/lights/SpotLightsSystem.hpp @@ -14,8 +14,6 @@ #pragma once #include "GroupSystem.hpp" -#include "ecs/System.hpp" -#include "ecs/GroupSystem.hpp" #include "components/Light.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" @@ -35,7 +33,7 @@ namespace nexo::system { * @note Required Singleton Component: * - components::RenderContext */ - class SpotLightsSystem : public ecs::GroupSystem< + class SpotLightsSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read>, ecs::NonOwned< diff --git a/tests/ecs/ComponentArray.test.cpp b/tests/ecs/ComponentArray.test.cpp index 5831fac91..16ce41501 100644 --- a/tests/ecs/ComponentArray.test.cpp +++ b/tests/ecs/ComponentArray.test.cpp @@ -265,7 +265,6 @@ namespace nexo::ecs { newComp.value = i * 10; componentArray->insert(i, newComp); } - size_t largeSize = componentArray->size(); // Now remove many elements to trigger shrink for (Entity i = 0; i < 15; ++i) { diff --git a/tests/ecs/Group.test.cpp b/tests/ecs/Group.test.cpp index 8206d54a3..63b3dac1d 100644 --- a/tests/ecs/Group.test.cpp +++ b/tests/ecs/Group.test.cpp @@ -105,7 +105,6 @@ namespace nexo::ecs { template auto createGroup(auto nonOwned) { using OwnedTuple = std::tuple>...>; - using NonOwnedTuple = decltype(std::make_tuple(nonOwned)); auto ownedArrays = std::make_tuple(std::static_pointer_cast>(getNthArray())...); auto nonOwnedArrays = nonOwned; @@ -249,11 +248,7 @@ namespace nexo::ecs { TEST_F(GroupTest, IteratorEmptyGroup) { auto group = createGroup(std::make_tuple(velocityArray)); - // Iterate - should not execute the loop body - size_t count = 0; - for (auto [entity, position] : *group) { - count++; - } + size_t count = std::distance(group->begin(), group->end()); EXPECT_EQ(count, 0); } @@ -288,7 +283,7 @@ namespace nexo::ecs { // Use eachInRange to process a subset of entities int callCount = 0; - group->eachInRange(1, 2, [&callCount](Entity e, PositionComponent& pos, VelocityComponent& vel, TagComponent& tag) { + group->eachInRange(1, 2, [&callCount](Entity e, PositionComponent&, VelocityComponent&, TagComponent&) { EXPECT_GE(e, 1); EXPECT_LE(e, 3); callCount++; @@ -420,21 +415,21 @@ namespace nexo::ecs { // Check entities in each partition int countCategory0 = 0; - partitionView.each(0, [&countCategory0](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + partitionView.each(0, [&countCategory0](Entity, PositionComponent&, TagComponent& tag, HealthComponent&) { EXPECT_EQ(tag.category, 0); countCategory0++; }); EXPECT_EQ(countCategory0, 2); // Entities 0 and 3 have category 0 int countCategory1 = 0; - partitionView.each(1, [&countCategory1](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + partitionView.each(1, [&countCategory1](Entity, PositionComponent&, TagComponent& tag, HealthComponent&) { EXPECT_EQ(tag.category, 1); countCategory1++; }); EXPECT_EQ(countCategory1, 2); // Entities 1 and 4 have category 1 int countCategory2 = 0; - partitionView.each(2, [&countCategory2](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + partitionView.each(2, [&countCategory2](Entity, PositionComponent&, TagComponent& tag, HealthComponent&) { EXPECT_EQ(tag.category, 2); countCategory2++; }); @@ -494,7 +489,7 @@ namespace nexo::ecs { // Try to iterate through a non-existent partition int callCount = 0; - partitionView.each(99, [&callCount](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + partitionView.each(99, [&callCount](Entity, PositionComponent&, TagComponent&, HealthComponent&) { callCount++; }); @@ -521,14 +516,14 @@ namespace nexo::ecs { // Check each partition int evenCount = 0; - partitionView.each(0, [&evenCount](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + partitionView.each(0, [&evenCount](Entity e, PositionComponent&, TagComponent&, HealthComponent&) { EXPECT_EQ(e % 2, 0); // Should be even evenCount++; }); EXPECT_EQ(evenCount, 3); // Entities 0, 2, 4 int oddCount = 0; - partitionView.each(1, [&oddCount](Entity e, PositionComponent& pos, TagComponent& tag, HealthComponent& health) { + partitionView.each(1, [&oddCount](Entity e, PositionComponent&, TagComponent&, HealthComponent&) { EXPECT_EQ(e % 2, 1); // Should be odd oddCount++; }); @@ -547,7 +542,7 @@ namespace nexo::ecs { // Try using each method int callCount = 0; - group->each([&callCount](Entity e, PositionComponent& pos, VelocityComponent& vel) { + group->each([&callCount](Entity, PositionComponent&, VelocityComponent&) { callCount++; }); EXPECT_EQ(callCount, 0); diff --git a/tests/ecs/QuerySystem.test.cpp b/tests/ecs/QuerySystem.test.cpp index 4deb2e667..3b217b736 100644 --- a/tests/ecs/QuerySystem.test.cpp +++ b/tests/ecs/QuerySystem.test.cpp @@ -147,7 +147,6 @@ namespace nexo::ecs { const auto& settings = getSingleton(); for (Entity entity : entities) { - const Position& pos = getComponent(entity); Velocity& vel = getComponent(entity); // Apply game speed scaling diff --git a/tests/engine/components/Camera.test.cpp b/tests/engine/components/Camera.test.cpp index b95f5af30..246a3b547 100644 --- a/tests/engine/components/Camera.test.cpp +++ b/tests/engine/components/Camera.test.cpp @@ -26,14 +26,14 @@ class DummyFramebuffer : public nexo::renderer::Framebuffer { public: void bind() override {} void unbind() override {} - void setClearColor(const glm::vec4 &color) override {} + void setClearColor(const glm::vec4 &) override {} unsigned int getFramebufferId() const override { return 0; } - void resize(unsigned int width, unsigned int height) override {} - void getPixelWrapper(unsigned int attachmentIndex, int x, int y, void *result, const std::type_info &ti) const override {} - void clearAttachmentWrapper(unsigned int attachmentIndex, const void *value, const std::type_info &ti) const override {} + void resize(unsigned int, unsigned int ) override {} + void getPixelWrapper(unsigned int, int, int, void *, const std::type_info &) const override {} + void clearAttachmentWrapper(unsigned int, const void *, const std::type_info &) const override {} [[nodiscard]] nexo::renderer::FramebufferSpecs &getSpecs() override { static nexo::renderer::FramebufferSpecs specs; return specs; } [[nodiscard]] const nexo::renderer::FramebufferSpecs &getSpecs() const override { static nexo::renderer::FramebufferSpecs specs; return specs; } - [[nodiscard]] unsigned int getColorAttachmentId(unsigned int index = 0) const override { return 0; } + [[nodiscard]] unsigned int getColorAttachmentId(unsigned int) const override { return 0; } unsigned int getDepthAttachmentId() const override { return 0; } }; diff --git a/tests/renderer/Buffer.test.cpp b/tests/renderer/Buffer.test.cpp index 328a2f97c..6da261674 100644 --- a/tests/renderer/Buffer.test.cpp +++ b/tests/renderer/Buffer.test.cpp @@ -206,7 +206,7 @@ namespace nexo::renderer { }; int count = 0; - for (auto& element : layout) { + for (auto& _ : layout) { count++; } EXPECT_EQ(count, 2); diff --git a/tests/renderer/Renderer2D.test.cpp b/tests/renderer/Renderer2D.test.cpp index b26b6a5b7..eed8992e5 100644 --- a/tests/renderer/Renderer2D.test.cpp +++ b/tests/renderer/Renderer2D.test.cpp @@ -487,7 +487,7 @@ namespace nexo::renderer { renderer2D->shutdown(); // Expect RendererNotInitialized exception - EXPECT_THROW(renderer2D->getStats(), RendererNotInitialized); + EXPECT_THROW(static_cast(renderer2D->getStats()), RendererNotInitialized); // Re-init for TearDown function renderer2D->init(); } From cd727fd3c86ed003d9c3227d6e23eb2d7f588921 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 10 Apr 2025 01:50:51 +0900 Subject: [PATCH 059/450] style(ecs-opti): fixed indent --- editor/src/DocumentWindows/EditorScene.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 143b29ab8..a49d30cd9 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -105,7 +105,7 @@ namespace nexo::editor { void EditorScene::deleteCamera(const ecs::Entity cameraId) { - if (cameraId == m_activeCamera) + if (static_cast(cameraId) == m_activeCamera) m_activeCamera = -1; m_cameras.erase(cameraId); if (!m_cameras.empty()) @@ -253,9 +253,9 @@ namespace nexo::editor { // Mouse is not inside the viewport if (!(mx >= 0 && my >= 0 && mx < m_viewSize.x && my < m_viewSize.y)) - return; + return; - cameraComponent.m_renderTarget->bind(); + cameraComponent.m_renderTarget->bind(); int data = cameraComponent.m_renderTarget->getPixel(1, static_cast(mx), static_cast(my)); cameraComponent.m_renderTarget->unbind(); if (data == -1) From 1f04fd12811ac1bc678663df91a2f43b81cc63a6 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 10 Apr 2025 02:44:46 +0900 Subject: [PATCH 060/450] docs(ecs-opti): updated docstrings --- .idea/sonarlint.xml | 8 -- engine/src/ecs/ComponentArray.hpp | 23 ++-- engine/src/ecs/Coordinator.hpp | 22 +++- engine/src/ecs/Definitions.hpp | 58 ++++++++++ engine/src/ecs/Entity.hpp | 11 ++ engine/src/ecs/Group.hpp | 32 ++++++ engine/src/ecs/GroupSystem.hpp | 75 +++++++++++-- engine/src/ecs/QuerySystem.hpp | 44 +++++++- engine/src/ecs/SingletonComponent.hpp | 5 + engine/src/ecs/SingletonComponentMixin.hpp | 23 +++- engine/src/ecs/System.hpp | 106 ++++++++++++++++-- engine/src/systems/CameraSystem.hpp | 76 ++++++++----- engine/src/systems/RenderSystem.hpp | 32 +++--- .../src/systems/lights/AmbientLightSystem.hpp | 25 +++-- .../lights/DirectionalLightsSystem.hpp | 25 +++-- .../src/systems/lights/PointLightsSystem.hpp | 25 +++-- .../src/systems/lights/SpotLightsSystem.hpp | 25 +++-- 17 files changed, 474 insertions(+), 141 deletions(-) delete mode 100644 .idea/sonarlint.xml diff --git a/.idea/sonarlint.xml b/.idea/sonarlint.xml deleted file mode 100644 index 8ab94a851..000000000 --- a/.idea/sonarlint.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index de74545a8..b92fd2712 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -90,10 +90,9 @@ namespace nexo::ecs { /** * @brief Inserts a new component for the given entity. * - * @tparam U Type of component parameter (allows for perfect forwarding) * @param entity The entity to add the component to * @param component The component instance to add - * @throws ArrayBoundsException if entity ID exceeds MAX_ENTITIES + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES * * @pre The entity must be a valid entity ID */ @@ -235,7 +234,7 @@ namespace nexo::ecs { * * @param index The index to look up * @return The entity at that index - * @throws ArrayBoundsException if the index is invalid + * @throws OutOfRange if the index is invalid * * @pre The index must be less than the array size */ @@ -336,15 +335,15 @@ namespace nexo::ecs { } /** - * @brief Forces a component to be set at a specific index (internal use only) - * - * Used primarily during group reordering operations. - * - * @param index The index to set the component at - * @param entity The entity to associate with this component - * @param component The component data to set - * @throws OutOfRange if the index is invalid - */ + * @brief Forces a component to be set at a specific index (internal use only) + * + * Used primarily during group reordering operations. + * + * @param index The index to set the component at + * @param entity The entity to associate with this component + * @param component The component data to set + * @throws OutOfRange if the index is invalid + */ void forceSetComponentAt(size_t index, const Entity entity, T component) { if (index >= m_size) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 544e664e2..7685afabf 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -168,6 +168,12 @@ namespace nexo::ecs { 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() { @@ -295,12 +301,26 @@ namespace nexo::ecs { 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) { @@ -333,8 +353,6 @@ namespace nexo::ecs { const ComponentType componentType = m_componentManager->getComponentType(); return signature.test(componentType); } - - void updateSystemEntities() const; private: std::shared_ptr m_componentManager; diff --git a/engine/src/ecs/Definitions.hpp b/engine/src/ecs/Definitions.hpp index df0d406b4..99e8a1f63 100644 --- a/engine/src/ecs/Definitions.hpp +++ b/engine/src/ecs/Definitions.hpp @@ -21,16 +21,53 @@ 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; + /** + * @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() { @@ -43,6 +80,12 @@ namespace nexo::ecs { return id; } + /** + * @brief Gets the component type ID, removing const/volatile/reference qualifiers + * + * @tparam T Component type + * @return ComponentType ID for the component type + */ template ComponentType getComponentTypeID() { @@ -50,9 +93,24 @@ namespace nexo::ecs { } // 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; } diff --git a/engine/src/ecs/Entity.hpp b/engine/src/ecs/Entity.hpp index 3e83fd65a..62ce8a02b 100644 --- a/engine/src/ecs/Entity.hpp +++ b/engine/src/ecs/Entity.hpp @@ -73,7 +73,18 @@ namespace nexo::ecs { */ [[nodiscard]] Signature getSignature(Entity entity) const; + /** + * @brief Returns the number of currently active entities + * + * @return std::uint32_t The count of living entities + */ [[nodiscard]] std::uint32_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; private: diff --git a/engine/src/ecs/Group.hpp b/engine/src/ecs/Group.hpp index e5f287a89..4451b4b63 100644 --- a/engine/src/ecs/Group.hpp +++ b/engine/src/ecs/Group.hpp @@ -291,9 +291,32 @@ namespace nexo::ecs { 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()); } /** @@ -445,6 +468,12 @@ namespace nexo::ecs { // 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; @@ -923,6 +952,9 @@ namespace nexo::ecs { 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 diff --git a/engine/src/ecs/GroupSystem.hpp b/engine/src/ecs/GroupSystem.hpp index b04ab4dee..1bcc445da 100644 --- a/engine/src/ecs/GroupSystem.hpp +++ b/engine/src/ecs/GroupSystem.hpp @@ -119,20 +119,43 @@ namespace nexo::ecs { AccessType::Read; }; - /** - * @brief Access-controlled span wrapper for component arrays - */ + /** + * @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(); } - // Conditionally define operator[] based on access type + /** + * @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< @@ -147,16 +170,42 @@ namespace nexo::ecs { } } + /** + * @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]; } - // Iterator support + /** + * @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(); } }; @@ -168,6 +217,12 @@ namespace nexo::ecs { SingletonAccessTypes...>, 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, @@ -252,9 +307,13 @@ namespace nexo::ecs { std::shared_ptr m_group = nullptr; private: - /** - * @brief Implementation to create a group with the extracted component types - */ + /** + * @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) { diff --git a/engine/src/ecs/QuerySystem.hpp b/engine/src/ecs/QuerySystem.hpp index 58be1c7f9..8e11e8d35 100644 --- a/engine/src/ecs/QuerySystem.hpp +++ b/engine/src/ecs/QuerySystem.hpp @@ -35,7 +35,13 @@ namespace nexo::ecs { QuerySystem, Components...> { private: - // Helper template to check if a component type exists in the parameter pack with Read access + /** + * @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 = @@ -43,13 +49,23 @@ namespace nexo::ecs { HasReadAccess::value); }; - // Base case for the template recursion + /** + * @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>; }; - // Convenience function to check read access + /** + * @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() { @@ -60,6 +76,15 @@ namespace nexo::ecs { // Make the base class a friend to access protected members friend class SingletonComponentMixin, Components...>; + /** + * @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) { @@ -102,7 +127,18 @@ namespace nexo::ecs { 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: @@ -139,6 +175,8 @@ namespace nexo::ecs { private: // Cache of component arrays for faster access std::unordered_map> m_componentArrays; + + /// Component signature defining required components for this system Signature m_signature; }; } diff --git a/engine/src/ecs/SingletonComponent.hpp b/engine/src/ecs/SingletonComponent.hpp index ac30e7cf9..1f7bfe587 100644 --- a/engine/src/ecs/SingletonComponent.hpp +++ b/engine/src/ecs/SingletonComponent.hpp @@ -73,6 +73,11 @@ namespace nexo::ecs { SingletonComponent(SingletonComponent&&) = default; SingletonComponent& operator=(SingletonComponent&&) = default; + /** + * @brief Gets the singleton component instance + * + * @return T& Reference to the instance of the singleton component + */ T &getInstance() { return _instance; } private: diff --git a/engine/src/ecs/SingletonComponentMixin.hpp b/engine/src/ecs/SingletonComponentMixin.hpp index 74029b920..ae0bf8659 100644 --- a/engine/src/ecs/SingletonComponentMixin.hpp +++ b/engine/src/ecs/SingletonComponentMixin.hpp @@ -30,7 +30,14 @@ namespace nexo::ecs { template class SingletonComponentMixin { private: - // Helper to check if a singleton component has read access in the parameter pack + + /** + * @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 && @@ -38,7 +45,11 @@ namespace nexo::ecs { }; protected: - // Cache of singleton components for faster access + /** + * @brief Cache of singleton components for faster access + * + * Maps component type IDs to their singleton component instances + */ std::unordered_map> m_singletonComponents; /** @@ -68,7 +79,13 @@ namespace nexo::ecs { } public: - // Convenience function to check singleton read access + + /** + * @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() { diff --git a/engine/src/ecs/System.hpp b/engine/src/ecs/System.hpp index 2b841547f..b52914ed0 100644 --- a/engine/src/ecs/System.hpp +++ b/engine/src/ecs/System.hpp @@ -28,24 +28,83 @@ 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 { public: - void insert(Entity entity); + /** + * @brief Insert an entity into the set + * + * @param entity The entity to insert + */ + void insert(Entity entity); - void erase(Entity entity); + /** + * @brief Remove an entity from the set + * + * @param entity The entity to remove + */ + void erase(Entity entity); - bool empty() const { return dense.empty(); } + /** + * @brief Check if the set is empty + * + * @return true if the set contains no entities, false otherwise + */ + bool empty() const { return dense.empty(); } - bool contains(const Entity entity) const { return sparse.contains(entity); } + /** + * @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); } - size_t size() const { return dense.size(); } + /** + * @brief Get the number of entities in the set + * + * @return The number of entities + */ + size_t size() const { return dense.size(); } - const std::vector& getDense() const { return dense; } - auto begin() const { return dense.begin(); } - auto end() const { return dense.end(); } + /** + * @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; }; @@ -60,6 +119,9 @@ namespace nexo::ecs { class System { public: virtual ~System() = default; + /** + * @brief Global coordinator instance shared by all systems + */ static std::shared_ptr coord; }; @@ -72,6 +134,9 @@ namespace nexo::ecs { ~AQuerySystem() override = default; virtual const Signature &getSignature() const = 0; + /** + * @brief Entities that match this system's signature + */ SparseSet entities; }; @@ -118,6 +183,14 @@ namespace nexo::ecs { return system; } + /** + * @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) { @@ -168,8 +241,19 @@ namespace nexo::ecs { */ void entitySignatureChanged(Entity entity, Signature oldSignature, Signature newSignature); private: - std::unordered_map m_signatures{}; - std::unordered_map> m_querySystems{}; - std::unordered_map> m_groupSystems{}; + /** + * @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{}; }; } diff --git a/engine/src/systems/CameraSystem.hpp b/engine/src/systems/CameraSystem.hpp index 6930a1426..414f62fce 100644 --- a/engine/src/systems/CameraSystem.hpp +++ b/engine/src/systems/CameraSystem.hpp @@ -24,6 +24,7 @@ #include "components/RenderContext.hpp" namespace nexo::system { + /** * @brief System responsible for updating the camera context. * @@ -31,13 +32,14 @@ namespace nexo::system { * matrices using the CameraComponent and TransformComponent. The computed CameraContext is * then pushed into the RenderContext (a singleton component). * - * @note Required Components on camera entities: - * - components::SceneTag - * - components::CameraComponent - * - components::TransformComponent + * @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 Required Singleton Component: - * - components::RenderContext + * @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< @@ -51,18 +53,25 @@ namespace nexo::system { }; /** - * @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 Required Components on camera entities: - * - components::PerspectiveCameraController - * - components::SceneTag - * - components::CameraComponent - * - components::TransformComponent - */ + * @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, @@ -81,18 +90,25 @@ namespace nexo::system { }; /** - * @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 Required Components on camera entities: - * - components::PerspectiveCameraTarget - * - components::SceneTag - * - components::CameraComponent - * - components::TransformComponent - */ + * @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, diff --git a/engine/src/systems/RenderSystem.hpp b/engine/src/systems/RenderSystem.hpp index eb3d93108..d5f3e403c 100644 --- a/engine/src/systems/RenderSystem.hpp +++ b/engine/src/systems/RenderSystem.hpp @@ -23,22 +23,22 @@ 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 Required Singleton Component: - * - components::RenderContext - * - * @note Required Components on renderable entities: - * - components::SceneTag - * - components::CameraComponent (for camera entities stored in the RenderContext) - * - components::TransformComponent - * - components::RenderComponent - */ + * @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 RenderSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read, diff --git a/engine/src/systems/lights/AmbientLightSystem.hpp b/engine/src/systems/lights/AmbientLightSystem.hpp index c944ef1c8..0a8881e4a 100644 --- a/engine/src/systems/lights/AmbientLightSystem.hpp +++ b/engine/src/systems/lights/AmbientLightSystem.hpp @@ -20,18 +20,19 @@ namespace nexo::system { /** - * @brief System responsible for updating ambient light data in the scene. - * - * This system iterates over ambient light entities and updates the global ambient light - * in the RenderContext with the first valid ambient light component it finds. - * - * @note Required Components on ambient light entities: - * - components::SceneTag - * - components::AmbientLightComponent - * - * @note Required Singleton Component: - * - components::RenderContext - */ + * @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>, diff --git a/engine/src/systems/lights/DirectionalLightsSystem.hpp b/engine/src/systems/lights/DirectionalLightsSystem.hpp index d19cb3b5c..76563e9ed 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.hpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.hpp @@ -22,18 +22,19 @@ namespace nexo::system { /** - * @brief System responsible for updating directional lights in the scene. - * - * This system iterates over all directional light entities and updates the RenderContext with the - * directional light components from those entities. - * - * @note Required Components on directional light entities: - * - components::SceneTag - * - components::DirectionalLightComponent - * - * @note Required Singleton Component: - * - components::RenderContext - */ + * @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). + */ class DirectionalLightsSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read>, diff --git a/engine/src/systems/lights/PointLightsSystem.hpp b/engine/src/systems/lights/PointLightsSystem.hpp index 89e3aa154..ec2372ab3 100644 --- a/engine/src/systems/lights/PointLightsSystem.hpp +++ b/engine/src/systems/lights/PointLightsSystem.hpp @@ -22,18 +22,19 @@ namespace nexo::system { /** - * @brief System responsible for updating point lights in the scene. - * - * This system iterates over all point light entities and updates the RenderContext with - * their point light components. - * - * @note Required Components on point light entities: - * - components::SceneTag - * - components::PointLightComponent - * - * @note Required Singleton Component: - * - components::RenderContext - */ + * @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). + */ class PointLightsSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read>, diff --git a/engine/src/systems/lights/SpotLightsSystem.hpp b/engine/src/systems/lights/SpotLightsSystem.hpp index 41ec0ee54..ce5ede2c7 100644 --- a/engine/src/systems/lights/SpotLightsSystem.hpp +++ b/engine/src/systems/lights/SpotLightsSystem.hpp @@ -21,18 +21,19 @@ namespace nexo::system { /** - * @brief System responsible for updating spot lights in the scene. - * - * This system iterates over all spot light entities and updates the RenderContext with their - * spot light components. - * - * @note Required Components on spot light entities: - * - components::SceneTag - * - components::SpotLightComponent - * - * @note Required Singleton Component: - * - components::RenderContext - */ + * @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). + */ class SpotLightsSystem final : public ecs::GroupSystem< ecs::Owned< ecs::Read>, From f0b0bd6684ccfd902a510ea3c0b39916fcf4fa13 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 10 Apr 2025 03:18:21 +0900 Subject: [PATCH 061/450] fix(ecs-opti): some minor fixes --- engine/src/ecs/ComponentArray.hpp | 6 +++--- engine/src/ecs/Components.hpp | 8 ++++---- engine/src/ecs/Entity.cpp | 2 +- engine/src/ecs/Entity.hpp | 4 ++-- engine/src/ecs/Group.hpp | 23 +++++++--------------- engine/src/ecs/SingletonComponent.hpp | 4 ++-- engine/src/ecs/SingletonComponentMixin.hpp | 2 +- 7 files changed, 20 insertions(+), 29 deletions(-) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index b92fd2712..e15cd6883 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -65,8 +65,8 @@ namespace nexo::ecs { * @note This class is not thread-safe. Access should be synchronized externally when * used in multi-threaded contexts. */ - template= 1)>> + template + requires (capacity >= 1) class alignas(64) ComponentArray final : public IComponentArray { public: /** @@ -469,7 +469,7 @@ namespace nexo::ecs { { if (m_size < m_componentArray.capacity() / 2 && m_componentArray.capacity() > capacity) { // Only shrink if vectors are significantly oversized to avoid frequent reallocations - auto newCapacity = static_cast(m_size * 1.5); + auto newCapacity = static_cast(m_size * 2); if (newCapacity < capacity) newCapacity = capacity; diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 62282d5e6..4a2c24066 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -549,13 +549,13 @@ namespace nexo::ecs { bool valid = true; // Check in owned arrays - std::apply([&](auto&&... arrays) { - ((valid = valid && arrays->hasComponent(e)), ...); - }, ownedArrays); + std::apply([&](auto&&... arrays) { + valid = (valid && ... && arrays->hasComponent(e)); + }, ownedArrays); // Check in non-owned arrays std::apply([&](auto&&... arrays) { - ((valid = valid && arrays->hasComponent(e)), ...); + valid = (valid && ... && arrays->hasComponent(e)); }, nonOwnedArrays); // Add to group if valid diff --git a/engine/src/ecs/Entity.cpp b/engine/src/ecs/Entity.cpp index ebf70293b..f086d298f 100644 --- a/engine/src/ecs/Entity.cpp +++ b/engine/src/ecs/Entity.cpp @@ -70,7 +70,7 @@ namespace nexo::ecs { return m_signatures[entity]; } - std::uint32_t EntityManager::getLivingEntityCount() const + size_t EntityManager::getLivingEntityCount() const { return m_livingEntities.size(); } diff --git a/engine/src/ecs/Entity.hpp b/engine/src/ecs/Entity.hpp index 62ce8a02b..9da777be7 100644 --- a/engine/src/ecs/Entity.hpp +++ b/engine/src/ecs/Entity.hpp @@ -76,9 +76,9 @@ namespace nexo::ecs { /** * @brief Returns the number of currently active entities * - * @return std::uint32_t The count of living entities + * @return size_t The count of living entities */ - [[nodiscard]] std::uint32_t getLivingEntityCount() const; + [[nodiscard]] size_t getLivingEntityCount() const; /** * @brief Retrieves a view of all currently active entities diff --git a/engine/src/ecs/Group.hpp b/engine/src/ecs/Group.hpp index 4451b4b63..e55396f10 100644 --- a/engine/src/ecs/Group.hpp +++ b/engine/src/ecs/Group.hpp @@ -160,13 +160,13 @@ namespace nexo::ecs { { if (std::tuple_size_v == 0) THROW_EXCEPTION(InternalError, "Group must have at least one owned component"); - m_ownedSignature = std::apply([]([[maybe_unused]] auto&&... arrays) -> Signature { + 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 { + const Signature nonOwnedSignature = std::apply([]([[maybe_unused]] auto&&... arrays) { Signature signature; ((signature.set(getComponentTypeID::component_type>())), ...); return signature; @@ -277,15 +277,6 @@ namespace nexo::ecs { return m_index == other.m_index && m_view == other.m_view; } - /** - * @brief Inequality operator. - * - * @param other Another iterator. - * @return true If iterators are not equal. - * @return false Otherwise. - */ - bool operator!=(const GroupIterator& other) const { return !(*this == other); } - private: const Group* m_view; ///< Pointer to the group. std::size_t m_index; ///< Current index in the group. @@ -523,7 +514,7 @@ namespace nexo::ecs { entities.push_back(drivingArray->getEntityAtIndex(i)); // Sort entities based on the extracted field from the component - std::sort(entities.begin(), entities.end(), + std::ranges::sort(entities.begin(), entities.end(), [&](Entity a, Entity b) { const auto& compA = compArray->get(a); const auto& compB = compArray->get(b); @@ -741,9 +732,9 @@ namespace nexo::ecs { * @param keyExtractor Function to extract key from an entity. */ PartitionStorage(Group* group, EntityKeyExtractor keyExtractor) - : m_group(group), m_keyExtractor(std::move(keyExtractor)), m_isDirty(true) {} + : m_group(group), m_keyExtractor(std::move(keyExtractor)) {} - [[nodiscard]] bool isDirty() const override { return m_isDirty; } + bool isDirty() const override { return m_isDirty; } void markDirty() override { m_isDirty = true; } /** @@ -817,7 +808,7 @@ namespace nexo::ecs { 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; ///< Flag indicating if partitions need rebuilding. + bool m_isDirty = true; ///< Flag indicating if partitions need rebuilding. }; /** @@ -841,7 +832,7 @@ namespace nexo::ecs { * @param newOrder New order of entities. */ template - void reorderArray(ArrayPtr array, const std::vector& newOrder) + void reorderArray(ArrayPtr array, const std::vector& newOrder) const { size_t groupSize = array->groupSize(); if (newOrder.size() != groupSize) diff --git a/engine/src/ecs/SingletonComponent.hpp b/engine/src/ecs/SingletonComponent.hpp index 1f7bfe587..23d6ed9d3 100644 --- a/engine/src/ecs/SingletonComponent.hpp +++ b/engine/src/ecs/SingletonComponent.hpp @@ -70,8 +70,8 @@ namespace nexo::ecs { SingletonComponent& operator=(const SingletonComponent&) = delete; // Allow moving - SingletonComponent(SingletonComponent&&) = default; - SingletonComponent& operator=(SingletonComponent&&) = default; + SingletonComponent(SingletonComponent&&) noexcept = default; + SingletonComponent& operator=(SingletonComponent&&) noexcept = default; /** * @brief Gets the singleton component instance diff --git a/engine/src/ecs/SingletonComponentMixin.hpp b/engine/src/ecs/SingletonComponentMixin.hpp index ae0bf8659..b97cb30e0 100644 --- a/engine/src/ecs/SingletonComponentMixin.hpp +++ b/engine/src/ecs/SingletonComponentMixin.hpp @@ -111,7 +111,7 @@ namespace nexo::ecs { } // Get the stored singleton component wrapper - auto& singletonComponentPtr = m_singletonComponents[typeIndex]; + const auto& singletonComponentPtr = m_singletonComponents[typeIndex]; auto* componentWrapper = dynamic_cast*>(singletonComponentPtr.get()); if (!componentWrapper) From 09ef71de15db71280f59a51d84305b5e903e24b0 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:04:40 +0900 Subject: [PATCH 062/450] feat(ecs-opti): add clearColor as arg to the factory --- engine/src/CameraFactory.cpp | 5 +++-- engine/src/CameraFactory.hpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/engine/src/CameraFactory.cpp b/engine/src/CameraFactory.cpp index 6dddcf44e..d558ad7b6 100644 --- a/engine/src/CameraFactory.cpp +++ b/engine/src/CameraFactory.cpp @@ -20,8 +20,8 @@ namespace nexo { ecs::Entity CameraFactory::createPerspectiveCamera(glm::vec3 pos, unsigned int width, - unsigned int height, std::shared_ptr renderTarget, - float fov, float nearPlane, float farPlane) + unsigned int height, std::shared_ptr renderTarget, + const glm::vec4 &clearColor, float fov, float nearPlane, float farPlane) { components::TransformComponent transform{}; transform.pos = pos; @@ -34,6 +34,7 @@ namespace nexo { camera.farPlane = farPlane; camera.type = components::CameraType::PERSPECTIVE; camera.m_renderTarget = renderTarget; + camera.clearColor = clearColor; ecs::Entity newCamera = Application::m_coordinator->createEntity(); Application::m_coordinator->addComponent(newCamera, transform); diff --git a/engine/src/CameraFactory.hpp b/engine/src/CameraFactory.hpp index 2cf0bbdd8..a1a056eda 100644 --- a/engine/src/CameraFactory.hpp +++ b/engine/src/CameraFactory.hpp @@ -23,6 +23,6 @@ namespace nexo { public: static ecs::Entity createPerspectiveCamera(glm::vec3 pos, unsigned int width, unsigned int height, std::shared_ptr renderTarget = nullptr, - float fov = 45.0f, float nearPlane = 0.1f, float farPlane = 1000.0f); + const glm::vec4 &clearColor = {0.0f, 0.0f, 0.0f, 1.0f}, float fov = 45.0f, float nearPlane = 0.1f, float farPlane = 1000.0f); }; } From e787feea112558a7a22cdda3c1f05d36003ec83d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:05:04 +0900 Subject: [PATCH 063/450] rm(ecs-opti): remove useless utils func --- engine/src/ecs/Access.hpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/engine/src/ecs/Access.hpp b/engine/src/ecs/Access.hpp index 55d495d18..b198b4928 100644 --- a/engine/src/ecs/Access.hpp +++ b/engine/src/ecs/Access.hpp @@ -149,16 +149,4 @@ namespace nexo::ecs { */ template struct IsSingleton : std::bool_constant::value || IsWriteSingleton::value> {}; - - /** - * @brief Gets the type index for a component - * - * @tparam T The component type - * @return std::type_index The type index - */ - template - std::type_index getTypeIndex() - { - return std::type_index(typeid(T)); - } } From 41752e6863cda6e98ea084728ec38ffbe13a320d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:05:21 +0900 Subject: [PATCH 064/450] style(ecs-opti): remove useless comments --- engine/src/ecs/ComponentArray.hpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index e15cd6883..b6c2f1822 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -20,7 +20,6 @@ #include #include -#include namespace nexo::ecs { /** @@ -146,7 +145,6 @@ namespace nexo::ecs { m_sparse[m_dense[groupLastIndex]] = groupLastIndex; } --m_groupSize; - // Now update indexToRemove to reflect the new position. indexToRemove = groupLastIndex; } @@ -298,7 +296,6 @@ namespace nexo::ecs { if (index != m_groupSize) { std::swap(m_componentArray[index], m_componentArray[m_groupSize]); std::swap(m_dense[index], m_dense[m_groupSize]); - // Update sparse mapping for both swapped entities. m_sparse[m_dense[index]] = index; m_sparse[m_dense[m_groupSize]] = m_groupSize; } @@ -324,7 +321,6 @@ namespace nexo::ecs { size_t index = m_sparse[entity]; if (index >= m_groupSize) return; - // Decrement group size first; the entity to remove is at index. --m_groupSize; if (index != m_groupSize) { std::swap(m_componentArray[index], m_componentArray[m_groupSize]); @@ -349,13 +345,8 @@ namespace nexo::ecs { if (index >= m_size) THROW_EXCEPTION(OutOfRange, index); - // Update the sparse mapping m_sparse[entity] = index; - - // Update the dense array m_dense[index] = entity; - - // Update the component m_componentArray[index] = std::move(component); } From 0ae1efec868dcf39e37daf6c65905a3fc9948ed1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:05:52 +0900 Subject: [PATCH 065/450] style(ecs-opti): remove comment + fix indent --- engine/src/ecs/Components.hpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 4a2c24066..7ce4c02ab 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -161,10 +161,9 @@ namespace std { struct hash { size_t operator()(const nexo::ecs::GroupKey& key) const noexcept { - // Create a combined hash of both signatures const size_t h1 = std::hash()(key.ownedSignature); const size_t h2 = std::hash()(key.nonOwnedSignature); - return h1 ^ (h2 << 1); // Combine hashes with a bit shift + return h1 ^ (h2 << 1); } }; } @@ -270,10 +269,10 @@ namespace nexo::ecs { { getComponentArray()->insert(entity, std::move(component)); - for (const auto& group : std::ranges::views::values(m_groupRegistry)) - { - if ((signature & group->allSignature()) == group->allSignature()) - group->addToGroup(entity); + for (const auto& group : std::ranges::views::values(m_groupRegistry)) { + if ((signature & group->allSignature()) == group->allSignature()) { + group->addToGroup(entity); + } } } @@ -422,7 +421,6 @@ namespace nexo::ecs { template auto registerGroup(const auto& nonOwned) { - // Generate a unique key for this group type combination const GroupKey newGroupKey = generateGroupKey(nonOwned); // Check if this exact group already exists @@ -447,7 +445,6 @@ namespace nexo::ecs { } } - // No conflicts found, create the new group auto group = createNewGroup(nonOwned); m_groupRegistry[newGroupKey] = group; return group; @@ -491,7 +488,6 @@ namespace nexo::ecs { */ [[nodiscard]] static bool hasCommonOwnedComponents(const GroupKey& key1, const GroupKey& key2) { - // If there's any bit that's set in both owned signatures, they share components return (key1.ownedSignature & key2.ownedSignature).any(); } @@ -558,7 +554,6 @@ namespace nexo::ecs { valid = (valid && ... && arrays->hasComponent(e)); }, nonOwnedArrays); - // Add to group if valid if (valid) { std::apply([&](auto&&... arrays) { ((arrays->addToGroup(e)), ...); @@ -566,7 +561,6 @@ namespace nexo::ecs { } } - // Create and return the group return std::make_shared>(ownedArrays, nonOwnedArrays); } From 2c92599105e6f7319a9b235127eb620875d3ffaf Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:06:22 +0900 Subject: [PATCH 066/450] refactor(ecs-opti): add bunch of error handling --- engine/src/ecs/Group.hpp | 109 +++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/engine/src/ecs/Group.hpp b/engine/src/ecs/Group.hpp index e55396f10..371ed6317 100644 --- a/engine/src/ecs/Group.hpp +++ b/engine/src/ecs/Group.hpp @@ -130,7 +130,6 @@ namespace nexo::ecs { template using EntityKeyExtractor = std::function; - //------------------------------------------------------------ /** * @brief Group class for a view over entities with both owned and non‑owned components. * @@ -160,6 +159,7 @@ namespace nexo::ecs { { if (std::tuple_size_v == 0) THROW_EXCEPTION(InternalError, "Group must have at least one owned component"); + m_ownedSignature = std::apply([]([[maybe_unused]] auto&&... arrays) { Signature signature; ((signature.set(getComponentTypeID::component_type>())), ...); @@ -227,7 +227,7 @@ namespace nexo::ecs { using value_type = decltype(std::declval().dereference(0)); using reference = value_type; using difference_type = std::ptrdiff_t; - using pointer = void; // Not used + using pointer = void; /** * @brief Constructs a GroupIterator. @@ -245,9 +245,9 @@ namespace nexo::ecs { */ reference operator*() const { - if (m_index >= m_view->size()) { + if (m_index >= m_view->size()) THROW_EXCEPTION(OutOfRange, m_index); - } + return m_view->dereference(m_index); } @@ -256,14 +256,23 @@ namespace nexo::ecs { * * @return GroupIterator& Reference to the iterator after increment. */ - GroupIterator& operator++() { ++m_index; return *this; } + GroupIterator& operator++() + { + ++m_index; + return *this; + } /** * @brief Post-increment operator. * * @return GroupIterator Iterator before increment. */ - GroupIterator operator++(int) { GroupIterator tmp = *this; ++(*this); return tmp; } + GroupIterator operator++(int) + { + GroupIterator tmp = *this; + ++(*this); + return tmp; + } /** * @brief Equality operator. @@ -323,9 +332,9 @@ namespace nexo::ecs { void each(Func func) const { auto firstArray = std::get<0>(m_ownedArrays); - if (!firstArray) { + 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, @@ -346,13 +355,12 @@ namespace nexo::ecs { void eachInRange(size_t startIndex, const size_t count, Func func) const { auto firstArray = std::get<0>(m_ownedArrays); - if (!firstArray) { + if (!firstArray) THROW_EXCEPTION(InternalError, "Component array is null"); - } - if (startIndex >= firstArray->groupSize()) { - return; // Nothing to iterate if start is beyond the end - } + 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++) { @@ -375,6 +383,7 @@ namespace nexo::ecs { std::apply([e](auto&&... arrays) { ((arrays->addToGroup(e)), ...); }, m_ownedArrays); + m_sortingInvalidated = true; invalidatePartitions(); } @@ -391,6 +400,7 @@ namespace nexo::ecs { std::apply([e](auto&&... arrays) { ((arrays->removeFromGroup(e)), ...); }, m_ownedArrays); + m_sortingInvalidated = true; invalidatePartitions(); } @@ -422,9 +432,9 @@ namespace nexo::ecs { { if constexpr (tuple_contains_component_v) { auto compArray = getOwnedImpl(); // internal lookup in owned tuple - if (!compArray) { + 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 @@ -445,9 +455,9 @@ namespace nexo::ecs { { if constexpr (tuple_contains_component_v) { auto compArray = getOwnedImpl(); // internal lookup in owned tuple - if (!compArray) { + 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 @@ -484,28 +494,30 @@ namespace nexo::ecs { void sortBy(FieldExtractor extractor, bool ascending = true) { SortingOrder sortingOrder = ascending ? SortingOrder::ASCENDING : SortingOrder::DESCENDING; - if (sortingOrder != m_sortingOrder) - { + + if (sortingOrder != m_sortingOrder) { m_sortingOrder = sortingOrder; m_sortingInvalidated = true; } + if (!m_sortingInvalidated) return; - // Get the appropriate component array + std::shared_ptr> compArray; - if constexpr (tuple_contains_component_v) { + if constexpr (tuple_contains_component_v) compArray = getOwnedImpl(); - } else if constexpr (tuple_contains_component_v) + else if constexpr (tuple_contains_component_v) compArray = getNonOwnedImpl(); else static_assert(dependent_false::value, "Component type not found in group"); - // Get the driving array (always the first owned array) + if (!compArray) + THROW_EXCEPTION(InternalError, "Component array is null"); + auto drivingArray = std::get<0>(m_ownedArrays); const size_t groupSize = drivingArray->groupSize(); - // Create a vector of entities to sort std::vector entities; entities.reserve(groupSize); @@ -523,7 +535,6 @@ namespace nexo::ecs { return extractor(compA) > extractor(compB); }); - // Reorder only the owned component arrays according to the new order reorderGroup(entities); m_sortingInvalidated = false; } @@ -624,14 +635,18 @@ namespace nexo::ecs { std::string typeId = typeid(KeyType).name(); typeId += "_" + std::string(typeid(CompType).name()); - // Create entity-based key extractor that uses the component extractor EntityKeyExtractor entityKeyExtractor = [this, keyExtractor](Entity e) { - // Get the component and extract the key 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"); @@ -655,12 +670,9 @@ namespace nexo::ecs { // Check if we already have this partition view auto it = m_partitionStorageMap.find(partitionId); if (it == m_partitionStorageMap.end()) { - // Create a new partition storage auto storage = std::make_unique>(this, keyExtractor); auto* storagePtr = storage.get(); m_partitionStorageMap[partitionId] = std::move(storage); - - // Rebuild the partitions storagePtr->rebuild(); return PartitionView(this, storagePtr->getPartitions()); @@ -669,11 +681,9 @@ namespace nexo::ecs { // Get the existing storage and cast to the right type auto* storage = static_cast*>(it->second.get()); - // Rebuild if needed if (storage->isDirty()) storage->rebuild(); - // Return a view to the partitions return PartitionView(this, storage->getPartitions()); } @@ -682,8 +692,8 @@ namespace nexo::ecs { */ void invalidatePartitions() { - for (auto& [_, storage] : m_partitionStorageMap) - storage->markDirty(); + for (auto& [_, storage] : m_partitionStorageMap) + storage->markDirty(); } private: @@ -698,7 +708,6 @@ namespace nexo::ecs { * This allows handling partition storage for different key types uniformly. */ struct IPartitionStorage { - /// Virtual destructor. virtual ~IPartitionStorage() = default; /** * @brief Checks if the partition storage is dirty (needs rebuilding). @@ -747,7 +756,6 @@ namespace nexo::ecs { { if (!m_isDirty) return; - auto drivingArray = std::get<0>(m_group->m_ownedArrays); const size_t groupSize = drivingArray->groupSize(); @@ -758,7 +766,6 @@ namespace nexo::ecs { return; } - // Collect all entity keys std::unordered_map> keyToEntities; for (size_t i = 0; i < groupSize; i++) { @@ -767,7 +774,6 @@ namespace nexo::ecs { keyToEntities[key].push_back(e); } - // Create the partitions m_partitions.clear(); m_partitions.reserve(keyToEntities.size()); @@ -788,9 +794,7 @@ namespace nexo::ecs { currentIndex += entities.size(); } - // Reorder the entities according to partitions m_group->reorderGroup(newOrder); - m_isDirty = false; } @@ -818,7 +822,6 @@ namespace nexo::ecs { */ void reorderGroup(const std::vector& newOrder) { - // Implementation to rearrange entities in all owned arrays std::apply([&](auto&&... arrays) { ((reorderArray(arrays, newOrder)), ...); }, m_ownedArrays); @@ -839,28 +842,21 @@ namespace nexo::ecs { THROW_EXCEPTION(InternalError, "New order size doesn't match group size"); if (groupSize == 0) - return; // Nothing to reorder + return; // Create a temporary storage for components using CompType = typename std::decay_t::component_type; std::vector tempComponents; tempComponents.reserve(groupSize); - // Copy components in the new order - try { - for (Entity e : newOrder) - tempComponents.push_back(array->get(e)); + for (Entity e : newOrder) + tempComponents.push_back(array->get(e)); //Maybe we should not push back, does it make a copy ? - // Update the sparse-to-dense mapping and components - for (size_t i = 0; i < groupSize; i++) { - Entity e = newOrder[i]; - array->forceSetComponentAt(i, e, std::move(tempComponents[i])); - } - } catch (...) { - // If anything goes wrong, don't leave the array in a partially-updated state - THROW_EXCEPTION(InternalError, "Reordering failed, array may be in an inconsistent state"); + 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. @@ -870,8 +866,8 @@ namespace nexo::ecs { */ auto dereference(std::size_t index) const { - // Retrieve the entity from the first owned array. 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)...); @@ -950,6 +946,7 @@ namespace nexo::ecs { 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. From 536778d0a23ee1977dd66e66c22c980de1a618c2 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:06:57 +0900 Subject: [PATCH 067/450] refactor(ecs-opti): add bunch of error handling + remove useless comments --- engine/src/ecs/GroupSystem.hpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/engine/src/ecs/GroupSystem.hpp b/engine/src/ecs/GroupSystem.hpp index 1bcc445da..de88e3664 100644 --- a/engine/src/ecs/GroupSystem.hpp +++ b/engine/src/ecs/GroupSystem.hpp @@ -163,12 +163,11 @@ namespace nexo::ecs { const std::remove_const_t& > { - if constexpr (GetComponentAccess>::accessType == AccessType::Write) { + if constexpr (GetComponentAccess>::accessType == AccessType::Write) return const_cast&>(m_span[index]); - } else { + else return m_span[index]; } - } /** * @brief Const access operator @@ -226,18 +225,16 @@ namespace nexo::ecs { GroupSystem() { static_assert(std::tuple_size_v > 0, - "GroupSystem must have at least one owned component type"); - if (!coord) { + "GroupSystem must have at least one owned component type"); + + if (!coord) THROW_EXCEPTION(InternalError, "Coordinator is null in GroupSystem constructor"); - } - // Create the group m_group = createGroupImpl( tupleToTypeList(), tupleToTypeList() ); - // Initialize singleton components this->initializeSingletonComponents(); } @@ -250,9 +247,9 @@ namespace nexo::ecs { template auto get() { - if (!m_group) { + if (!m_group) THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); - } + constexpr bool isOwned = isOwnedComponent(); if constexpr (isOwned) { // Get the span from the group @@ -269,12 +266,11 @@ namespace nexo::ecs { auto componentArray = m_group->template get(); // Apply access control by wrapping in a special pointer wrapper if needed - if constexpr (GetComponentAccess::accessType == AccessType::Read) { + if constexpr (GetComponentAccess::accessType == AccessType::Read) return std::shared_ptr>(m_group->template get()); - } else { + else return componentArray; } - } } /** @@ -297,9 +293,9 @@ namespace nexo::ecs { */ [[nodiscard]] std::span getEntities() const { - if (!m_group) { + if (!m_group) THROW_EXCEPTION(InternalError, "Group is null in GroupSystem"); - } + return m_group->entities(); } @@ -320,9 +316,13 @@ namespace nexo::ecs { 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); } } From 8aec2330a6356cede6b34ce4dd604e48adb2fa46 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:07:36 +0900 Subject: [PATCH 068/450] refactor(ecs-opti): now use ComponentType to be more coherent with the component manager implementation --- engine/src/ecs/SingletonComponent.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/engine/src/ecs/SingletonComponent.hpp b/engine/src/ecs/SingletonComponent.hpp index 23d6ed9d3..3bb98e946 100644 --- a/engine/src/ecs/SingletonComponent.hpp +++ b/engine/src/ecs/SingletonComponent.hpp @@ -18,6 +18,7 @@ #include #include +#include "Definitions.hpp" #include "Logger.hpp" #include "ECSExceptions.hpp" @@ -104,15 +105,14 @@ namespace nexo::ecs { template void registerSingletonComponent(Args&&... args) { - using Decayed = std::decay_t; - std::type_index typeName(typeid(Decayed)); + 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)...)} + std::make_shared>(std::forward(args)...)} ); } @@ -126,7 +126,7 @@ namespace nexo::ecs { template T &getSingletonComponent() { - const std::type_index typeName(typeid(T)); + ComponentType typeName = getUniqueComponentTypeID(); if (!m_singletonComponents.contains(typeName)) THROW_EXCEPTION(SingletonComponentNotRegistered); @@ -145,7 +145,7 @@ namespace nexo::ecs { template std::shared_ptr getRawSingletonComponent() { - const std::type_index typeName(typeid(T)); + ComponentType typeName = getUniqueComponentTypeID(); if (!m_singletonComponents.contains(typeName)) THROW_EXCEPTION(SingletonComponentNotRegistered); @@ -163,13 +163,13 @@ namespace nexo::ecs { template void unregisterSingletonComponent() { - const std::type_index typeName(typeid(T)); + ComponentType typeName = getUniqueComponentTypeID(); if (!m_singletonComponents.contains(typeName)) THROW_EXCEPTION(SingletonComponentNotRegistered); m_singletonComponents.erase(typeName); } private: - std::unordered_map> m_singletonComponents{}; + std::unordered_map> m_singletonComponents{}; }; } From d61b55d30886a4591db59f3a5d7cb6d048b82dc7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:08:15 +0900 Subject: [PATCH 069/450] fix(ecs-opti): fix non-singleton components being passed to SingletonComponentMixin class --- engine/src/ecs/QuerySystem.hpp | 63 ++++++++-- engine/src/ecs/SingletonComponentMixin.hpp | 133 ++++++++++++++------- 2 files changed, 138 insertions(+), 58 deletions(-) diff --git a/engine/src/ecs/QuerySystem.hpp b/engine/src/ecs/QuerySystem.hpp index 8e11e8d35..d7bbd3738 100644 --- a/engine/src/ecs/QuerySystem.hpp +++ b/engine/src/ecs/QuerySystem.hpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include "Definitions.hpp" #include "ECSExceptions.hpp" #include "System.hpp" #include "Access.hpp" @@ -24,16 +25,58 @@ #include namespace nexo::ecs { + /** + * @brief Metaprogramming helper to extract singleton components from a parameter pack + * + * This template recursively filters a list of components, extracting only the + * singleton components to pass to SingletonComponentMixin. This ensures that + * regular components aren't processed by the mixin. + * + * @tparam Components Component access specifiers to filter for singleton types + */ + template + struct ExtractSingletonComponents; + + /** + * @brief Base case specialization for empty component list + * + * When no components remain to be processed, this provides a void-specialized + * SingletonComponentMixin as the base type. + */ + template<> + struct ExtractSingletonComponents<> { + using type = SingletonComponentMixin; + }; + + /** + * @brief Recursive case for ExtractSingletonComponents + * + * Checks if the current component is a singleton. If it is, includes it in + * the mixin via RebindWithComponent. If not, skips it and continues with the rest. + * + * @tparam Component The current component to check + * @tparam Rest Remaining components to process + */ + template + struct ExtractSingletonComponents { + // If Component is a singleton access type, include it in the mixin + using type = std::conditional_t< + IsSingleton::value, + // 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 + >; + }; /** * @class QuerySystem * @brief System that directly queries component arrays * * @tparam Components Component access specifiers (Read, Write, ReadSingleton, WriteSingleton) */ - template - class QuerySystem : public AQuerySystem, public SingletonComponentMixin< - QuerySystem, - Components...> { + 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 @@ -73,9 +116,6 @@ namespace nexo::ecs { } public: - // Make the base class a friend to access protected members - friend class SingletonComponentMixin, Components...>; - /** * @brief Constructs a new QuerySystem * @@ -87,9 +127,8 @@ namespace nexo::ecs { */ QuerySystem() { - if (!coord) { + if (!coord) THROW_EXCEPTION(InternalError, "Coordinator is null in QuerySystem constructor"); - } // Set system signature based on required components (ignore singleton components) (setComponentSignatureIfRegular(m_signature), ...); @@ -111,7 +150,7 @@ namespace nexo::ecs { template std::conditional_t(), const T&, T&> getComponent(Entity entity) { - const std::type_index typeIndex = getTypeIndex(); + const ComponentType typeIndex = getUniqueComponentTypeID(); const auto it = m_componentArrays.find(typeIndex); if (it == m_componentArrays.end()) @@ -153,7 +192,7 @@ namespace nexo::ecs { if constexpr (!IsSingleton::value) { using T = typename ComponentAccessType::ComponentType; auto componentArray = coord->getComponentArray(); - m_componentArrays[getTypeIndex()] = componentArray; + m_componentArrays[getUniqueComponentTypeID()] = componentArray; } } @@ -174,7 +213,7 @@ namespace nexo::ecs { private: // Cache of component arrays for faster access - std::unordered_map> m_componentArrays; + std::unordered_map> m_componentArrays; /// Component signature defining required components for this system Signature m_signature; diff --git a/engine/src/ecs/SingletonComponentMixin.hpp b/engine/src/ecs/SingletonComponentMixin.hpp index b97cb30e0..1540a79cf 100644 --- a/engine/src/ecs/SingletonComponentMixin.hpp +++ b/engine/src/ecs/SingletonComponentMixin.hpp @@ -1,19 +1,7 @@ -//// SingletonComponentMixin.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: Mehdy MORVAN -// Date: 06/04/2025 -// Description: Base class for common singleton components operations in systems -// -/////////////////////////////////////////////////////////////////////////////// #pragma once #include "Access.hpp" +#include "Definitions.hpp" #include "SingletonComponent.hpp" #include #include @@ -31,26 +19,43 @@ namespace nexo::ecs { 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 - */ + /** + * @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 singleton components for faster access - * - * Maps component type IDs to their singleton component instances - */ - std::unordered_map> m_singletonComponents; + /** + * @brief Cache of singleton components for faster access + * + * Maps component type IDs to their singleton component instances + */ + std::unordered_map> m_singletonComponents; /** * @brief Initializes singleton components for this system @@ -69,23 +74,19 @@ namespace nexo::ecs { template void cacheSingletonComponent() { - try { - auto* derived = static_cast(this); - std::shared_ptr instance = derived->coord->template getRawSingletonComponent(); - m_singletonComponents[getTypeIndex()] = instance; - } catch (const nexo::ecs::SingletonComponentNotRegistered&) { - // Singleton not registered yet, we'll try again when getSingleton is called - } + auto* derived = static_cast(this); + std::shared_ptr instance = derived->coord->template getRawSingletonComponent(); + m_singletonComponents[getUniqueComponentTypeID()] = 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 - */ + /** + * @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() { @@ -103,14 +104,13 @@ namespace nexo::ecs { template std::conditional_t(), const T&, T&> getSingleton() { - const std::type_index typeIndex = getTypeIndex(); + const ComponentType typeIndex = getUniqueComponentTypeID(); if (!m_singletonComponents.contains(typeIndex)) { // Late binding in case the singleton was registered after system creation cacheSingletonComponent(); } - // Get the stored singleton component wrapper const auto& singletonComponentPtr = m_singletonComponents[typeIndex]; auto* componentWrapper = dynamic_cast*>(singletonComponentPtr.get()); @@ -118,13 +118,54 @@ namespace nexo::ecs { THROW_EXCEPTION(SingletonComponentNotRegistered); // Return the reference with appropriate constness - if constexpr (hasReadSingletonAccess()) { - // For read-only access, return const reference + if constexpr (hasReadSingletonAccess()) return const_cast(componentWrapper->getInstance()); - } else { - // For read-write access, return non-const reference + else return componentWrapper->getInstance(); - } } }; + + /** + * @brief Base case specialization for SingletonComponentMixin with no components + * + * This specialization serves as the termination point for template recursion + * when filtering singleton components. It provides a minimal implementation + * with placeholder rebinding methods that allow component accumulation during + * the template recursion process. + */ + 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() {} + }; } From 6580ab0268771a8b4102a3b50837576b0bc374ff Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:08:45 +0900 Subject: [PATCH 070/450] refactor(ecs-opti): add camera clearColor as arg --- editor/src/utils/ScenePreview.cpp | 8 ++++---- editor/src/utils/ScenePreview.hpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/editor/src/utils/ScenePreview.cpp b/editor/src/utils/ScenePreview.cpp index 869d8e687..cfe02998d 100644 --- a/editor/src/utils/ScenePreview.cpp +++ b/editor/src/utils/ScenePreview.cpp @@ -51,7 +51,7 @@ namespace nexo::editor::utils { return entityCopy; } - static ecs::Entity createPreviewCamera(scene::SceneId sceneId, ecs::Entity entity, ecs::Entity entityCopy, const glm::vec2 &previewSize) + static ecs::Entity createPreviewCamera(scene::SceneId sceneId, ecs::Entity entity, ecs::Entity entityCopy, const glm::vec2 &previewSize, const glm::vec4 &clearColor) { auto &app = getApp(); renderer::FramebufferSpecs framebufferSpecs; @@ -108,7 +108,7 @@ namespace nexo::editor::utils { // Create the perspective camera using the computed position. ecs::Entity cameraId = CameraFactory::createPerspectiveCamera(cameraPos, - framebufferSpecs.width, framebufferSpecs.height, framebuffer); + framebufferSpecs.width, framebufferSpecs.height, framebuffer, clearColor); // Update the camera's transform. auto &cameraTransform = nexo::Application::m_coordinator->getComponent(cameraId); @@ -142,7 +142,7 @@ namespace nexo::editor::utils { app.getSceneManager().getScene(sceneId).addEntity(spotLight); } - void genScenePreview(const std::string &uniqueSceneName, const glm::vec2 &previewSize, ecs::Entity entity, ScenePreviewOut &out) + void genScenePreview(const std::string &uniqueSceneName, const glm::vec2 &previewSize, ecs::Entity entity, ScenePreviewOut &out, const glm::vec4 &clearColor) { auto &app = getApp(); @@ -150,7 +150,7 @@ namespace nexo::editor::utils { out.entityCopy = copyEntity(entity); - out.cameraId = createPreviewCamera(out.sceneId, entity, out.entityCopy, previewSize); + out.cameraId = createPreviewCamera(out.sceneId, entity, out.entityCopy, previewSize, clearColor); setupPreviewLights(out.sceneId, out.entityCopy); out.sceneGenerated = true; diff --git a/editor/src/utils/ScenePreview.hpp b/editor/src/utils/ScenePreview.hpp index a9417c52c..8da380ad7 100644 --- a/editor/src/utils/ScenePreview.hpp +++ b/editor/src/utils/ScenePreview.hpp @@ -67,6 +67,6 @@ namespace nexo::editor::utils { * @param entity The entity to generate the preview from. * @param out Output structure containing preview scene details. */ - void genScenePreview(const std::string &uniqueSceneName, const glm::vec2 &previewSize, ecs::Entity entity, ScenePreviewOut &out); + 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}); } From 3920df83cf5debfb03a6a2bdff0ce912dad142a9 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:09:19 +0900 Subject: [PATCH 071/450] fix(ecs-opti): now retrieve camera component after rendering preview since ref was getting invalidated --- .../EntityProperties/RenderProperty.cpp | 32 +++++++++---------- .../src/DocumentWindows/MaterialInspector.cpp | 4 +-- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index 623e4bee3..9c308910c 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -154,25 +154,23 @@ namespace nexo::editor { static int entityBase = -1; if (sectionOpen) { - if (entityBase != static_cast(entity)) - { - //TODO: I guess all of this should be centralized in the assets - utils::ScenePreviewOut previewParams; - utils::genScenePreview("Modify material inspector", {64, 64}, entity, previewParams); - auto &app = nexo::getApp(); - app.getSceneManager().getScene(previewParams.sceneId).setActiveStatus(false); - auto &cameraComponent = Application::m_coordinator->getComponent(previewParams.cameraId); - cameraComponent.clearColor = {0.05f, 0.05f, 0.05f, 0.0f}; - app.run(previewParams.sceneId, RenderingType::FRAMEBUFFER); - framebuffer = cameraComponent.m_renderTarget; - app.getSceneManager().deleteScene(previewParams.sceneId); - entityBase = static_cast(entity); - } + if (entityBase != static_cast(entity)) + { + //TODO: I guess all of this should be centralized in the assets + utils::ScenePreviewOut previewParams; + utils::genScenePreview("Modify material inspector", {64, 64}, entity, previewParams); + auto &app = nexo::getApp(); + app.getSceneManager().getScene(previewParams.sceneId).setActiveStatus(false); + app.run(previewParams.sceneId, RenderingType::FRAMEBUFFER); + auto &cameraComponent = Application::m_coordinator->getComponent(previewParams.cameraId); + framebuffer = cameraComponent.m_renderTarget; + app.getSceneManager().deleteScene(previewParams.sceneId); + entityBase = static_cast(entity); + } // --- Material Preview --- - if (framebuffer->getColorAttachmentId(0) != 0) - ImGui::Image(static_cast(static_cast(framebuffer->getColorAttachmentId(0))), ImVec2(64, 64), ImVec2(0, 1), ImVec2(1, 0)); - + if (framebuffer && framebuffer->getColorAttachmentId(0) != 0) + ImGui::Image(static_cast(static_cast(framebuffer->getColorAttachmentId(0))), ImVec2(64, 64), ImVec2(0, 1), ImVec2(1, 0)); ImGui::SameLine(); ImGui::BeginGroup(); diff --git a/editor/src/DocumentWindows/MaterialInspector.cpp b/editor/src/DocumentWindows/MaterialInspector.cpp index 3a21311e5..8d9676501 100644 --- a/editor/src/DocumentWindows/MaterialInspector.cpp +++ b/editor/src/DocumentWindows/MaterialInspector.cpp @@ -60,13 +60,13 @@ namespace nexo::editor { { utils::genScenePreview("Modify material inspector", {64, 64}, m_ecsEntity, previewParams); auto &app = nexo::getApp(); - auto &cameraComponent = nexo::Application::m_coordinator->getComponent(previewParams.cameraId); - cameraComponent.clearColor = {0.05f, 0.05f, 0.05f, 0.0f}; app.run(previewParams.sceneId, RenderingType::FRAMEBUFFER); + auto &cameraComponent = nexo::Application::m_coordinator->getComponent(previewParams.cameraId); m_framebuffer = cameraComponent.m_renderTarget; materialModified = false; app.getSceneManager().deleteScene(previewParams.sceneId); } + if (!m_framebuffer) THROW_EXCEPTION(BackendRendererApiFatalFailure, "OPENGL", "Failed to initialize framebuffer in Material Inspector window"); // --- Material preview --- From 2015075218a89ba2de4d4949e81185310a240486 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:41:00 +0900 Subject: [PATCH 072/450] style(ecs-opti): remove redundant cast --- engine/src/ecs/ComponentArray.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index b6c2f1822..9a1c33a77 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -460,7 +460,7 @@ namespace nexo::ecs { { if (m_size < m_componentArray.capacity() / 2 && m_componentArray.capacity() > capacity) { // Only shrink if vectors are significantly oversized to avoid frequent reallocations - auto newCapacity = static_cast(m_size * 2); + size_t newCapacity = m_size * 2; if (newCapacity < capacity) newCapacity = capacity; From 25c92b4c725224fe66ededfbc393d868ac6106c4 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:41:25 +0900 Subject: [PATCH 073/450] refactor(ecs-opti): now using range based sort --- engine/src/ecs/Group.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/engine/src/ecs/Group.hpp b/engine/src/ecs/Group.hpp index 371ed6317..b8da34f21 100644 --- a/engine/src/ecs/Group.hpp +++ b/engine/src/ecs/Group.hpp @@ -526,14 +526,14 @@ namespace nexo::ecs { entities.push_back(drivingArray->getEntityAtIndex(i)); // Sort entities based on the extracted field from the component - std::ranges::sort(entities.begin(), entities.end(), - [&](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); - }); + 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; @@ -743,7 +743,7 @@ namespace nexo::ecs { PartitionStorage(Group* group, EntityKeyExtractor keyExtractor) : m_group(group), m_keyExtractor(std::move(keyExtractor)) {} - bool isDirty() const override { return m_isDirty; } + [[nodiscard]] bool isDirty() const override { return m_isDirty; } void markDirty() override { m_isDirty = true; } /** From 859eae64bcc54ed49f0d07359737c45fe2af243e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:41:41 +0900 Subject: [PATCH 074/450] style(ecs-opti): add header --- engine/src/ecs/SingletonComponentMixin.hpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/engine/src/ecs/SingletonComponentMixin.hpp b/engine/src/ecs/SingletonComponentMixin.hpp index 1540a79cf..827fd3d25 100644 --- a/engine/src/ecs/SingletonComponentMixin.hpp +++ b/engine/src/ecs/SingletonComponentMixin.hpp @@ -1,3 +1,16 @@ +//// SingletonComponentMixin.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: Mehdy MORVAN +// Date: 09/04/2025 +// Description: Header file for the singleton component mixin class +// +/////////////////////////////////////////////////////////////////////////////// #pragma once #include "Access.hpp" @@ -166,6 +179,9 @@ namespace nexo::ecs { * Since this specialization represents a mixin with no singleton components, * there's nothing to initialize. */ - void initializeSingletonComponents() {} + void initializeSingletonComponents() + { + // No-op method + } }; } From 1c7442bedc3272a8b2dbd1d1d2902cb9551d0d9b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 08:42:09 +0900 Subject: [PATCH 075/450] fix(ecs-opti): now using const ref --- editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp | 2 +- editor/src/DocumentWindows/MaterialInspector.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index 9c308910c..6b3ba1af2 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -162,7 +162,7 @@ namespace nexo::editor { auto &app = nexo::getApp(); app.getSceneManager().getScene(previewParams.sceneId).setActiveStatus(false); app.run(previewParams.sceneId, RenderingType::FRAMEBUFFER); - auto &cameraComponent = Application::m_coordinator->getComponent(previewParams.cameraId); + const auto &cameraComponent = Application::m_coordinator->getComponent(previewParams.cameraId); framebuffer = cameraComponent.m_renderTarget; app.getSceneManager().deleteScene(previewParams.sceneId); entityBase = static_cast(entity); diff --git a/editor/src/DocumentWindows/MaterialInspector.cpp b/editor/src/DocumentWindows/MaterialInspector.cpp index 8d9676501..8e1caa0b2 100644 --- a/editor/src/DocumentWindows/MaterialInspector.cpp +++ b/editor/src/DocumentWindows/MaterialInspector.cpp @@ -61,7 +61,7 @@ namespace nexo::editor { utils::genScenePreview("Modify material inspector", {64, 64}, m_ecsEntity, previewParams); auto &app = nexo::getApp(); app.run(previewParams.sceneId, RenderingType::FRAMEBUFFER); - auto &cameraComponent = nexo::Application::m_coordinator->getComponent(previewParams.cameraId); + const auto &cameraComponent = nexo::Application::m_coordinator->getComponent(previewParams.cameraId); m_framebuffer = cameraComponent.m_renderTarget; materialModified = false; app.getSceneManager().deleteScene(previewParams.sceneId); From fc297495212908cefe8c07e50655c93393a10ea5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 09:32:00 +0900 Subject: [PATCH 076/450] feat(ecs-opti): add exceptions for too many lights --- engine/src/core/exceptions/Exceptions.hpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/engine/src/core/exceptions/Exceptions.hpp b/engine/src/core/exceptions/Exceptions.hpp index 7ebc9243a..b915dab0e 100644 --- a/engine/src/core/exceptions/Exceptions.hpp +++ b/engine/src/core/exceptions/Exceptions.hpp @@ -15,8 +15,10 @@ #include #include +#include #include "Exception.hpp" +#include "components/Light.hpp" namespace nexo::core { class FileNotFoundException final : public Exception { @@ -39,4 +41,25 @@ namespace nexo::core { 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 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 TooManyDirectionalLightsException : public Exception { + public: + explicit TooManyDirectionalLightsException(unsigned int sceneRendered, size_t nbDirectionalLights, + const std::source_location loc = std::source_location::current()) + : Exception(std::format("Too many directional lights ({} > {}) in scene [{}]", nbDirectionalLights, MAX_DIRECTIONAL_LIGHTS, sceneRendered), loc) {} + }; } From 84cec44b7ba1d5dd58ccf9f4aeef802e1696df48 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 09:32:20 +0900 Subject: [PATCH 077/450] fix(ecs-opti): add error handling when using too many lights --- engine/src/systems/lights/DirectionalLightsSystem.cpp | 4 ++++ engine/src/systems/lights/PointLightsSystem.cpp | 5 +++++ engine/src/systems/lights/SpotLightsSystem.cpp | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/engine/src/systems/lights/DirectionalLightsSystem.cpp b/engine/src/systems/lights/DirectionalLightsSystem.cpp index ff64e20f1..1b719c69d 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.cpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.cpp @@ -16,6 +16,7 @@ #include "components/Light.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" +#include "core/exceptions/Exceptions.hpp" #include "ecs/Coordinator.hpp" namespace nexo::system { @@ -37,6 +38,9 @@ namespace nexo::system { if (!partition) return; + if (partition->count > MAX_DIRECTIONAL_LIGHTS) + THROW_EXCEPTION(core::TooManyDirectionalLightsException, sceneRendered, partition->count); + const auto directionalLightSpan = get(); for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) diff --git a/engine/src/systems/lights/PointLightsSystem.cpp b/engine/src/systems/lights/PointLightsSystem.cpp index 183ce063f..eb1c99a07 100644 --- a/engine/src/systems/lights/PointLightsSystem.cpp +++ b/engine/src/systems/lights/PointLightsSystem.cpp @@ -13,9 +13,11 @@ /////////////////////////////////////////////////////////////////////////////// #include "PointLightsSystem.hpp" +#include "Exception.hpp" #include "components/Light.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" +#include "core/exceptions/Exceptions.hpp" #include "ecs/Coordinator.hpp" namespace nexo::system { @@ -37,6 +39,9 @@ namespace nexo::system { if (!partition) return; + if (partition->count > MAX_POINT_LIGHTS) + THROW_EXCEPTION(core::TooManyPointLightsException, sceneRendered, partition->count); + const auto pointLightSpan = get(); for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) diff --git a/engine/src/systems/lights/SpotLightsSystem.cpp b/engine/src/systems/lights/SpotLightsSystem.cpp index a37e01aff..93f104600 100644 --- a/engine/src/systems/lights/SpotLightsSystem.cpp +++ b/engine/src/systems/lights/SpotLightsSystem.cpp @@ -16,6 +16,7 @@ #include "components/Light.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" +#include "core/exceptions/Exceptions.hpp" #include "ecs/Coordinator.hpp" namespace nexo::system { @@ -37,6 +38,9 @@ namespace nexo::system { if (!partition) return; + if (partition->count > MAX_SPOT_LIGHTS) + THROW_EXCEPTION(core::TooManySpotLightsException, sceneRendered, partition->count); + const auto spotLightSpan = get(); for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) From 6cd3d2fb185a57ba630bc217a8cc336032fa36c6 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 11:06:34 +0900 Subject: [PATCH 078/450] feat(ecs-opti): add new LOG_ONCE macro to print a log only once --- common/Logger.hpp | 136 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/common/Logger.hpp b/common/Logger.hpp index 48f414d0f..61ee572ad 100644 --- a/common/Logger.hpp +++ b/common/Logger.hpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include namespace nexo { @@ -75,6 +77,61 @@ namespace nexo { std::cout << "[" << toString(level) << "] " << message << std::endl; } + /** + * @brief Registry to track which log messages have been emitted + * + * This class is used to ensure certain messages are only logged once + * until they are explicitly reset. + */ + class OnceRegistry { + public: + /** + * @brief Get the singleton instance of the registry + * + * @return OnceRegistry& Reference to the registry + */ + static OnceRegistry& instance() { + static OnceRegistry registry; + return registry; + } + + /** + * @brief Check if a message has been logged and mark it as logged + * + * @param key The unique identifier for the message + * @return true If this is the first time seeing this message + * @return false If this message has been logged before + */ + bool shouldLog(const std::string& key) { + std::lock_guard lock(m_mutex); + return m_loggedKeys.insert(key).second; + } + + /** + * @brief Reset a specific message so it can be logged again + * + * @param key The unique identifier for the message to reset + */ + void reset(const std::string& key) { + std::lock_guard lock(m_mutex); + m_loggedKeys.erase(key); + } + + /** + * @brief Reset all messages so they can be logged again + */ + void resetAll() { + std::lock_guard lock(m_mutex); + m_loggedKeys.clear(); + } + + private: + OnceRegistry() = default; + + std::unordered_set m_loggedKeys; + std::mutex m_mutex; + }; + class Logger { public: static void setCallback(std::function callback) @@ -102,6 +159,65 @@ namespace nexo { logString(level, ss.str()); } } + + /** + * @brief Generate a key incorporating format string and parameter values + * + * @tparam Args Variadic template for format arguments + * @param fmt Format string + * @param args Format arguments + * @return std::string Key incorporating the parameters + */ + template + static std::string generateKey(const std::string_view fmt, const std::string& location, Args&&... args) + { + std::stringstream ss; + ss << fmt << "@" << location << "|"; + + // Add parameter values to the key + ((ss << toFormatFriendly(args) << "|"), ...); + + return ss.str(); + } + + /** + * @brief Log a message only once until reset + * + * This method logs a message only the first time it is called with a given key. + * The key incorporates both the format string and the parameter values. + * + * @tparam Args Variadic template for format arguments + * @param level Log level + * @param loc Source location of the log call + * @param fmt Format string + * @param key Key including format string and parameter values + * @param args Format arguments + */ + template + static void logOnce(const LogLevel level, const std::source_location loc, + const std::string_view fmt, const std::string& key, Args &&... args) + { + if (OnceRegistry::instance().shouldLog(key)) { + logWithFormat(level, loc, fmt, std::forward(args)...); + } + } + + /** + * @brief Reset a specific log message so it can be logged again with logOnce + * + * @param key The unique identifier for the log message to reset + */ + static void resetOnce(const std::string& key) { + OnceRegistry::instance().reset(key); + } + + /** + * @brief Reset all log messages so they can be logged again with logOnce + */ + static void resetAllOnce() { + OnceRegistry::instance().resetAll(); + } + private: static void logString(const LogLevel level, const std::string &message) { @@ -121,6 +237,26 @@ namespace nexo { #define LOG_EXCEPTION(exception) \ LOG(NEXO_ERROR, "{}:{} - Exception: {}", exception.getFile(), exception.getLine(), exception.getMessage()) +/** + * @brief Generate a unique key for a log message incorporating format and parameters + * + * This creates a key that includes both the format string and the parameter values, + * allowing specific message instances to be reset later. + */ +#define NEXO_LOG_ONCE_KEY(fmt, ...) \ + nexo::Logger::generateKey(fmt, std::string(__FILE__) + ":" + std::to_string(__LINE__), ##__VA_ARGS__) + +/** + * @brief Log a message only once until it's reset + * + * This ensures the message is only logged the first time this line is executed + * with these specific parameters. Subsequent calls with the same parameters + * will be ignored until the message is reset. + */ +#define LOG_ONCE(level, fmt, ...) \ + nexo::Logger::logOnce(level, std::source_location::current(), fmt, \ + NEXO_LOG_ONCE_KEY(fmt, ##__VA_ARGS__), ##__VA_ARGS__) + #define NEXO_FATAL nexo::LogLevel::FATAL #define NEXO_ERROR nexo::LogLevel::ERROR #define NEXO_WARN nexo::LogLevel::WARN From d960dbd278a1bc7749d0fb9d0c53d997feb41d59 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 11:06:59 +0900 Subject: [PATCH 079/450] fix(ecs-opti): add error handling + logs in the systems --- engine/src/systems/CameraSystem.cpp | 10 +++++++--- engine/src/systems/RenderSystem.cpp | 9 ++++++--- engine/src/systems/lights/AmbientLightSystem.cpp | 10 +++++++--- engine/src/systems/lights/DirectionalLightsSystem.cpp | 8 +++++--- engine/src/systems/lights/DirectionalLightsSystem.hpp | 2 ++ engine/src/systems/lights/PointLightsSystem.cpp | 8 +++++--- engine/src/systems/lights/PointLightsSystem.hpp | 2 ++ engine/src/systems/lights/SpotLightsSystem.cpp | 8 +++++--- engine/src/systems/lights/SpotLightsSystem.hpp | 2 ++ 9 files changed, 41 insertions(+), 18 deletions(-) diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index 508f0c68a..44fce4b0c 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -21,6 +21,7 @@ #include "core/event/KeyCodes.hpp" #include "Application.hpp" #include "core/event/WindowEvent.hpp" +#include "core/exceptions/Exceptions.hpp" #include #include @@ -39,9 +40,12 @@ namespace nexo::system { const auto *partition = scenePartition.getPartition(sceneRendered); - //TODO: Throw exception here ? - if (!partition) - return; + if (!partition) { + LOG_ONCE(NEXO_WARN, "No camera found in scene {}, skipping", sceneRendered); + return; + } + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No camera found in scene {}, skipping", sceneRendered)); + const auto cameraSpan = get(); const auto transformComponentArray = get(); const auto entitySpan = m_group->entities(); diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 89f8ccb97..31c52513a 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -21,6 +21,7 @@ #include "components/Render.hpp" #include "renderer/RenderCommand.hpp" #include "ecs/Coordinator.hpp" +#include "core/exceptions/Exceptions.hpp" #include @@ -98,9 +99,11 @@ namespace nexo::system { const auto *partition = scenePartition.getPartition(sceneRendered); - //TODO: Throw exception here ? - if (!partition) - return; + if (!partition) { + LOG_ONCE(NEXO_WARN, "Nothing to render in scene {}, skipping", sceneRendered); + return; + } + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneRendered)); const auto transformSpan = get(); const auto renderSpan = get(); diff --git a/engine/src/systems/lights/AmbientLightSystem.cpp b/engine/src/systems/lights/AmbientLightSystem.cpp index df4d31ae9..ae7ca626b 100644 --- a/engine/src/systems/lights/AmbientLightSystem.cpp +++ b/engine/src/systems/lights/AmbientLightSystem.cpp @@ -14,6 +14,7 @@ #include "AmbientLightSystem.hpp" +#include "Logger.hpp" #include "components/Light.hpp" #include "ecs/Coordinator.hpp" @@ -32,9 +33,12 @@ namespace nexo::system { const auto *partition = scenePartition.getPartition(sceneRendered); - //TODO: Throw exception here ? - if (!partition) - return; + if (!partition) { + LOG_ONCE(NEXO_WARN, "No ambient light found in scene {}, skipping", sceneRendered); + return; + } + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No ambient light found in scene {}, skipping", sceneRendered)); + const auto ambientSpan = get(); if (ambientSpan.size()) diff --git a/engine/src/systems/lights/DirectionalLightsSystem.cpp b/engine/src/systems/lights/DirectionalLightsSystem.cpp index 1b719c69d..767dee43b 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.cpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.cpp @@ -34,9 +34,11 @@ namespace nexo::system { const auto *partition = scenePartition.getPartition(sceneRendered); - //TODO: Throw exception here ? - if (!partition) - return; + if (!partition) { + LOG_ONCE(NEXO_WARN, "No directional light found in scene {}, skipping", sceneRendered); + return; + } + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No directional light found in scene {}, skipping", sceneRendered)); if (partition->count > MAX_DIRECTIONAL_LIGHTS) THROW_EXCEPTION(core::TooManyDirectionalLightsException, sceneRendered, partition->count); diff --git a/engine/src/systems/lights/DirectionalLightsSystem.hpp b/engine/src/systems/lights/DirectionalLightsSystem.hpp index 76563e9ed..c9324a4b2 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.hpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.hpp @@ -34,6 +34,8 @@ namespace nexo::system { * * @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< diff --git a/engine/src/systems/lights/PointLightsSystem.cpp b/engine/src/systems/lights/PointLightsSystem.cpp index eb1c99a07..dcd669f60 100644 --- a/engine/src/systems/lights/PointLightsSystem.cpp +++ b/engine/src/systems/lights/PointLightsSystem.cpp @@ -35,9 +35,11 @@ namespace nexo::system { const auto *partition = scenePartition.getPartition(sceneRendered); - //TODO: Throw exception here ? - if (!partition) - return; + if (!partition) { + LOG_ONCE(NEXO_WARN, "No point light found in scene {}, skipping", sceneRendered); + return; + } + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No point light found in scene {}, skipping", sceneRendered)); if (partition->count > MAX_POINT_LIGHTS) THROW_EXCEPTION(core::TooManyPointLightsException, sceneRendered, partition->count); diff --git a/engine/src/systems/lights/PointLightsSystem.hpp b/engine/src/systems/lights/PointLightsSystem.hpp index ec2372ab3..5a9619927 100644 --- a/engine/src/systems/lights/PointLightsSystem.hpp +++ b/engine/src/systems/lights/PointLightsSystem.hpp @@ -34,6 +34,8 @@ namespace nexo::system { * * @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< diff --git a/engine/src/systems/lights/SpotLightsSystem.cpp b/engine/src/systems/lights/SpotLightsSystem.cpp index 93f104600..c0842bc30 100644 --- a/engine/src/systems/lights/SpotLightsSystem.cpp +++ b/engine/src/systems/lights/SpotLightsSystem.cpp @@ -34,9 +34,11 @@ namespace nexo::system { const auto *partition = scenePartition.getPartition(sceneRendered); - //TODO: Throw exception here ? - if (!partition) - return; + if (!partition) { + LOG_ONCE(NEXO_WARN, "No spot light found in scene {}, skipping", sceneRendered); + return; + } + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No spot light found in scene {}, skipping", sceneRendered)); if (partition->count > MAX_SPOT_LIGHTS) THROW_EXCEPTION(core::TooManySpotLightsException, sceneRendered, partition->count); diff --git a/engine/src/systems/lights/SpotLightsSystem.hpp b/engine/src/systems/lights/SpotLightsSystem.hpp index ce5ede2c7..f292aae49 100644 --- a/engine/src/systems/lights/SpotLightsSystem.hpp +++ b/engine/src/systems/lights/SpotLightsSystem.hpp @@ -33,6 +33,8 @@ namespace nexo::system { * * @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< From d7381182e6222d77a00299f47c300502b913dec0 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 11:29:07 +0900 Subject: [PATCH 080/450] refactor(ecs-opti): now use compile time caching to avoid dynamic_cast when caching singleton components --- engine/src/ecs/SingletonComponentMixin.hpp | 31 +++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/engine/src/ecs/SingletonComponentMixin.hpp b/engine/src/ecs/SingletonComponentMixin.hpp index 827fd3d25..6d73cabc7 100644 --- a/engine/src/ecs/SingletonComponentMixin.hpp +++ b/engine/src/ecs/SingletonComponentMixin.hpp @@ -64,11 +64,18 @@ namespace nexo::ecs { protected: /** - * @brief Cache of singleton components for faster access + * @brief Cache of strongly-typed singleton components for faster access * - * Maps component type IDs to their singleton component instances + * Stores components with their specific types to avoid dynamic casting */ - std::unordered_map> m_singletonComponents; + 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 @@ -89,7 +96,10 @@ namespace nexo::ecs { { auto* derived = static_cast(this); std::shared_ptr instance = derived->coord->template getRawSingletonComponent(); - m_singletonComponents[getUniqueComponentTypeID()] = instance; + + // Store in the type-specific cache + auto typedInstance = std::static_pointer_cast>(instance); + TypedComponentCache::instance = typedInstance; } public: @@ -117,24 +127,21 @@ namespace nexo::ecs { template std::conditional_t(), const T&, T&> getSingleton() { - const ComponentType typeIndex = getUniqueComponentTypeID(); - - if (!m_singletonComponents.contains(typeIndex)) { + if (!TypedComponentCache::instance) { // Late binding in case the singleton was registered after system creation cacheSingletonComponent(); } - const auto& singletonComponentPtr = m_singletonComponents[typeIndex]; - auto* componentWrapper = dynamic_cast*>(singletonComponentPtr.get()); + auto& typedComponent = TypedComponentCache::instance; - if (!componentWrapper) + if (!typedComponent) THROW_EXCEPTION(SingletonComponentNotRegistered); // Return the reference with appropriate constness if constexpr (hasReadSingletonAccess()) - return const_cast(componentWrapper->getInstance()); + return const_cast(typedComponent->getInstance()); else - return componentWrapper->getInstance(); + return typedComponent->getInstance(); } }; From 3c99b9dee8707acedb74d9a44be7a52482810e8e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 11:34:46 +0900 Subject: [PATCH 081/450] refactor(ecs-opti): shrinkIfNeeded function is now more leniant to avoid frequent re-allocations --- engine/src/ecs/ComponentArray.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 9a1c33a77..ec572caa5 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -20,6 +20,7 @@ #include #include +#include namespace nexo::ecs { /** @@ -458,9 +459,9 @@ namespace nexo::ecs { */ void shrinkIfNeeded() { - if (m_size < m_componentArray.capacity() / 2 && m_componentArray.capacity() > capacity) { + 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 = m_size * 2; + size_t newCapacity = std::max(m_size * 2, static_cast(capacity)); if (newCapacity < capacity) newCapacity = capacity; From 71c0d58bab548557bdf8edf6e00d74026dda6e3f Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 11:51:59 +0900 Subject: [PATCH 082/450] fix(ecs-opti): now use the getComponent method instead of using the coord --- engine/src/systems/CameraSystem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index 44fce4b0c..e8d4f51de 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -233,8 +233,8 @@ namespace nexo::system { continue; } - auto &transformCameraComponent = coord->getComponent(entity); - const auto &transformTargetComponent = coord->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; From dc949e9f30db79eb877d364cf471fc3a512bd28b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 26 Mar 2025 06:11:55 +0900 Subject: [PATCH 083/450] init(document-windows): init branch From 2f153d08bea2e83cf8e5bfe4fbec50fc7b7ef459 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 20:18:58 +0900 Subject: [PATCH 084/450] fix(document-windows): imgui demo dont show up anymore --- editor/src/Editor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 8062079fa..d9e2e30f2 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -332,7 +332,7 @@ namespace nexo::editor { buildDockspace(); drawMenuBar(); - ImGui::ShowDemoWindow(); + // ImGui::ShowDemoWindow(); From f7775d2a1b9fc285ca3f160445a67044c31a87ba Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 20:19:18 +0900 Subject: [PATCH 085/450] refactor(document-windows): now use std::any for sub inspector data --- editor/src/DocumentWindows/InspectorWindow.hpp | 18 +++++++++++++----- .../src/DocumentWindows/MaterialInspector.cpp | 9 ++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/editor/src/DocumentWindows/InspectorWindow.hpp b/editor/src/DocumentWindows/InspectorWindow.hpp index 662bd205d..b45ee352f 100644 --- a/editor/src/DocumentWindows/InspectorWindow.hpp +++ b/editor/src/DocumentWindows/InspectorWindow.hpp @@ -122,17 +122,25 @@ namespace nexo::editor { * @return std::variant A variant holding a pointer to components::Material if set, * or std::monostate if no data is available. */ - template - std::variant getSubInspectorData() const + template + Data *getSubInspectorData() const { - auto it = m_subInspectorData.find(std::type_index(typeid(T))); - return (it != m_subInspectorData.end()) ? it->second : std::variant{std::monostate{}}; + auto it = m_subInspectorData.find(std::type_index(typeid(WindowType))); + if (it != m_subInspectorData.end()) { + try { + return std::any_cast(it->second); + } + catch (const std::bad_any_cast& e) { + return nullptr; + } + } + return nullptr; } private: std::unordered_map> m_entityProperties; std::unordered_map m_subInspectorVisibility; - std::unordered_map> m_subInspectorData; + std::unordered_map m_subInspectorData; /** * @brief Displays the scene's properties in the inspector UI. diff --git a/editor/src/DocumentWindows/MaterialInspector.cpp b/editor/src/DocumentWindows/MaterialInspector.cpp index 8e1caa0b2..af9abc994 100644 --- a/editor/src/DocumentWindows/MaterialInspector.cpp +++ b/editor/src/DocumentWindows/MaterialInspector.cpp @@ -16,6 +16,7 @@ #include "DocumentWindows/InspectorWindow.hpp" #include "Exception.hpp" #include "components/Render.hpp" +#include "components/Render3D.hpp" #include "utils/ScenePreview.hpp" #include "components/Camera.hpp" #include "Components/Widgets.hpp" @@ -77,11 +78,9 @@ namespace nexo::editor { auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); if (!inspectorWindow) return; - auto materialVariant = inspectorWindow->getSubInspectorData(); - if (std::holds_alternative(materialVariant)) { - auto materialPtr = std::get(materialVariant); - materialModified = Widgets::drawMaterialInspector(materialPtr); - } + auto materialVariant = inspectorWindow->getSubInspectorData(); + if (materialVariant) + materialModified = Widgets::drawMaterialInspector(materialVariant); } void MaterialInspector::show() From adc830a47074fbb87975ff3b312d9b8b9792e4f6 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 20:21:43 +0900 Subject: [PATCH 086/450] docs(documents-windows): update docstring for getSubInspectorData --- editor/src/DocumentWindows/InspectorWindow.hpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/editor/src/DocumentWindows/InspectorWindow.hpp b/editor/src/DocumentWindows/InspectorWindow.hpp index b45ee352f..1def4680f 100644 --- a/editor/src/DocumentWindows/InspectorWindow.hpp +++ b/editor/src/DocumentWindows/InspectorWindow.hpp @@ -112,15 +112,14 @@ namespace nexo::editor { } /** - * @brief Retrieves the material data associated with the specified sub-inspector type. + * @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 T. - * If an entry for T exists, it returns the associated pointer to a material; otherwise, it returns a variant - * containing std::monostate to indicate that no data is set. + * 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 T The sub-inspector type used to look up the associated data. - * @return std::variant A variant holding a pointer to components::Material if set, - * or std::monostate if no data is available. + * @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 Data *getSubInspectorData() const From 4bf04b81436c4c0333a412bb73fad35d7b844ce1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 22:02:12 +0900 Subject: [PATCH 087/450] feat(document-windows): add new user log level --- common/Logger.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/Logger.hpp b/common/Logger.hpp index 61ee572ad..5c21e3325 100644 --- a/common/Logger.hpp +++ b/common/Logger.hpp @@ -44,7 +44,8 @@ namespace nexo { WARN, INFO, DEBUG, - DEV + DEV, + USER }; inline std::string toString(const LogLevel level) From bb3ef2cda06ea0d7cc6cf62a988ed14872a1f478 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 22:02:37 +0900 Subject: [PATCH 088/450] feat(document-windows): add new utils function to open the file explorer --- editor/src/utils/FileSystem.cpp | 27 +++++++++++++++++++++++++++ editor/src/utils/FileSystem.hpp | 25 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 editor/src/utils/FileSystem.cpp create mode 100644 editor/src/utils/FileSystem.hpp diff --git a/editor/src/utils/FileSystem.cpp b/editor/src/utils/FileSystem.cpp new file mode 100644 index 000000000..895f0c3df --- /dev/null +++ b/editor/src/utils/FileSystem.cpp @@ -0,0 +1,27 @@ +//// FileSystem.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: 15/04/2025 +// Description: Source file for the file system utils functions +// +/////////////////////////////////////////////////////////////////////////////// + +#include "FileSystem.hpp" + +namespace nexo::editor::utils { + void openFolder(const std::string &folderPath) + { + #ifdef _WIN32 + ShellExecuteA(nullptr, "open", folderPath.c_str(), nullptr, nullptr, SW_SHOWDEFAULT); + #else + std::string command = "xdg-open " + folderPath; + std::system(command.c_str()); + #endif + } +} diff --git a/editor/src/utils/FileSystem.hpp b/editor/src/utils/FileSystem.hpp new file mode 100644 index 000000000..fac9a39b3 --- /dev/null +++ b/editor/src/utils/FileSystem.hpp @@ -0,0 +1,25 @@ +//// FileSystem.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: Mehdy MORVAN +// Date: 15/04/2025 +// Description: Header file for utils function to handle files +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include + +#ifdef _WIN32 + #include +#endif + +namespace nexo::editor::utils { + void openFolder(const std::string &folderPath); +} From b40e7a02956a632e14e58200833a8fcbddabcfe5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 22:03:20 +0900 Subject: [PATCH 089/450] feat(document-windows): add a log file system, when reaching a certain amount of logs, they get flushed in the file to better handle memory usage --- editor/src/DocumentWindows/ConsoleWindow.cpp | 102 ++++++++++++++++--- editor/src/DocumentWindows/ConsoleWindow.hpp | 8 +- logs/.gitkeep | 0 3 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 logs/.gitkeep diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp index bba45007f..59cf8d6df 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow.cpp @@ -14,7 +14,19 @@ #include #include +#include +#include +#include +#include +#include +#include +#include #include "ConsoleWindow.hpp" +#include "Editor.hpp" +#include "Logger.hpp" +#include "Path.hpp" +#include "tinyfiledialogs.h" +#include "utils/FileSystem.hpp" #include @@ -38,8 +50,9 @@ namespace nexo::editor { case loguru::Verbosity_WARNING: return "[WARNING]"; case loguru::Verbosity_INFO: return "[INFO]"; case loguru::Verbosity_INVALID: return "[INVALID]"; - case loguru::Verbosity_1: return "[DEBUG]"; - case loguru::Verbosity_2: return "[DEV]"; + case loguru::Verbosity_1: return "[USER]"; + case loguru::Verbosity_2: return "[DEBUG]"; + case loguru::Verbosity_3: return "[DEV]"; default: return "[UNKNOWN]"; } } @@ -61,8 +74,9 @@ namespace nexo::editor { case LogLevel::ERROR: return loguru::Verbosity_ERROR; case LogLevel::WARN: return loguru::Verbosity_WARNING; case LogLevel::INFO: return loguru::Verbosity_INFO; - case LogLevel::DEBUG: return loguru::Verbosity_1; - case LogLevel::DEV: return loguru::Verbosity_2; + 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; } return loguru::Verbosity_INVALID; @@ -94,15 +108,35 @@ namespace nexo::editor { break; // Yellow case loguru::Verbosity_INFO: color = ImVec4(0.0f, 0.5f, 1.0f, 1.0f); break; // Blue - case loguru::Verbosity_1: color = ImVec4(0.898f, 0.0f, 1.0f, 1.0f); // Debug + 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_2: color = ImVec4(0.388f, 0.055f, 0.851f, 1.0f); // Debug + 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; } + static const std::string generateLogFilePath() + { + auto now = std::chrono::system_clock::now(); + std::time_t now_time = std::chrono::system_clock::to_time_t(now); + + std::tm localTime; +#if defined(_MSC_VER) || defined(__MINGW32__) + localtime_s(&localTime, &now_time); +#else + localtime_r(&now_time, &localTime); +#endif + + std::ostringstream oss; + // Format: YYYY-MM-DD-HHMMSS, e.g., 2025-04-15-123045 + oss << "../logs/NEXO-" << std::put_time(&localTime, "%Y-%m-%d-%H%M%S") << ".log"; + return oss.str(); + } + void ConsoleWindow::loguruCallback([[maybe_unused]] void *userData, const loguru::Message &message) @@ -126,6 +160,9 @@ namespace nexo::editor { VLOG_F(loguruLevel, "%s", message.c_str()); }; Logger::setCallback(engineLogCallback); + m_logFilePath = Path::resolvePathRelativeToExe(generateLogFilePath()).string(); + m_logs.reserve(m_maxLogCapacity); + m_bufferLogsToExport.reserve(m_maxBufferLogToExportCapacity); }; void ConsoleWindow::setup() @@ -140,25 +177,41 @@ namespace nexo::editor { void ConsoleWindow::addLog(const LogMessage &message) { + if (m_logs.size() >= m_maxLogCapacity) { + m_bufferLogsToExport.push_back(message); + m_logs.erase(m_logs.begin()); + } + + if (m_bufferLogsToExport.size() > m_maxBufferLogToExportCapacity) { + exportLogsBuffered(); + m_bufferLogsToExport.clear(); + } + m_logs.push_back(message); } void ConsoleWindow::clearLog() { + exportLogsBuffered(); + 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; + } + } + m_logs.clear(); - items.clear(); } template void ConsoleWindow::addLog(const char *fmt, Args &&... args) { - try - { + try { std::string formattedMessage = std::vformat(fmt, std::make_format_args(std::forward(args)...)); - items.emplace_back(formattedMessage); - } catch (const std::format_error &e) - { - items.emplace_back(std::format("[Error formatting log message]: {}", e.what())); + m_logs.emplace_back(LogMessage{nexoLevelToLoguruLevel(LogLevel::USER), formattedMessage}); + } catch (const std::format_error &e) { + m_logs.emplace_back(LogMessage{nexoLevelToLoguruLevel(LogLevel::ERROR), std::format("[Error formatting log message]: {}", e.what())}); } scrollToBottom = true; @@ -167,7 +220,7 @@ namespace nexo::editor { void ConsoleWindow::executeCommand(const char *command_line) { commands.emplace_back(command_line); - addLog("# {}\n", command_line); + addLog("{}\n", command_line); } void ConsoleWindow::calcLogPadding() @@ -206,6 +259,17 @@ namespace nexo::editor { ImGui::PopTextWrapPos(); } + void ConsoleWindow::exportLogsBuffered() + { + 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; + } + } + void ConsoleWindow::showVerbositySettingsPopup() { ImGui::Text("Select Verbosity Levels"); @@ -219,8 +283,9 @@ namespace nexo::editor { {loguru::Verbosity_ERROR, "ERROR"}, {loguru::Verbosity_WARNING, "WARNING"}, {loguru::Verbosity_INFO, "INFO"}, - {loguru::Verbosity_1, "DEBUG"}, - {loguru::Verbosity_2, "DEV"}, + {loguru::Verbosity_1, "USER"}, + {loguru::Verbosity_2, "DEBUG"}, + {loguru::Verbosity_3, "DEV"} }; for (const auto &[level, name]: levels) @@ -240,6 +305,11 @@ namespace nexo::editor { } } + ImGui::Separator(); + ImGui::Checkbox("File logging", &m_exportLog); + if (ImGui::Button("Open log folder")) + utils::openFolder(Path::resolvePathRelativeToExe("../logs")); + ImGui::EndPopup(); } diff --git a/editor/src/DocumentWindows/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow.hpp index c8c0531d5..6bd9d68d2 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.hpp +++ b/editor/src/DocumentWindows/ConsoleWindow.hpp @@ -82,18 +82,23 @@ namespace nexo::editor { private: float m_logPadding = 0.0f; char inputBuf[512] = {}; - std::deque items; bool scrollToBottom = true; std::vector commands; // History of executed commands. + size_t m_maxLogCapacity = 200; + size_t m_maxBufferLogToExportCapacity = 20; + std::string m_logFilePath = ""; + bool m_exportLog = true; std::set selectedVerbosityLevels = { loguru::Verbosity_FATAL, loguru::Verbosity_ERROR, loguru::Verbosity_WARNING, loguru::Verbosity_INFO, + loguru::Verbosity_1, }; std::vector m_logs; + std::vector m_bufferLogsToExport; /** * @brief Clears all log entries and display items. @@ -112,6 +117,7 @@ namespace nexo::editor { */ void addLog(const LogMessage& message); void displayLog(loguru::Verbosity verbosity, const std::string &msg) const; + void exportLogsBuffered(); void showVerbositySettingsPopup(); /** diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 000000000..e69de29bb From cb5e7dbfd3d0f7882439b6b0b4b2da2e9fb960fa Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 22:03:34 +0900 Subject: [PATCH 090/450] chore(document-windows): add source files --- editor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index d7ebae4a5..cc9337893 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -21,6 +21,7 @@ set(SRCS editor/src/utils/ScenePreview.cpp editor/src/utils/Config.cpp editor/src/utils/String.cpp + editor/src/utils/FileSystem.cpp editor/src/Editor.cpp editor/src/WindowRegistry.cpp editor/src/DockingRegistry.cpp From b649803a6bba475b7bd273bcd3d1645c93ac9cd0 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 22:11:42 +0900 Subject: [PATCH 091/450] fix(document-windows): now properly computes the padding based on the level + fix windows compilation --- editor/src/DocumentWindows/ConsoleWindow.cpp | 33 +++++++++----------- editor/src/DocumentWindows/ConsoleWindow.hpp | 23 ++++++++------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp index 59cf8d6df..34f11b690 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow.cpp @@ -214,29 +214,24 @@ namespace nexo::editor { m_logs.emplace_back(LogMessage{nexoLevelToLoguruLevel(LogLevel::ERROR), std::format("[Error formatting log message]: {}", e.what())}); } - scrollToBottom = true; + m_scrollToBottom = true; } void ConsoleWindow::executeCommand(const char *command_line) { - commands.emplace_back(command_line); + m_commands.emplace_back(command_line); addLog("{}\n", command_line); } void ConsoleWindow::calcLogPadding() { m_logPadding = 0.0f; - for (const auto &[verbosity, message, prefix]: m_logs) - { - if (!selectedVerbosityLevels.contains(verbosity)) - continue; - const std::string tag = verbosityToString(verbosity); + 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; - } } m_logPadding += ImGui::GetStyle().ItemSpacing.x; } @@ -290,16 +285,16 @@ namespace nexo::editor { for (const auto &[level, name]: levels) { - bool selected = (selectedVerbosityLevels.contains(level)); + bool selected = (m_selectedVerbosityLevels.contains(level)); if (ImGui::Checkbox(name, &selected)) { if (selected) { - selectedVerbosityLevels.insert(level); + m_selectedVerbosityLevels.insert(level); calcLogPadding(); } else { - selectedVerbosityLevels.erase(level); + m_selectedVerbosityLevels.erase(level); calcLogPadding(); } } @@ -308,7 +303,7 @@ namespace nexo::editor { ImGui::Separator(); ImGui::Checkbox("File logging", &m_exportLog); if (ImGui::Button("Open log folder")) - utils::openFolder(Path::resolvePathRelativeToExe("../logs")); + utils::openFolder(Path::resolvePathRelativeToExe("../logs").string()); ImGui::EndPopup(); } @@ -328,7 +323,7 @@ namespace nexo::editor { auto id = 0; for (const auto &[verbosity, message, prefix]: m_logs) { - if (!selectedVerbosityLevels.contains(verbosity)) + if (!m_selectedVerbosityLevels.contains(verbosity)) continue; ImGui::PushID(id++); @@ -336,17 +331,17 @@ namespace nexo::editor { ImGui::PopID(); } - if (scrollToBottom) + if (m_scrollToBottom) ImGui::SetScrollHereY(1.0f); - scrollToBottom = false; + m_scrollToBottom = false; ImGui::EndChild(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60.0f); - if (ImGui::InputText("Input", inputBuf, IM_ARRAYSIZE(inputBuf), ImGuiInputTextFlags_EnterReturnsTrue)) + if (ImGui::InputText("Input", m_inputBuf, IM_ARRAYSIZE(m_inputBuf), ImGuiInputTextFlags_EnterReturnsTrue)) { - executeCommand(inputBuf); - std::memset(inputBuf, '\0', sizeof(inputBuf)); + executeCommand(m_inputBuf); + std::memset(m_inputBuf, '\0', sizeof(m_inputBuf)); } ImGui::SameLine(); diff --git a/editor/src/DocumentWindows/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow.hpp index 6bd9d68d2..4385c4d78 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.hpp +++ b/editor/src/DocumentWindows/ConsoleWindow.hpp @@ -75,21 +75,19 @@ namespace nexo::editor { void show() override; void update() override; - template - void addLog(const char* fmt, Args&&... args); void executeCommand(const char* command_line); private: - float m_logPadding = 0.0f; - char inputBuf[512] = {}; - bool scrollToBottom = true; - std::vector commands; // History of executed commands. - size_t m_maxLogCapacity = 200; - size_t m_maxBufferLogToExportCapacity = 20; + char m_inputBuf[512] = {}; + std::vector m_commands; // History of executed commands. + std::string m_logFilePath = ""; bool m_exportLog = true; - std::set selectedVerbosityLevels = { + bool m_scrollToBottom = true; + + + std::set m_selectedVerbosityLevels = { loguru::Verbosity_FATAL, loguru::Verbosity_ERROR, loguru::Verbosity_WARNING, @@ -97,8 +95,12 @@ namespace nexo::editor { 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. @@ -116,6 +118,9 @@ namespace nexo::editor { * @param message The log message to append. */ void addLog(const LogMessage& message); + + template + void addLog(const char* fmt, Args&&... args); void displayLog(loguru::Verbosity verbosity, const std::string &msg) const; void exportLogsBuffered(); void showVerbositySettingsPopup(); From 5fdd501fd78d8b58656411ff5b91260ab4f3827b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 22:23:22 +0900 Subject: [PATCH 092/450] fix(document-windows): trying to fix windows --- editor/src/DocumentWindows/ConsoleWindow.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp index 34f11b690..fae746ab2 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow.cpp @@ -209,9 +209,15 @@ namespace nexo::editor { { try { std::string formattedMessage = std::vformat(fmt, std::make_format_args(std::forward(args)...)); - m_logs.emplace_back(LogMessage{nexoLevelToLoguruLevel(LogLevel::USER), formattedMessage}); + m_logs.push_back({ + .verbosity = nexoLevelToLoguruLevel(LogLevel::USER), + .message = formattedMessage, + .prefix = ""}); } catch (const std::format_error &e) { - m_logs.emplace_back(LogMessage{nexoLevelToLoguruLevel(LogLevel::ERROR), std::format("[Error formatting log message]: {}", e.what())}); + m_logs.push_back({ + .verbosity = nexoLevelToLoguruLevel(LogLevel::ERROR), + .message = std::format("[Error formatting log message]: {}", e.what()), + .prefix = ""}); } m_scrollToBottom = true; From 03a55d649428dce1edc13b212f5c496a03b69237 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 22:32:34 +0900 Subject: [PATCH 093/450] fix(document-windows): trying to fix again --- editor/src/DocumentWindows/ConsoleWindow.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp index fae746ab2..f10b81ada 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow.cpp @@ -209,15 +209,17 @@ namespace nexo::editor { { try { std::string formattedMessage = std::vformat(fmt, std::make_format_args(std::forward(args)...)); - m_logs.push_back({ - .verbosity = nexoLevelToLoguruLevel(LogLevel::USER), - .message = formattedMessage, - .prefix = ""}); + LogMessage newMessage; + newMessage.verbosity = nexoLevelToLoguruLevel(LogLevel::USER); + newMessage.message = formattedMessage; + newMessage.prefix = ""; + m_logs.push_back(newMessage); } catch (const std::format_error &e) { - m_logs.push_back({ - .verbosity = nexoLevelToLoguruLevel(LogLevel::ERROR), - .message = std::format("[Error formatting log message]: {}", e.what()), - .prefix = ""}); + LogMessage newMessage; + newMessage.verbosity = nexoLevelToLoguruLevel(LogLevel::ERROR); + newMessage.message = std::format("[Error formatting log message]: {}", e.what()); + newMessage.prefix = ""; + m_logs.push_back(newMessage); } m_scrollToBottom = true; From 4e5da49186724689d1d50407d5e65331aa132f1e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 22:54:27 +0900 Subject: [PATCH 094/450] fix(document-windows): trying to fix windows again --- editor/src/DocumentWindows/ConsoleWindow.cpp | 31 ++++++++++++-------- editor/src/DocumentWindows/ConsoleWindow.hpp | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp index f10b81ada..d42db826c 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow.cpp @@ -142,11 +142,11 @@ namespace nexo::editor { const loguru::Message &message) { const auto console = static_cast(userData); - console->addLog({ - .verbosity = message.verbosity, - .message = message.message, - .prefix = message.prefix - }); + LogMessage newMessage; + newMessage.verbosity = message.verbosity; + newMessage.message = message.message; + newMessage.prefix = message.prefix; + console->addLog(newMessage); } @@ -208,16 +208,23 @@ namespace nexo::editor { void ConsoleWindow::addLog(const char *fmt, Args &&... args) { try { - std::string formattedMessage = std::vformat(fmt, std::make_format_args(std::forward(args)...)); + // Create a buffer for the formatted message + char buffer[1024]; + snprintf(buffer, sizeof(buffer), fmt, std::forward(args)...); + LogMessage newMessage; newMessage.verbosity = nexoLevelToLoguruLevel(LogLevel::USER); - newMessage.message = formattedMessage; + newMessage.message = std::string(buffer); newMessage.prefix = ""; m_logs.push_back(newMessage); - } catch (const std::format_error &e) { + } catch (const std::exception &e) { LogMessage newMessage; newMessage.verbosity = nexoLevelToLoguruLevel(LogLevel::ERROR); - newMessage.message = std::format("[Error formatting log message]: {}", e.what()); + + char errorBuffer[1024]; + snprintf(errorBuffer, sizeof(errorBuffer), "[Error formatting log message]: %s", e.what()); + newMessage.message = std::string(errorBuffer); + newMessage.prefix = ""; m_logs.push_back(newMessage); } @@ -225,10 +232,10 @@ namespace nexo::editor { m_scrollToBottom = true; } - void ConsoleWindow::executeCommand(const char *command_line) + void ConsoleWindow::executeCommand(const char *commandLine) { - m_commands.emplace_back(command_line); - addLog("{}\n", command_line); + m_commands.emplace_back(commandLine); + addLog("%s", commandLine); } void ConsoleWindow::calcLogPadding() diff --git a/editor/src/DocumentWindows/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow.hpp index 4385c4d78..189e0fe27 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.hpp +++ b/editor/src/DocumentWindows/ConsoleWindow.hpp @@ -75,7 +75,7 @@ namespace nexo::editor { void show() override; void update() override; - void executeCommand(const char* command_line); + void executeCommand(const char* commandLine); private: char m_inputBuf[512] = {}; From 3ffecaf69b5bbe07eae8ea6d031767c5d091e77e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 15 Apr 2025 23:19:29 +0900 Subject: [PATCH 095/450] fix(document-windows): trying to fix again fix again wola pls bon ntm fix stp last fix --- common/Logger.hpp | 9 +++-- editor/src/DocumentWindows/ConsoleWindow.cpp | 42 ++++++++++---------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/common/Logger.hpp b/common/Logger.hpp index 5c21e3325..6f7d1f85f 100644 --- a/common/Logger.hpp +++ b/common/Logger.hpp @@ -40,7 +40,7 @@ namespace nexo { enum class LogLevel { FATAL, - ERROR, + ERR, WARN, INFO, DEBUG, @@ -53,9 +53,10 @@ namespace nexo { switch (level) { case LogLevel::FATAL: return "FATAL"; - case LogLevel::ERROR: return "ERROR"; + case LogLevel::ERR: return "ERROR"; case LogLevel::WARN: return "WARN"; case LogLevel::INFO: return "INFO"; + case LogLevel::USER: return "USER"; case LogLevel::DEBUG: return "DEBUG"; case LogLevel::DEV: return "DEV"; } @@ -72,7 +73,7 @@ namespace nexo { inline void defaultCallback(const LogLevel level, const std::string &message) { - if (level == LogLevel::FATAL || level == LogLevel::ERROR) + if (level == LogLevel::FATAL || level == LogLevel::ERR) std::cerr << "[" << toString(level) << "] " << message << std::endl; else std::cout << "[" << toString(level) << "] " << message << std::endl; @@ -259,7 +260,7 @@ namespace nexo { NEXO_LOG_ONCE_KEY(fmt, ##__VA_ARGS__), ##__VA_ARGS__) #define NEXO_FATAL nexo::LogLevel::FATAL -#define NEXO_ERROR nexo::LogLevel::ERROR +#define NEXO_ERROR nexo::LogLevel::ERR #define NEXO_WARN nexo::LogLevel::WARN #define NEXO_INFO nexo::LogLevel::INFO #define NEXO_DEBUG nexo::LogLevel::DEBUG diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp index d42db826c..a3a11264b 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow.cpp @@ -21,11 +21,13 @@ #include #include #include +#include +#include +#include #include "ConsoleWindow.hpp" #include "Editor.hpp" #include "Logger.hpp" #include "Path.hpp" -#include "tinyfiledialogs.h" #include "utils/FileSystem.hpp" #include @@ -71,7 +73,7 @@ namespace nexo::editor { switch (level) { case LogLevel::FATAL: return loguru::Verbosity_FATAL; - case LogLevel::ERROR: return loguru::Verbosity_ERROR; + 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; @@ -119,22 +121,17 @@ namespace nexo::editor { return color; } - static const std::string generateLogFilePath() + static std::string generateLogFilePath() { - auto now = std::chrono::system_clock::now(); - std::time_t now_time = std::chrono::system_clock::to_time_t(now); - - std::tm localTime; -#if defined(_MSC_VER) || defined(__MINGW32__) - localtime_s(&localTime, &now_time); -#else - localtime_r(&now_time, &localTime); -#endif - - std::ostringstream oss; - // Format: YYYY-MM-DD-HHMMSS, e.g., 2025-04-15-123045 - oss << "../logs/NEXO-" << std::put_time(&localTime, "%Y-%m-%d-%H%M%S") << ".log"; - return oss.str(); + auto now = std::time(nullptr); + auto tm = *std::localtime(&now); + + std::ostringstream ss; + ss << "../logs/NEXO-"; + ss << std::put_time(&tm, "%Y-%m-%d-%H%M%S"); + ss << ".log"; + + return ss.str(); } @@ -208,21 +205,22 @@ namespace nexo::editor { void ConsoleWindow::addLog(const char *fmt, Args &&... args) { try { - // Create a buffer for the formatted message char buffer[1024]; - snprintf(buffer, sizeof(buffer), fmt, std::forward(args)...); + int result = snprintf(buffer, sizeof(buffer), fmt, std::forward(args)...); + if (result < 0) + return; LogMessage newMessage; - newMessage.verbosity = nexoLevelToLoguruLevel(LogLevel::USER); + newMessage.verbosity = loguru::Verbosity_1; newMessage.message = std::string(buffer); newMessage.prefix = ""; m_logs.push_back(newMessage); } catch (const std::exception &e) { LogMessage newMessage; - newMessage.verbosity = nexoLevelToLoguruLevel(LogLevel::ERROR); + newMessage.verbosity = loguru::Verbosity_ERROR; char errorBuffer[1024]; - snprintf(errorBuffer, sizeof(errorBuffer), "[Error formatting log message]: %s", e.what()); + snprintf(errorBuffer, sizeof(errorBuffer), "Error formatting log message: %s", e.what()); newMessage.message = std::string(errorBuffer); newMessage.prefix = ""; From 8345384ba8062f76412fb53a5f0e15245ac71ca7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 16 Apr 2025 01:59:03 +0900 Subject: [PATCH 096/450] fix(documment-windows): fix scene renaming --- editor/src/DocumentWindows/EditorScene.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index a49d30cd9..f962d4559 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -16,7 +16,9 @@ #include +#include "ADocumentWindow.hpp" #include "EntityFactory3D.hpp" +#include "IconsFontAwesome.h" #include "LightFactory.hpp" #include "CameraFactory.hpp" #include "Nexo.hpp" @@ -196,10 +198,12 @@ 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 = m_windowName + "###" + std::string(NEXO_WND_USTRID_DEFAULT_SCENE); - if (ImGui::Begin(m_windowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse)) + if (ImGui::Begin(sceneWindowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse)) { - firstDockSetup(m_windowName); + firstDockSetup(NEXO_WND_USTRID_DEFAULT_SCENE); auto &app = getApp(); m_viewPosition = ImGui::GetCursorScreenPos(); From 3dc4f86f7a1b985a54930ad0e5f9e2a431db8afb Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 16 Apr 2025 02:26:52 +0900 Subject: [PATCH 097/450] fix(document-windows): fix repeated print when modifying material --- engine/src/systems/CameraSystem.cpp | 6 ++++-- engine/src/systems/RenderSystem.cpp | 7 +++++-- engine/src/systems/lights/AmbientLightSystem.cpp | 7 +++++-- engine/src/systems/lights/DirectionalLightsSystem.cpp | 7 +++++-- engine/src/systems/lights/PointLightsSystem.cpp | 7 +++++-- engine/src/systems/lights/SpotLightsSystem.cpp | 7 +++++-- 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index e8d4f51de..1ffc61e28 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -40,11 +40,13 @@ namespace nexo::system { 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, "No camera found in scene {}, skipping", sceneRendered); + 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", sceneRendered)); + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No camera found in scene {}, skipping", sceneName)); const auto cameraSpan = get(); const auto transformComponentArray = get(); diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 31c52513a..ddd284068 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -22,6 +22,7 @@ #include "renderer/RenderCommand.hpp" #include "ecs/Coordinator.hpp" #include "core/exceptions/Exceptions.hpp" +#include "Application.hpp" #include @@ -99,11 +100,13 @@ namespace nexo::system { 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", sceneRendered); + LOG_ONCE(NEXO_WARN, "Nothing to render in scene {}, skipping", sceneName); return; } - nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneRendered)); + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneName)); const auto transformSpan = get(); const auto renderSpan = get(); diff --git a/engine/src/systems/lights/AmbientLightSystem.cpp b/engine/src/systems/lights/AmbientLightSystem.cpp index ae7ca626b..3a24b4469 100644 --- a/engine/src/systems/lights/AmbientLightSystem.cpp +++ b/engine/src/systems/lights/AmbientLightSystem.cpp @@ -15,6 +15,7 @@ #include "AmbientLightSystem.hpp" #include "Logger.hpp" +#include "Application.hpp" #include "components/Light.hpp" #include "ecs/Coordinator.hpp" @@ -33,11 +34,13 @@ namespace nexo::system { 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, "No ambient light found in scene {}, skipping", sceneRendered); + LOG_ONCE(NEXO_WARN, "No ambient light found in scene {}, skipping", sceneName); return; } - nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No ambient light found in scene {}, skipping", sceneRendered)); + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No ambient light found in scene {}, skipping", sceneName)); const auto ambientSpan = get(); diff --git a/engine/src/systems/lights/DirectionalLightsSystem.cpp b/engine/src/systems/lights/DirectionalLightsSystem.cpp index 767dee43b..562215492 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.cpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.cpp @@ -18,6 +18,7 @@ #include "components/SceneComponents.hpp" #include "core/exceptions/Exceptions.hpp" #include "ecs/Coordinator.hpp" +#include "Application.hpp" namespace nexo::system { void DirectionalLightsSystem::update() @@ -34,11 +35,13 @@ namespace nexo::system { 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, "No directional light found in scene {}, skipping", sceneRendered); + 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", sceneRendered)); + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No directional light found in scene {}, skipping", sceneName)); if (partition->count > MAX_DIRECTIONAL_LIGHTS) THROW_EXCEPTION(core::TooManyDirectionalLightsException, sceneRendered, partition->count); diff --git a/engine/src/systems/lights/PointLightsSystem.cpp b/engine/src/systems/lights/PointLightsSystem.cpp index dcd669f60..89bf1c928 100644 --- a/engine/src/systems/lights/PointLightsSystem.cpp +++ b/engine/src/systems/lights/PointLightsSystem.cpp @@ -19,6 +19,7 @@ #include "components/SceneComponents.hpp" #include "core/exceptions/Exceptions.hpp" #include "ecs/Coordinator.hpp" +#include "Application.hpp" namespace nexo::system { void PointLightsSystem::update() @@ -35,11 +36,13 @@ namespace nexo::system { 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, "No point light found in scene {}, skipping", sceneRendered); + 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", sceneRendered)); + 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); diff --git a/engine/src/systems/lights/SpotLightsSystem.cpp b/engine/src/systems/lights/SpotLightsSystem.cpp index c0842bc30..866bd49ac 100644 --- a/engine/src/systems/lights/SpotLightsSystem.cpp +++ b/engine/src/systems/lights/SpotLightsSystem.cpp @@ -18,6 +18,7 @@ #include "components/SceneComponents.hpp" #include "core/exceptions/Exceptions.hpp" #include "ecs/Coordinator.hpp" +#include "Application.hpp" namespace nexo::system { void SpotLightsSystem::update() @@ -34,11 +35,13 @@ namespace nexo::system { 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, "No spot light found in scene {}, skipping", sceneRendered); + 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", sceneRendered)); + 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); From d87ae053d04739942ccaca609adf7bacc865c0c9 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 03:28:30 +0900 Subject: [PATCH 098/450] refactor(documents-windows): the ### are now directly in the USTRIDs macro --- editor/src/DocumentWindows/AssetManagerWindow.cpp | 2 +- editor/src/DocumentWindows/ConsoleWindow.cpp | 2 +- editor/src/DocumentWindows/InspectorWindow.cpp | 2 +- editor/src/DocumentWindows/MaterialInspector.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManagerWindow.cpp b/editor/src/DocumentWindows/AssetManagerWindow.cpp index 064b6df7c..571d64e1a 100644 --- a/editor/src/DocumentWindows/AssetManagerWindow.cpp +++ b/editor/src/DocumentWindows/AssetManagerWindow.cpp @@ -52,7 +52,7 @@ namespace nexo::editor { void AssetManagerWindow::show() { ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); - ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" "###" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, ImGuiWindowFlags_MenuBar); + ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, ImGuiWindowFlags_MenuBar); firstDockSetup(NEXO_WND_USTRID_ASSET_MANAGER); drawMenuBar(); diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp index a3a11264b..7f91052ec 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow.cpp @@ -324,7 +324,7 @@ namespace nexo::editor { void ConsoleWindow::show() { ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - ImGui::Begin(ICON_FA_FILE_TEXT " Console" "###" NEXO_WND_USTRID_CONSOLE, &m_opened, ImGuiWindowFlags_NoCollapse); + ImGui::Begin(ICON_FA_FILE_TEXT " Console" NEXO_WND_USTRID_CONSOLE, &m_opened, ImGuiWindowFlags_NoCollapse); firstDockSetup(NEXO_WND_USTRID_CONSOLE); const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); diff --git a/editor/src/DocumentWindows/InspectorWindow.cpp b/editor/src/DocumentWindows/InspectorWindow.cpp index 1c628683c..fa3831fc4 100644 --- a/editor/src/DocumentWindows/InspectorWindow.cpp +++ b/editor/src/DocumentWindows/InspectorWindow.cpp @@ -64,7 +64,7 @@ namespace nexo::editor void InspectorWindow::show() { - ImGui::Begin(ICON_FA_SLIDERS " Inspector" "###" NEXO_WND_USTRID_INSPECTOR, &m_opened, ImGuiWindowFlags_NoCollapse); + ImGui::Begin(ICON_FA_SLIDERS " Inspector" NEXO_WND_USTRID_INSPECTOR, &m_opened, ImGuiWindowFlags_NoCollapse); firstDockSetup(NEXO_WND_USTRID_INSPECTOR); auto const &selector = Selector::get(); const int selectedEntity = selector.getSelectedEntity(); diff --git a/editor/src/DocumentWindows/MaterialInspector.cpp b/editor/src/DocumentWindows/MaterialInspector.cpp index af9abc994..feb2c62aa 100644 --- a/editor/src/DocumentWindows/MaterialInspector.cpp +++ b/editor/src/DocumentWindows/MaterialInspector.cpp @@ -96,7 +96,7 @@ namespace nexo::editor { if (m_firstOpened) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; - if (ImGui::Begin("Material Inspector" "###" NEXO_WND_USTRID_MATERIAL_INSPECTOR, &inspectorWindow->getSubInspectorVisibility(), window_flags)) + if (ImGui::Begin("Material Inspector" NEXO_WND_USTRID_MATERIAL_INSPECTOR, &inspectorWindow->getSubInspectorVisibility(), window_flags)) { firstDockSetup(NEXO_WND_USTRID_MATERIAL_INSPECTOR); renderMaterialInspector(selectedEntity); From b2f5093c9574c12e57f48cabb6a0131db7423f54 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 03:29:52 +0900 Subject: [PATCH 099/450] fix(document-windows): scene name is now properly updated when renaming the scene via the scene tree + fix: use unique str ids based on the scene id --- editor/src/DocumentWindows/EditorScene.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index f962d4559..4ffe06600 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -166,10 +166,10 @@ namespace nexo::editor { { const auto viewPortOffset = ImGui::GetCursorPos(); auto &cameraComponent = Application::m_coordinator->getComponent(m_activeCamera); + const ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail(); // Resize handling - if (const ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail(); - m_viewSize.x != viewportPanelSize.x || m_viewSize.y != viewportPanelSize.y) + if ((viewportPanelSize.x > 0 && viewportPanelSize.y > 0) && (m_viewSize.x != viewportPanelSize.x || m_viewSize.y != viewportPanelSize.y)) { cameraComponent.resize(static_cast(viewportPanelSize.x), static_cast(viewportPanelSize.y)); @@ -198,12 +198,12 @@ 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 = m_windowName + "###" + std::string(NEXO_WND_USTRID_DEFAULT_SCENE); + m_windowName = selector.getUiHandle(m_sceneUuid, std::string(ICON_FA_GLOBE) + " " + m_windowName); + const std::string &sceneWindowName = m_windowName + std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId); if (ImGui::Begin(sceneWindowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse)) { - firstDockSetup(NEXO_WND_USTRID_DEFAULT_SCENE); + firstDockSetup(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId)); auto &app = getApp(); m_viewPosition = ImGui::GetCursorScreenPos(); @@ -243,9 +243,8 @@ namespace nexo::editor { return; if (m_focused && m_hovered) handleKeyEvents(); - - auto const &cameraComponent = Application::m_coordinator->getComponent(static_cast(m_activeCamera)); runEngine(m_sceneId, RenderingType::FRAMEBUFFER); + auto const &cameraComponent = Application::m_coordinator->getComponent(static_cast(m_activeCamera)); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsUsing() && m_focused) { auto [mx, my] = ImGui::GetMousePos(); From d035339a743af339d2a1613b23104f4bb6dc5fa0 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 03:31:31 +0900 Subject: [PATCH 100/450] feat(document-windows): add two utils function, findAllEditorScenes to retrieves every editor scenes in the config file + setAllWindowDocksIDsFromConfig that set the docks ids in the docking registry based on the one found in the config file at startup --- editor/src/utils/Config.cpp | 95 +++++++++++++++++++++++++++++++++++++ editor/src/utils/Config.hpp | 7 +++ 2 files changed, 102 insertions(+) diff --git a/editor/src/utils/Config.cpp b/editor/src/utils/Config.cpp index a04e5c854..59ec56ad4 100644 --- a/editor/src/utils/Config.cpp +++ b/editor/src/utils/Config.cpp @@ -65,4 +65,99 @@ namespace nexo::editor { configFile.close(); return dockId; } + + const std::vector findAllEditorScenes() + { + std::string configPath = Path::resolvePathRelativeToExe( + "../config/default-layout.ini").string(); + std::ifstream configFile(configPath); + + std::vector sceneWindows; + + if (!configFile.is_open()) { + std::cout << "Could not open config file: " << configPath << std::endl; + return sceneWindows; + } + + std::string line; + std::regex windowRegex("\\[Window\\]\\[(###Default Scene\\d+)\\]"); + + while (std::getline(configFile, line)) { + std::smatch match; + if (std::regex_search(line, match, windowRegex) && match.size() > 1) { + // match[1] contains the window name (e.g., "Default Scene0") + sceneWindows.push_back(match[1].str()); + } + } + + configFile.close(); + return sceneWindows; + } + + void setAllWindowDockIDsFromConfig(WindowRegistry& registry) + { + 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; + return; + } + + std::string line; + std::string currentWindowName; + bool inWindowSection = false; + bool isHashedWindow = false; + + std::regex windowHeaderRegex("\\[Window\\]\\[(.+)\\]"); + std::regex dockIdRegex("DockId=(0x[0-9a-fA-F]+)"); + + while (std::getline(configFile, line)) { + // Check if this line is a window header + std::smatch windowMatch; + if (std::regex_search(line, windowMatch, windowHeaderRegex) && windowMatch.size() > 1) { + currentWindowName = windowMatch[1].str(); + inWindowSection = true; + + // Check if the window name starts with ### + isHashedWindow = (currentWindowName.size() >= 3 && + currentWindowName.substr(0, 3) == "###"); + + continue; + } + + // If we're in a window section and it's a hashed window, look for DockId + if (inWindowSection && isHashedWindow) { + // If we hit a new section, reset state + if (!line.empty() && line[0] == '[') { + inWindowSection = 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; + 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; + registry.setDockId(currentWindowName, dockId); + } + } + } else if (inWindowSection && !isHashedWindow && !line.empty() && line[0] == '[') { + // Reset state when we hit a new section + inWindowSection = false; + isHashedWindow = false; + } + } + + configFile.close(); + } } diff --git a/editor/src/utils/Config.hpp b/editor/src/utils/Config.hpp index db827ce6d..306f7a79e 100644 --- a/editor/src/utils/Config.hpp +++ b/editor/src/utils/Config.hpp @@ -13,8 +13,11 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include "WindowRegistry.hpp" + #include #include +#include namespace nexo::editor { /** @@ -28,4 +31,8 @@ namespace nexo::editor { * @return ImGuiID The dock ID corresponding to the window. Returns 0 if not found. */ ImGuiID findWindowDockIDFromConfig(const std::string& windowName); + + const std::vector findAllEditorScenes(); + + void setAllWindowDockIDsFromConfig(WindowRegistry& registry); } From 79ff930653bdab6d4847a1dcbded2decef424c5a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 03:32:22 +0900 Subject: [PATCH 101/450] feat(documents-windows): add resetDockId method to remove a dock id from the registry --- editor/src/DockingRegistry.cpp | 9 +++++++++ editor/src/DockingRegistry.hpp | 2 ++ 2 files changed, 11 insertions(+) diff --git a/editor/src/DockingRegistry.cpp b/editor/src/DockingRegistry.cpp index eacbc17c2..481da5eee 100644 --- a/editor/src/DockingRegistry.cpp +++ b/editor/src/DockingRegistry.cpp @@ -14,6 +14,7 @@ #include "DockingRegistry.hpp" #include +#include namespace nexo::editor { @@ -30,4 +31,12 @@ namespace nexo::editor { } return std::nullopt; } + + void DockingRegistry::resetDockId(const std::string &name) + { + auto it = dockIds.find(name); + if (it == dockIds.end()) + return; + dockIds.erase(it); + } } diff --git a/editor/src/DockingRegistry.hpp b/editor/src/DockingRegistry.hpp index 395e6e289..9893c3864 100644 --- a/editor/src/DockingRegistry.hpp +++ b/editor/src/DockingRegistry.hpp @@ -47,6 +47,8 @@ namespace nexo::editor { */ std::optional getDockId(const std::string& name) const; + void resetDockId(const std::string &name); + private: std::unordered_map> dockIds; }; From dc89447ddc516f896b679f05c3836d1124b5656a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 03:33:26 +0900 Subject: [PATCH 102/450] refactor(document-windows): now set unique str id for the default scene + use the new utils function to set the dock ids from the config file --- editor/src/Editor.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index d9e2e30f2..6e4a6c4d0 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -247,8 +247,7 @@ namespace nexo::editor { static bool dockingRegistryFilled = false; // If the dockspace node doesn't exist yet, create it - if (!ImGui::DockBuilderGetNode(dockspaceID)) - { + if (!ImGui::DockBuilderGetNode(dockspaceID)) { ImGui::DockBuilderRemoveNode(dockspaceID); ImGui::DockSpaceOverViewport(viewport->ID); ImGui::DockBuilderAddNode(dockspaceID, ImGuiDockNodeFlags_None); @@ -286,14 +285,15 @@ namespace nexo::editor { // ───────────────────────────────────────────── // Dock the windows into their corresponding nodes. - ImGui::DockBuilderDockWindow(NEXO_WND_USTRID_DEFAULT_SCENE, mainSceneTop); + const std::string defaultSceneUniqueStrId = std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(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); ImGui::DockBuilderDockWindow(NEXO_WND_USTRID_INSPECTOR, inspectorNode); ImGui::DockBuilderDockWindow(NEXO_WND_USTRID_MATERIAL_INSPECTOR, materialInspectorNode); ImGui::DockBuilderDockWindow(NEXO_WND_USTRID_ASSET_MANAGER, consoleNode); - m_windowRegistry.setDockId(NEXO_WND_USTRID_DEFAULT_SCENE, mainSceneTop); + m_windowRegistry.setDockId(defaultSceneUniqueStrId.c_str(), mainSceneTop); m_windowRegistry.setDockId(NEXO_WND_USTRID_CONSOLE, consoleNode); m_windowRegistry.setDockId(NEXO_WND_USTRID_SCENE_TREE, sceneTreeNode); m_windowRegistry.setDockId(NEXO_WND_USTRID_INSPECTOR, inspectorNode); @@ -306,14 +306,8 @@ namespace nexo::editor { // Finish building the dock layout. ImGui::DockBuilderFinish(dockspaceID); } - else if (!dockingRegistryFilled) - { - m_windowRegistry.setDockId(NEXO_WND_USTRID_DEFAULT_SCENE, findWindowDockIDFromConfig(NEXO_WND_USTRID_DEFAULT_SCENE)); - m_windowRegistry.setDockId(NEXO_WND_USTRID_CONSOLE, findWindowDockIDFromConfig(NEXO_WND_USTRID_CONSOLE)); - m_windowRegistry.setDockId(NEXO_WND_USTRID_SCENE_TREE, findWindowDockIDFromConfig(NEXO_WND_USTRID_SCENE_TREE)); - m_windowRegistry.setDockId(NEXO_WND_USTRID_INSPECTOR, findWindowDockIDFromConfig(NEXO_WND_USTRID_INSPECTOR)); - m_windowRegistry.setDockId(NEXO_WND_USTRID_MATERIAL_INSPECTOR, findWindowDockIDFromConfig(NEXO_WND_USTRID_MATERIAL_INSPECTOR)); - m_windowRegistry.setDockId(NEXO_WND_USTRID_ASSET_MANAGER, findWindowDockIDFromConfig(NEXO_WND_USTRID_ASSET_MANAGER)); + else if (!dockingRegistryFilled) { + setAllWindowDockIDsFromConfig(m_windowRegistry); dockingRegistryFilled = true; } From fdc6bb2d86ed14a67bb31870e6590390bf9ad062 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 03:34:26 +0900 Subject: [PATCH 103/450] feat(document-windows): add resetDockId + unregisterWindow method --- editor/src/WindowRegistry.cpp | 5 +++++ editor/src/WindowRegistry.hpp | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/editor/src/WindowRegistry.cpp b/editor/src/WindowRegistry.cpp index 7de7dd8be..348bc1b8f 100644 --- a/editor/src/WindowRegistry.cpp +++ b/editor/src/WindowRegistry.cpp @@ -48,6 +48,11 @@ namespace nexo::editor { return m_dockingRegistry.getDockId(name); } + void WindowRegistry::resetDockId(const std::string &name) + { + m_dockingRegistry.resetDockId(name); + } + void WindowRegistry::update() const { for (const auto &[_, windows]: m_windows) diff --git a/editor/src/WindowRegistry.hpp b/editor/src/WindowRegistry.hpp index 5bf2de2e8..9435ff9e9 100644 --- a/editor/src/WindowRegistry.hpp +++ b/editor/src/WindowRegistry.hpp @@ -60,6 +60,29 @@ namespace nexo::editor { windowsOfType.push_back(window); } + template + requires std::derived_from + void unregisterWindow(const std::string &windowName) + { + 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; + }); + + if (found == windowsOfType.end()) { + LOG(NEXO_WARN, "Window of type {} with name {} not found", typeid(T).name(), windowName); + return; + } + + windowsOfType.erase(found); + } + /** * @brief Retrieves a registered window of the specified type and name. * @@ -148,6 +171,8 @@ namespace nexo::editor { */ std::optional getDockId(const std::string& name) const; + void resetDockId(const std::string &name); + /** * @brief Initializes all managed windows. * From f7c4c4655959804aeff95cfe3b2603d0a4d480c6 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 03:34:53 +0900 Subject: [PATCH 104/450] refactor(document-windows): use unique str id for the default scene --- editor/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/main.cpp b/editor/main.cpp index f4dfe00c0..18fe1dc4d 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -30,14 +30,14 @@ int main(int argc, char **argv) loguru::g_stderr_verbosity = loguru::Verbosity_3; nexo::editor::Editor &editor = nexo::editor::Editor::getInstance(); - editor.registerWindow(NEXO_WND_USTRID_DEFAULT_SCENE); + editor.registerWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)); editor.registerWindow(NEXO_WND_USTRID_SCENE_TREE); editor.registerWindow(NEXO_WND_USTRID_INSPECTOR); editor.registerWindow(NEXO_WND_USTRID_CONSOLE); editor.registerWindow(NEXO_WND_USTRID_MATERIAL_INSPECTOR); editor.registerWindow(NEXO_WND_USTRID_ASSET_MANAGER); - if (auto defaultScene = editor.getWindow(NEXO_WND_USTRID_DEFAULT_SCENE).lock()) + if (auto defaultScene = editor.getWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)).lock()) defaultScene->setDefault(); editor.init(); From 8a842eac37e9012f9a3df4e04ac5f1f0e828511c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 03:35:36 +0900 Subject: [PATCH 105/450] refactor(document-windows): firstDockSetup method handles when a winow is undocked and redocked --- editor/src/ADocumentWindow.hpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index 648f37d12..3817e2f0a 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -22,12 +22,12 @@ 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_DEFAULT_SCENE "Default Scene" + #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_DEFAULT_SCENE "###Default Scene" class ADocumentWindow : public IDocumentWindow { public: @@ -79,10 +79,20 @@ namespace nexo::editor { const ImGuiID currentDockID = currentWindow->DockId; auto dockId = m_windowRegistry.getDockId(windowName); - if (m_firstOpened && (!isDocked || (dockId && currentDockID != *dockId))) - currentWindow->DockId = *dockId; - else if (dockId && currentDockID != *dockId) + // 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 + // 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); m_firstOpened = false; } } From 64cd503f377380798f6fa1b56790adfa8ebe07a4 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 03:36:21 +0900 Subject: [PATCH 106/450] feat(document-windows): add delete scene and create scene feature --- .../src/DocumentWindows/SceneTreeWindow.cpp | 120 ++++++++++++++---- .../src/DocumentWindows/SceneTreeWindow.hpp | 3 + 2 files changed, 97 insertions(+), 26 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index 71e3c57c6..35691d027 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -13,6 +13,8 @@ /////////////////////////////////////////////////////////////////////////////// #include "SceneTreeWindow.hpp" +#include "ADocumentWindow.hpp" +#include "utils/Config.hpp" #include "DocumentWindows/InspectorWindow.hpp" #include "Primitive.hpp" @@ -90,7 +92,14 @@ namespace nexo::editor { void SceneTreeWindow::sceneSelected([[maybe_unused]] const SceneObject &obj) const { - //TODO: Delete scene + if (ImGui::MenuItem("Delete Scene")) { + auto &app = Application::getInstance(); + auto &selector = Selector::get(); + selector.unselectEntity(); + const std::string &sceneName = selector.getUiHandle(obj.uuid, obj.uiName); + m_windowRegistry.unregisterWindow(sceneName); + app.getSceneManager().deleteScene(obj.data.sceneProperties.sceneId); + } } void SceneTreeWindow::lightSelected(const SceneObject &obj) const @@ -192,37 +201,96 @@ namespace nexo::editor { } } - void SceneTreeWindow::sceneCreationMenu() + bool SceneTreeWindow::setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName) { - if (m_popupManager.showPopupModal("Create New Scene")) - { - static char sceneNameBuffer[256] = ""; + ImGuiWindow* floatingWindow = ImGui::FindWindowByName(floatingWindowName.c_str()); + if (!floatingWindow) + return false; + + // Create a new docking node + auto newDockId = ImGui::GetID("##DockNode"); + + // Configure the docking node + ImGui::DockBuilderRemoveNode(newDockId); + ImGui::DockBuilderAddNode(newDockId, ImGuiDockNodeFlags_None); + + // Set node size and position based on the floating window + ImVec2 windowPos = floatingWindow->Pos; + ImVec2 windowSize = floatingWindow->Size; + ImGui::DockBuilderSetNodeSize(newDockId, windowSize); + ImGui::DockBuilderSetNodePos(newDockId, windowPos); + + // Dock the windows to this node + ImGui::DockBuilderDockWindow(floatingWindowName.c_str(), newDockId); + ImGui::DockBuilderFinish(newDockId); + + // Update the registry with the new dock IDs + m_windowRegistry.setDockId(floatingWindowName, newDockId); + m_windowRegistry.setDockId(newSceneName, newDockId); + return true; + } - ImGui::Text("Enter Scene Name:"); - ImGui::InputText("##SceneName", sceneNameBuffer, sizeof(sceneNameBuffer)); + bool SceneTreeWindow::handleSceneCreation(const std::string &newSceneName) + { + if (newSceneName.empty()) { + LOG(NEXO_WARN, "Scene name is empty !"); + return false; + } - if (ImGui::Button("Create")) - { - if (const std::string newSceneName(sceneNameBuffer); !newSceneName.empty()) - { - //TOOD: create scene - auto newScene = std::make_shared(sceneNameBuffer, m_windowRegistry); - newScene->setDefault(); - newScene->setup(); - m_windowRegistry.registerWindow(newScene); - memset(sceneNameBuffer, 0, sizeof(sceneNameBuffer)); - - m_popupManager.closePopupInContext(); - } else - LOG(NEXO_WARN, "Scene name is empty !"); + auto newScene = std::make_shared(newSceneName, m_windowRegistry); + newScene->setDefault(); + newScene->setup(); + m_windowRegistry.registerWindow(newScene); + + auto currentEditorSceneWindow = m_windowRegistry.getWindows(); + // If no editor scene is open, check the config file + if (currentEditorSceneWindow.empty()) { + const std::vector &editorSceneInConfig = findAllEditorScenes(); + if (!editorSceneInConfig.empty()) { + auto dockId = m_windowRegistry.getDockId(editorSceneInConfig[0]); + if (!dockId) + return false; + m_windowRegistry.setDockId(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId()), *dockId); + return true; } + // If nothing is present in config file, simply let it float + return false; + } - ImGui::SameLine(); - if (ImGui::Button("Cancel")) - m_popupManager.closePopupInContext(); + // Else we retrieve the first active editor scene + const std::string windowName = std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(currentEditorSceneWindow[0]->getSceneId()); + 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 (!dockId) { + setupNewDockSpaceNode(windowName, std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId())); + return true; + } + m_windowRegistry.setDockId(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId()), *dockId); + return true; + } - m_popupManager.closePopup(); + void SceneTreeWindow::sceneCreationMenu() + { + if (!m_popupManager.showPopupModal("Create New Scene")) + return; + + static char sceneNameBuffer[256] = ""; + + ImGui::Text("Enter Scene Name:"); + ImGui::InputText("##SceneName", sceneNameBuffer, sizeof(sceneNameBuffer)); + + if (ImGui::Button("Create")) { + if (handleSceneCreation(sceneNameBuffer)) { + memset(sceneNameBuffer, 0, sizeof(sceneNameBuffer)); + m_popupManager.closePopupInContext(); + } } + + ImGui::SameLine(); + if (ImGui::Button("Cancel")) + m_popupManager.closePopupInContext(); + + m_popupManager.closePopup(); } void SceneTreeWindow::show() @@ -230,7 +298,7 @@ namespace nexo::editor { ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - 300, 20), ImGuiCond_FirstUseEver); 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)) + if (ImGui::Begin(ICON_FA_SITEMAP " Scene Tree" NEXO_WND_USTRID_SCENE_TREE, &m_opened, ImGuiWindowFlags_NoCollapse)) { firstDockSetup(NEXO_WND_USTRID_SCENE_TREE); // Opens the right click popup when no items are hovered diff --git a/editor/src/DocumentWindows/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow.hpp index c8c05d22f..129ecbf9b 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.hpp @@ -14,6 +14,7 @@ #pragma once #include "ADocumentWindow.hpp" +#include "EditorScene.hpp" #include "IconsFontAwesome.h" #include "Nexo.hpp" #include "core/scene/SceneManager.hpp" @@ -290,5 +291,7 @@ namespace nexo::editor { * The popup is closed either upon successful scene creation or when the "Cancel" button is clicked. */ void sceneCreationMenu(); + bool handleSceneCreation(const std::string &newSceneName); + bool setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName); }; } From aac35a81712db7905f21691ba46cf57680e6fa33 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 04:06:05 +0900 Subject: [PATCH 107/450] fix(document-windows): properly deletes an entity --- engine/src/systems/RenderSystem.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index ddd284068..23547de88 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -102,11 +102,6 @@ namespace nexo::system { 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; - } - nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneName)); const auto transformSpan = get(); const auto renderSpan = get(); @@ -124,6 +119,14 @@ namespace nexo::system { camera.renderTarget->clearAttachment(1, -1); } + if (!partition) { + LOG_ONCE(NEXO_WARN, "Nothing to render in scene {}, skipping", sceneName); + camera.renderTarget->unbind(); + renderContext.cameras.pop(); + continue; + } + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneName)); + for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) { const auto &transform = transformSpan[i]; From 5871b28362c8bbd4d0617c4d5b9b5efa3345d491 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 05:55:41 +0900 Subject: [PATCH 108/450] feat(document-windows): add popup bg in global style --- editor/src/Editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 6e4a6c4d0..2073923f7 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -126,6 +126,7 @@ namespace nexo::editor { // Depending definitions colors[ImGuiCol_WindowBg] = colWindowBg; + colors[ImGuiCol_PopupBg] = colWindowBg; colors[ImGuiCol_TitleBg] = colTitleBg; colors[ImGuiCol_TitleBgActive] = colTitleBgActive; colors[ImGuiCol_TitleBgCollapsed] = colTitleBg; From d2ad75a58b101a25982da91b36edaba1bb8db424 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 05:56:04 +0900 Subject: [PATCH 109/450] refactor(document-windows): popups now also have a gradient --- editor/src/DocumentWindows/PopupManager.cpp | 28 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/editor/src/DocumentWindows/PopupManager.cpp b/editor/src/DocumentWindows/PopupManager.cpp index 406fba922..0c0df18b9 100644 --- a/editor/src/DocumentWindows/PopupManager.cpp +++ b/editor/src/DocumentWindows/PopupManager.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "PopupManager.hpp" +#include "Components/Components.hpp" #include "Logger.hpp" #include @@ -51,11 +52,32 @@ namespace nexo::editor { return false; if (m_popups.at(popupModalName)) { - LOG(NEXO_INFO, "Opened {} popup", popupModalName); - ImGui::OpenPopup(popupModalName.c_str()); m_popups.at(popupModalName) = false; } - return ImGui::BeginPopupModal(popupModalName.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize); + + ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize + | ImGuiWindowFlags_NoBackground + | ImGuiWindowFlags_NoTitleBar; + if (!ImGui::BeginPopupModal(popupModalName.c_str(), nullptr, flags)) + return false; + + ImVec2 pMin = ImGui::GetWindowPos(); + ImVec2 size = ImGui::GetWindowSize(); + ImVec2 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) }, + }; + float angle = 148.0f; + + Components::drawRectFilledLinearGradient(pMin, pMax, angle, stops, drawList); + + return true; } + } From 8617877ea26c2c9cec74afba68d3a6bf01d875ac Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 06:10:12 +0900 Subject: [PATCH 110/450] refactor(document-windows): modify popupbg to better match color scheme --- editor/src/Editor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 2073923f7..861ffc85d 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -126,7 +126,6 @@ namespace nexo::editor { // Depending definitions colors[ImGuiCol_WindowBg] = colWindowBg; - colors[ImGuiCol_PopupBg] = colWindowBg; colors[ImGuiCol_TitleBg] = colTitleBg; colors[ImGuiCol_TitleBgActive] = colTitleBgActive; colors[ImGuiCol_TitleBgCollapsed] = colTitleBg; @@ -159,6 +158,7 @@ namespace nexo::editor { 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.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f); // Optionally, you might want to adjust the text color if needed: setupFonts(scaleFactorX, scaleFactorY); From e9ebd09b3d31a9f01609bddea58bf0a5698f2566 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 11:17:18 +0900 Subject: [PATCH 111/450] fi(document-windows): camera does not update anymore when not active --- engine/src/systems/CameraSystem.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index 1ffc61e28..6e1e1edee 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -86,6 +86,8 @@ namespace nexo::system { if (!sceneTag.isActive || sceneTag.id != sceneRendered) continue; auto &cameraComponent = getComponent(entity); + if (!cameraComponent.active) + continue; auto &transform = getComponent(entity); cameraComponent.resizing = false; @@ -121,7 +123,8 @@ namespace nexo::system { { constexpr float zoomSpeed = 0.5f; auto &sceneTag = getComponent(entity); - if (!sceneTag.isActive || sceneTag.id != sceneRendered) + 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); @@ -148,7 +151,7 @@ namespace nexo::system { if (!sceneTag.isActive || sceneTag.id != sceneRendered) continue; const auto &cameraComponent = getComponent(entity); - if (cameraComponent.resizing || !event::isMouseDown(NEXO_MOUSE_LEFT)) + if (cameraComponent.resizing || !event::isMouseDown(NEXO_MOUSE_LEFT) || !cameraComponent.active) continue; controller.yaw += -mouseDelta.x; @@ -188,7 +191,8 @@ namespace nexo::system { { constexpr float zoomSpeed = 0.5f; auto &tag = getComponent(entity); - if (!tag.isActive || sceneRendered != tag.id) + auto &cameraComponent = getComponent(entity); + if (!tag.isActive || sceneRendered != tag.id || !cameraComponent.active) continue; auto &target = getComponent(entity); target.distance -= event.y * zoomSpeed; @@ -229,7 +233,7 @@ namespace nexo::system { 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)) + if (!sceneTag.isActive || sceneTag.id != sceneRendered || cameraComponent.resizing || !event::isMouseDown(NEXO_MOUSE_RIGHT) || !cameraComponent.active) { targetComponent.lastMousePosition = currentMousePosition; continue; From 04d63edff4bd6bffebea5c30646df6c83f4e1171 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 11:18:15 +0900 Subject: [PATCH 112/450] feat(document-windows): add entity creation menu, primitives, models, lights and cameras --- .../src/DocumentWindows/SceneTreeWindow.cpp | 64 ++++++++++++++++++- .../src/DocumentWindows/SceneTreeWindow.hpp | 2 +- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index 35691d027..965f827d7 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -29,6 +29,8 @@ #include "components/Transform.hpp" #include "components/Uuid.hpp" #include "context/Selector.hpp" +#include "LightFactory.hpp" +#include "Components/Widgets.hpp" namespace nexo::editor { @@ -90,7 +92,7 @@ namespace nexo::editor { return nodeOpen; } - void SceneTreeWindow::sceneSelected([[maybe_unused]] const SceneObject &obj) const + void SceneTreeWindow::sceneSelected([[maybe_unused]] const SceneObject &obj) { if (ImGui::MenuItem("Delete Scene")) { auto &app = Application::getInstance(); @@ -100,6 +102,60 @@ namespace nexo::editor { m_windowRegistry.unregisterWindow(sceneName); app.getSceneManager().deleteScene(obj.data.sceneProperties.sceneId); } + + // ---- Add Entity submenu ---- + if (ImGui::BeginMenu("Add Entity")) { + auto &app = Application::getInstance(); + auto &sceneManager = app.getSceneManager(); + const auto sceneId = obj.data.sceneProperties.sceneId; + + // --- Primitives submenu --- + if (ImGui::BeginMenu("Primitives")) { + if (ImGui::MenuItem("Cube")) { + const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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); + } + ImGui::EndMenu(); + } + + // --- Model item (with file‑dialog) --- + if (ImGui::MenuItem("Model")) { + //TODO: import model + } + + // --- Lights submenu --- + 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); + } + if (ImGui::MenuItem("Point")) { + const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); + sceneManager.getScene(sceneId).addEntity(pointLight); + } + if (ImGui::MenuItem("Spot")) { + const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); + sceneManager.getScene(sceneId).addEntity(spotLight); + } + ImGui::EndMenu(); + } + + // --- Camera item --- + if (ImGui::MenuItem("Camera")) { + m_popupManager.openPopupWithCallback("Popup camera inspector", [this, obj]() { + const auto &editorScenes = m_windowRegistry.getWindows(); + for (const auto &scene : editorScenes) { + if (scene->getSceneId() == obj.data.sceneProperties.sceneId) { + Widgets::drawCameraCreator(obj.data.sceneProperties.sceneId, scene->getViewportSize()); + break; + } + } + }); + } + + ImGui::EndMenu(); // <-- matches the BeginMenu("Add Entity") + } } void SceneTreeWindow::lightSelected(const SceneObject &obj) const @@ -199,6 +255,12 @@ namespace nexo::editor { m_popupManager.openPopup("Create New Scene"); m_popupManager.closePopup(); } + + ImGui::SetNextWindowSize(ImVec2(1440,900)); + if (m_popupManager.showPopupModal("Popup camera inspector")) { + m_popupManager.runPopupCallback("Popup camera inspector"); + m_popupManager.closePopup(); + } } bool SceneTreeWindow::setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName) diff --git a/editor/src/DocumentWindows/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow.hpp index 129ecbf9b..513722c0f 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.hpp @@ -256,7 +256,7 @@ namespace nexo::editor { * * @param obj The scene object representing the scene to be deleted. */ - void sceneSelected(const SceneObject &obj) const; + void sceneSelected(const SceneObject &obj); /** * @brief Displays a context menu option to delete a light node. From d5510617a34933bca6464961468e7028682fe021 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 11:18:45 +0900 Subject: [PATCH 113/450] feat(document-windows): add open popup with callback --- editor/src/DocumentWindows/PopupManager.cpp | 15 ++++++++++++++- editor/src/DocumentWindows/PopupManager.hpp | 8 ++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/PopupManager.cpp b/editor/src/DocumentWindows/PopupManager.cpp index 0c0df18b9..b47fb619d 100644 --- a/editor/src/DocumentWindows/PopupManager.cpp +++ b/editor/src/DocumentWindows/PopupManager.cpp @@ -24,6 +24,12 @@ namespace nexo::editor { m_popups[popupName] = true; } + void PopupManager::openPopupWithCallback(const std::string &popupName, PopupCallback callback) + { + m_popups[popupName] = true; + m_callbacks[popupName] = callback; + } + void PopupManager::closePopup() const { ImGui::EndPopup(); @@ -61,7 +67,6 @@ namespace nexo::editor { | ImGuiWindowFlags_NoTitleBar; if (!ImGui::BeginPopupModal(popupModalName.c_str(), nullptr, flags)) return false; - ImVec2 pMin = ImGui::GetWindowPos(); ImVec2 size = ImGui::GetWindowSize(); ImVec2 pMax = ImVec2(pMin.x + size.x, pMin.y + size.y); @@ -80,4 +85,12 @@ namespace nexo::editor { return true; } + void PopupManager::runPopupCallback(const std::string &popupName) + { + if (m_callbacks.contains(popupName)) + { + m_callbacks[popupName](); + } + } + } diff --git a/editor/src/DocumentWindows/PopupManager.hpp b/editor/src/DocumentWindows/PopupManager.hpp index c85404517..61a3a0316 100644 --- a/editor/src/DocumentWindows/PopupManager.hpp +++ b/editor/src/DocumentWindows/PopupManager.hpp @@ -15,6 +15,7 @@ #include #include +#include namespace nexo::editor { @@ -26,6 +27,8 @@ namespace nexo::editor { */ class PopupManager { public: + using PopupCallback = std::function; + /** * @brief Opens a popup by name. @@ -36,6 +39,8 @@ namespace nexo::editor { */ void openPopup(const std::string &popupName); + void openPopupWithCallback(const std::string &popupName, PopupCallback callback); + /** * @brief Displays a modal popup. * @@ -72,6 +77,8 @@ namespace nexo::editor { */ void closePopupInContext() const; + void runPopupCallback(const std::string &popupName); + private: struct TransparentHasher { using is_transparent = void; // Required for heterogeneous lookup @@ -81,5 +88,6 @@ namespace nexo::editor { }; std::unordered_map> m_popups; + std::unordered_map m_callbacks; // Store callbacks }; } From 2206bff11e34dadbf325ca317d49f396897a4034 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 11:19:05 +0900 Subject: [PATCH 114/450] feat(document-windows): add viewport size getter --- editor/src/DocumentWindows/EditorScene.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp index 5833dd668..7e0d2f0cc 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene.hpp @@ -64,6 +64,7 @@ namespace nexo::editor { * @return scene::SceneId The identifier of this scene. */ [[nodiscard]] scene::SceneId getSceneId() const {return m_sceneId;}; + [[nodiscard]] ImVec2 getViewportSize() const {return m_viewSize;}; /** From 5abed2cddb52c4221fbde24a49c7c90e4a41ab8e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 11:19:41 +0900 Subject: [PATCH 115/450] fix(document-windows): improve header's text centering --- .../Components/EntityPropertiesComponents.cpp | 71 ++++++++++++------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/editor/src/Components/EntityPropertiesComponents.cpp b/editor/src/Components/EntityPropertiesComponents.cpp index d822fa488..0275b78a7 100644 --- a/editor/src/Components/EntityPropertiesComponents.cpp +++ b/editor/src/Components/EntityPropertiesComponents.cpp @@ -17,31 +17,50 @@ namespace nexo::editor { - bool EntityPropertiesComponents::drawHeader(const std::string &label, std::string_view headerText) - { - float increasedPadding = 2.0f; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, - ImVec2(ImGui::GetStyle().FramePadding.x, increasedPadding)); - - bool open = ImGui::TreeNodeEx(label.c_str(), - ImGuiTreeNodeFlags_DefaultOpen | - ImGuiTreeNodeFlags_Framed | - ImGuiTreeNodeFlags_AllowItemOverlap); - ImGui::PopStyleVar(); - - // Horizontal centering: - const float arrowPosX = ImGui::GetCursorPosX(); - ImGui::SameLine(0.0f, 0.0f); - const float totalWidth = ImGui::GetContentRegionAvail().x + arrowPosX; - const ImVec2 textSize = ImGui::CalcTextSize(headerText.data()); - const float textPosX = (totalWidth - textSize.x) * 0.5f; - ImGui::SetCursorPosX(textPosX); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 2.5f); // This stuff seems strange, should check in the long run if there is a better way - - ImGui::TextUnformatted(headerText.data()); - - return open; - } + bool EntityPropertiesComponents::drawHeader(const std::string &label, std::string_view headerText) + { + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::PushStyleVar( + ImGuiStyleVar_FramePadding, + ImVec2(style.FramePadding.x, 3.0f) + ); + + bool open = ImGui::TreeNodeEx( + label.c_str(), + ImGuiTreeNodeFlags_DefaultOpen | + ImGuiTreeNodeFlags_Framed | + ImGuiTreeNodeFlags_AllowItemOverlap | + ImGuiTreeNodeFlags_SpanAvailWidth + ); + + ImGui::PopStyleVar(); + + // We retrieve the bounding box of the tree node + ImVec2 bbMin = ImGui::GetItemRectMin(); + ImVec2 bbMax = ImGui::GetItemRectMax(); + ImVec2 bbSize = ImVec2(bbMax.x - bbMin.x, bbMax.y - bbMin.y); + + ImVec2 textSize = ImGui::CalcTextSize(headerText.data()); + + // We manually compute the absolute screen position + ImVec2 textPos; + textPos.x = bbMin.x + (bbSize.x - textSize.x) * 0.5f; + textPos.y = bbMin.y + (bbSize.y - textSize.y) * 0.5f; + + // Draw directly on top of it + ImDrawList* dl = ImGui::GetWindowDrawList(); + dl->AddText( + ImGui::GetFont(), + ImGui::GetFontSize(), + textPos, + ImGui::GetColorU32(ImGuiCol_Text), + headerText.data() + ); + + return open; + } + + void EntityPropertiesComponents::drawRowLabel(const ChannelLabel &rowLabel) { @@ -57,7 +76,7 @@ namespace nexo::editor { //ImVec2 cellPos = ImGui::GetCursorPos(); } //ImGui::SetWindowFontScale(1.11f); - + ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(rowLabel.label.c_str()); //ImGui::SetWindowFontScale(1.0f); } From e0b48a5e17e69688244fece73a76787d2eea03b1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 11:20:22 +0900 Subject: [PATCH 116/450] feat(document-windows): add generic function for drawing camera and transform properties --- editor/src/Components/Widgets.cpp | 219 ++++++++++++++++++++++++++++++ editor/src/Components/Widgets.hpp | 8 ++ 2 files changed, 227 insertions(+) diff --git a/editor/src/Components/Widgets.cpp b/editor/src/Components/Widgets.cpp index 62fdc5385..bee5bff28 100644 --- a/editor/src/Components/Widgets.cpp +++ b/editor/src/Components/Widgets.cpp @@ -14,11 +14,18 @@ #include "Widgets.hpp" +#include #include #include #include "Components.hpp" +#include "Definitions.hpp" #include "IconsFontAwesome.h" +#include "Nexo.hpp" +#include "components/Camera.hpp" +#include "EntityPropertiesComponents.hpp" +#include "CameraFactory.hpp" +#include "components/Transform.hpp" #include "tinyfiledialogs.h" namespace nexo::editor { @@ -166,4 +173,216 @@ namespace nexo::editor { modified = Widgets::drawColorEditor("##ColorEditor Specular texture", &material->specularColor, &colorPickerModeSpecular, &showColorPickerSpecular) || modified; return modified; } + + static ecs::Entity createDefaultPerspectiveCamera(const scene::SceneId sceneId, ImVec2 sceneViewportSize) + { + auto &app = getApp(); + renderer::FramebufferSpecs framebufferSpecs; + framebufferSpecs.attachments = { + renderer::FrameBufferTextureFormats::RGBA8, renderer::FrameBufferTextureFormats::RED_INTEGER, renderer::FrameBufferTextureFormats::Depth + }; + const ImVec2 availSize = ImGui::GetContentRegionAvail(); + const float totalWidth = availSize.x; + 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 panel + framebufferSpecs.width = static_cast(sceneViewportSize.x); + framebufferSpecs.height = static_cast(sceneViewportSize.y); + const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs); + ecs::Entity defaultCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, 0.0f}, static_cast(sceneViewportSize.x), static_cast(sceneViewportSize.y), renderTarget); + app.getSceneManager().getScene(sceneId).addEntity(static_cast(defaultCamera)); + return defaultCamera; + } + + void Widgets::drawTransformProperties(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, size, quat] = transformComponent; + + 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); + + EntityPropertiesComponents::drawRowDragFloat3("Position", "X", "Y", "Z", &pos.x); + + const glm::vec3 computedEuler = math::customQuatToEuler(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 (EntityPropertiesComponents::drawRowDragFloat3("Rotation", "X", "Y", "Z", &rotation.x)) { + const glm::vec3 deltaEuler = rotation - lastDisplayedEuler; + const glm::quat deltaQuat = glm::radians(deltaEuler); + quat = glm::normalize(deltaQuat * quat); + lastDisplayedEuler = math::customQuatToEuler(quat); + rotation = lastDisplayedEuler; + } + EntityPropertiesComponents::drawRowDragFloat3("Scale", "X", "Y", "Z", &size.x); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + + void Widgets::drawCameraProperties(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); + glm::vec2 viewPort = {cameraComponent.width, cameraComponent.height}; + std::vector badgeColors; + std::vector textBadgeColors; + + const bool disabled = cameraComponent.viewportLocked; + if (disabled) + ImGui::BeginDisabled(); + if (EntityPropertiesComponents::drawRowDragFloat2("Viewport size", "W", "H", &viewPort.x, -FLT_MAX, FLT_MAX, 1.0f, badgeColors, textBadgeColors, disabled)) + { + if (!cameraComponent.viewportLocked) + cameraComponent.resize(static_cast(viewPort.x), static_cast(viewPort.y)); + } + if (disabled) + ImGui::EndDisabled(); + + ImGui::TableSetColumnIndex(3); + + // Lock button + const std::string lockBtnLabel = cameraComponent.viewportLocked ? ICON_FA_LOCK "##ViewPortSettings" : ICON_FA_UNLOCK "##ViewPortSettings"; + if (Components::drawButton(lockBtnLabel)) { + cameraComponent.viewportLocked = !cameraComponent.viewportLocked; + } + + + ImGui::EndTable(); + } + + if (ImGui::BeginTable("InspectorCameraVariables", 2, ImGuiTableFlags_SizingStretchProp)) + { + ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + EntityPropertiesComponents::drawRowDragFloat1("FOV", "", &cameraComponent.fov, 30.0f, 120.0f, 0.3f); + EntityPropertiesComponents::drawRowDragFloat1("Near plane", "", &cameraComponent.nearPlane, 0.01f, 1.0f, 0.001f); + EntityPropertiesComponents::drawRowDragFloat1("Far plane", "", &cameraComponent.farPlane, 100.0f, 10000.0f, 1.0f); + + ImGui::EndTable(); + } + + + ImGui::PopStyleVar(); + + ImGui::Spacing(); + static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPicker = false; + ImGui::AlignTextToFramePadding(); + ImGui::Text("Clear Color"); + ImGui::SameLine(); + Widgets::drawColorEditor("##ColorEditor Spot light", &cameraComponent.clearColor, &colorPickerMode, &showColorPicker); + } + + bool Widgets::drawCameraCreator(const scene::SceneId sceneId, ImVec2 sceneViewportSize) + { + auto &app = getApp(); + + const ImVec2 availSize = ImGui::GetContentRegionAvail(); + const float totalWidth = availSize.x; + 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 + static ecs::Entity camera = ecs::MAX_ENTITIES; + if (camera == ecs::MAX_ENTITIES) + { + camera = createDefaultPerspectiveCamera(sceneId, ImVec2(previewWidth, totalHeight)); + } + ImGui::Columns(2, "CameraCreatorColumns", false); + + ImGui::SetColumnWidth(0, inspectorWidth); + // --- Left Side: Camera Inspector --- + { + ImGui::BeginChild("CameraInspector", ImVec2(inspectorWidth - 4, totalHeight), true); + static char cameraName[128] = ""; + ImGui::AlignTextToFramePadding(); + ImGui::Text("Name"); + ImGui::SameLine(); + ImGui::InputText("##CameraName", cameraName, IM_ARRAYSIZE(cameraName)); + ImGui::Spacing(); + + if (EntityPropertiesComponents::drawHeader("##CameraNode", "Camera")) + { + auto &cameraComponent = app.m_coordinator->getComponent(camera); + Widgets::drawCameraProperties(cameraComponent); + ImGui::TreePop(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + + if (EntityPropertiesComponents::drawHeader("##TransformNode", "Transform Component")) + { + static glm::vec3 lastDisplayedEuler(0.0f); + auto &transformComponent = app.m_coordinator->getComponent(camera); + Widgets::drawTransformProperties(transformComponent, lastDisplayedEuler); + ImGui::TreePop(); + } + + ImGui::EndChild(); + } + ImGui::NextColumn(); + // --- Right Side: Camera Preview --- + { + ImGui::BeginChild("CameraPreview", ImVec2(previewWidth - 4, totalHeight), true); + + auto &app = getApp(); + app.run(sceneId, RenderingType::FRAMEBUFFER); + auto const &cameraComponent = Application::m_coordinator->getComponent(camera); + const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); + + const float displayHeight = totalHeight - 20; + const float displayWidth = displayHeight; + + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 4, ImGui::GetCursorPosY() + 4)); + ImGui::Image(static_cast(static_cast(textureId)), + ImVec2(displayWidth, displayHeight), ImVec2(0, 1), ImVec2(1, 0)); + + ImGui::EndChild(); + } + + ImGui::Columns(1); + ImGui::Spacing(); + + // Bottom buttons - centered + constexpr float buttonWidth = 120.0f; + + if (ImGui::Button("OK", ImVec2(buttonWidth, 0))) + { + ImGui::CloseCurrentPopup(); + return true; + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) + { + ImGui::CloseCurrentPopup(); + return true; + } + return false; + } } diff --git a/editor/src/Components/Widgets.hpp b/editor/src/Components/Widgets.hpp index 551b35148..771085164 100644 --- a/editor/src/Components/Widgets.hpp +++ b/editor/src/Components/Widgets.hpp @@ -17,8 +17,11 @@ #include #include +#include "components/Camera.hpp" #include "components/Render3D.hpp" +#include "components/Transform.hpp" #include "renderer/Texture.hpp" +#include "core/scene/SceneManager.hpp" namespace nexo::editor { @@ -73,5 +76,10 @@ namespace nexo::editor { * @return true if any material property was modified; false otherwise. */ static bool drawMaterialInspector(components::Material *material); + + static void drawTransformProperties(components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler); + static void drawCameraProperties(components::CameraComponent &cameraComponent); + + static bool drawCameraCreator(const scene::SceneId sceneId, ImVec2 sceneViewportSize); }; } From 4386aac70f8ca586710a99d5af2e4f7eb60af816 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 11:59:17 +0900 Subject: [PATCH 117/450] feat(documents-windows): add render boolean to camera component to control if a camera should be rendered to --- engine/src/components/Camera.hpp | 1 + engine/src/systems/CameraSystem.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 5d4efabea..652f2ed33 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -48,6 +48,7 @@ namespace nexo::components { 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. diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index 6e1e1edee..250a01a0b 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -55,6 +55,8 @@ namespace nexo::system { 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); From fcacb0ab1aa7ed4aadb79864b332c60bcfd2cac8 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 12:00:29 +0900 Subject: [PATCH 118/450] refactor(document-windows): set render property of camera to true to be rendered --- editor/src/Components/Widgets.cpp | 1 + editor/src/DocumentWindows/EditorScene.cpp | 2 ++ .../src/DocumentWindows/EntityProperties/RenderProperty.cpp | 4 ++-- editor/src/utils/ScenePreview.cpp | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/editor/src/Components/Widgets.cpp b/editor/src/Components/Widgets.cpp index bee5bff28..28914c523 100644 --- a/editor/src/Components/Widgets.cpp +++ b/editor/src/Components/Widgets.cpp @@ -328,6 +328,7 @@ namespace nexo::editor { if (EntityPropertiesComponents::drawHeader("##CameraNode", "Camera")) { auto &cameraComponent = app.m_coordinator->getComponent(camera); + cameraComponent.render = true; Widgets::drawCameraProperties(cameraComponent); ImGui::TreePop(); } diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 4ffe06600..cb53c5e20 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -57,6 +57,8 @@ namespace nexo::editor { framebufferSpecs.height = static_cast(m_viewSize.y); const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs); m_activeCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, 0.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget); + auto &cameraComponent = app.m_coordinator->getComponent(m_activeCamera); + cameraComponent.render = true; m_cameras.insert(static_cast(m_activeCamera)); app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_activeCamera)); components::PerspectiveCameraController controller; diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index 6b3ba1af2..ca38ef541 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -47,6 +47,7 @@ namespace nexo::editor { 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 renderable3D = std::dynamic_pointer_cast(nexo::Application::m_coordinator->getComponent(scenePreviewInfo.entityCopy).renderable); @@ -183,7 +184,7 @@ namespace nexo::editor { // --- Material Action Buttons --- if (ImGui::Button("Create new material")) { - m_popupManager.openPopup("Create new material"); + m_popupManager.openPopup("Create new material", ImVec2(1440,900)); } ImGui::SameLine(); if (ImGui::Button("Modify Material")) @@ -199,7 +200,6 @@ namespace nexo::editor { ImGui::TreePop(); } - ImGui::SetNextWindowSize(ImVec2(1440,900)); if (m_popupManager.showPopupModal("Create new material")) { createMaterialPopup(entity); diff --git a/editor/src/utils/ScenePreview.cpp b/editor/src/utils/ScenePreview.cpp index cfe02998d..50cf0e08d 100644 --- a/editor/src/utils/ScenePreview.cpp +++ b/editor/src/utils/ScenePreview.cpp @@ -113,6 +113,8 @@ namespace nexo::editor::utils { // Update the camera's transform. auto &cameraTransform = nexo::Application::m_coordinator->getComponent(cameraId); cameraTransform.pos = cameraPos; + auto &cameraComponent = nexo::Application::m_coordinator->getComponent(cameraId); + cameraComponent.render = true; // Compute the camera's orientation so that it looks at the target. glm::vec3 newFront = glm::normalize(targetPos - cameraPos); From 205652e65990c051c1e2d24e3d63c3f1d481357d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 12:01:12 +0900 Subject: [PATCH 119/450] feat(document-windows): add optional popup size to the open popup method --- editor/src/DocumentWindows/PopupManager.cpp | 42 ++++++++++++++----- editor/src/DocumentWindows/PopupManager.hpp | 13 ++++-- .../src/DocumentWindows/SceneTreeWindow.cpp | 3 +- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/editor/src/DocumentWindows/PopupManager.cpp b/editor/src/DocumentWindows/PopupManager.cpp index b47fb619d..b6fe80c30 100644 --- a/editor/src/DocumentWindows/PopupManager.cpp +++ b/editor/src/DocumentWindows/PopupManager.cpp @@ -19,15 +19,24 @@ #include namespace nexo::editor { - void PopupManager::openPopup(const std::string &popupName) + void PopupManager::openPopup(const std::string &popupName, const ImVec2 &popupSize) { - m_popups[popupName] = true; + PopupProps props{ + .open = true, + .callback = nullptr, + .size = popupSize + }; + m_popups[popupName] = props; } - void PopupManager::openPopupWithCallback(const std::string &popupName, PopupCallback callback) + void PopupManager::openPopupWithCallback(const std::string &popupName, PopupCallback callback, const ImVec2 &popupSize) { - m_popups[popupName] = true; - m_callbacks[popupName] = callback; + PopupProps props{ + .open = true, + .callback = callback, + .size = popupSize + }; + m_popups[popupName] = props; } void PopupManager::closePopup() const @@ -44,10 +53,15 @@ namespace nexo::editor { { if (!m_popups.contains(popupName)) return false; - if (m_popups.at(popupName)) + PopupProps &props = m_popups.at(popupName); + if (props.open) { ImGui::OpenPopup(popupName.c_str()); - m_popups.at(popupName) = false; + props.open = false; + } + if (props.size.x != 0 && props.size.y != 0) + { + ImGui::SetNextWindowSize(props.size); } return ImGui::BeginPopup(popupName.c_str()); } @@ -56,10 +70,16 @@ namespace nexo::editor { { if (!m_popups.contains(popupModalName)) return false; - if (m_popups.at(popupModalName)) + PopupProps &props = m_popups.at(popupModalName); + if (m_popups.at(popupModalName).open) { ImGui::OpenPopup(popupModalName.c_str()); - m_popups.at(popupModalName) = false; + props.open = false; + } + + if (props.size.x != 0 && props.size.y != 0) + { + ImGui::SetNextWindowSize(props.size); } ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize @@ -87,9 +107,9 @@ namespace nexo::editor { void PopupManager::runPopupCallback(const std::string &popupName) { - if (m_callbacks.contains(popupName)) + if (m_popups.contains(popupName) && m_popups.at(popupName).callback != nullptr) { - m_callbacks[popupName](); + m_popups.at(popupName).callback(); } } diff --git a/editor/src/DocumentWindows/PopupManager.hpp b/editor/src/DocumentWindows/PopupManager.hpp index 61a3a0316..d163f9847 100644 --- a/editor/src/DocumentWindows/PopupManager.hpp +++ b/editor/src/DocumentWindows/PopupManager.hpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace nexo::editor { @@ -29,6 +30,11 @@ namespace nexo::editor { public: using PopupCallback = std::function; + struct PopupProps { + bool open = false; + PopupCallback callback = nullptr; + ImVec2 size = ImVec2(0, 0); + }; /** * @brief Opens a popup by name. @@ -37,9 +43,9 @@ namespace nexo::editor { * * @param popupName The unique name of the popup. */ - void openPopup(const std::string &popupName); + void openPopup(const std::string &popupName, const ImVec2 &popupSize = ImVec2(0, 0)); - void openPopupWithCallback(const std::string &popupName, PopupCallback callback); + void openPopupWithCallback(const std::string &popupName, PopupCallback callback, const ImVec2 &popupSize = ImVec2(0, 0)); /** * @brief Displays a modal popup. @@ -87,7 +93,6 @@ namespace nexo::editor { } }; - std::unordered_map> m_popups; - std::unordered_map m_callbacks; // Store callbacks + std::unordered_map> m_popups; }; } diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index 965f827d7..c76d03cbc 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -151,7 +151,7 @@ namespace nexo::editor { break; } } - }); + }, ImVec2(1440,900)); } ImGui::EndMenu(); // <-- matches the BeginMenu("Add Entity") @@ -256,7 +256,6 @@ namespace nexo::editor { m_popupManager.closePopup(); } - ImGui::SetNextWindowSize(ImVec2(1440,900)); if (m_popupManager.showPopupModal("Popup camera inspector")) { m_popupManager.runPopupCallback("Popup camera inspector"); m_popupManager.closePopup(); From e8844c9b9e62d4b2f1565d10af7f4920533957be Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 13:56:45 +0900 Subject: [PATCH 120/450] feat(document-windows): add billboards --- engine/src/EntityFactory3D.cpp | 41 ++++ engine/src/EntityFactory3D.hpp | 4 + engine/src/components/Shapes3D.hpp | 12 + engine/src/renderer/Renderer3D.cpp | 1 + engine/src/renderer/Renderer3D.hpp | 5 + engine/src/renderer/primitives/Billboard.cpp | 238 +++++++++++++++++++ 6 files changed, 301 insertions(+) create mode 100644 engine/src/renderer/primitives/Billboard.cpp diff --git a/engine/src/EntityFactory3D.cpp b/engine/src/EntityFactory3D.cpp index d3fb37580..c721a3372 100644 --- a/engine/src/EntityFactory3D.cpp +++ b/engine/src/EntityFactory3D.cpp @@ -14,6 +14,7 @@ #include "EntityFactory3D.hpp" #include "components/Light.hpp" +#include "components/Shapes3D.hpp" #include "components/Transform.hpp" #include "components/Uuid.hpp" #include "components/Camera.hpp" @@ -85,6 +86,46 @@ namespace nexo { Application::m_coordinator->addComponent(newModel, uuid); return newModel; } + + ecs::Entity EntityFactory3D::createBillboard(const glm::vec3 &pos, const glm::vec3 &size, const glm::vec4 &color) + { + components::TransformComponent transform{}; + + transform.pos = pos; + transform.size = size; + components::Material material{}; + material.albedoColor = color; + auto billboard = std::make_shared(); + auto renderable = std::make_shared(material, billboard); + components::RenderComponent renderComponent(renderable, components::RenderType::RENDER_3D); + + ecs::Entity newBillboard = Application::m_coordinator->createEntity(); + Application::m_coordinator->addComponent(newBillboard, transform); + Application::m_coordinator->addComponent(newBillboard, renderComponent); + components::UuidComponent uuid; + Application::m_coordinator->addComponent(newBillboard, uuid); + + return newBillboard; + } + + ecs::Entity EntityFactory3D::createBillboard(const glm::vec3 &pos, const glm::vec3 &size, const components::Material &material) + { + components::TransformComponent transform{}; + + transform.pos = pos; + transform.size = size; + auto billboard = std::make_shared(); + auto renderable = std::make_shared(material, billboard); + components::RenderComponent renderComponent(renderable, components::RenderType::RENDER_3D); + + ecs::Entity newBillboard = Application::m_coordinator->createEntity(); + Application::m_coordinator->addComponent(newBillboard, transform); + Application::m_coordinator->addComponent(newBillboard, renderComponent); + components::UuidComponent uuid; + Application::m_coordinator->addComponent(newBillboard, uuid); + + return newBillboard; + } } namespace nexo::utils { diff --git a/engine/src/EntityFactory3D.hpp b/engine/src/EntityFactory3D.hpp index 12b10ef65..3378250e2 100644 --- a/engine/src/EntityFactory3D.hpp +++ b/engine/src/EntityFactory3D.hpp @@ -59,7 +59,11 @@ namespace nexo * @return ecs::Entity The newly created cube entity. */ static ecs::Entity createCube(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, const components::Material &material); + static ecs::Entity createModel(const std::string& path, glm::vec3 pos, glm::vec3 size, glm::vec3 rotation); + + static ecs::Entity createBillboard(const glm::vec3 &pos, const glm::vec3 &size, const glm::vec4 &color); + static ecs::Entity createBillboard(const glm::vec3 &pos, const glm::vec3 &size, const components::Material &material); }; } diff --git a/engine/src/components/Shapes3D.hpp b/engine/src/components/Shapes3D.hpp index 3069ea6f2..93f4e3174 100644 --- a/engine/src/components/Shapes3D.hpp +++ b/engine/src/components/Shapes3D.hpp @@ -112,4 +112,16 @@ namespace nexo::components { } }; + struct BillBoard final : Shape3D { + void draw(std::shared_ptr &context, const TransformComponent &transf, const Material &material, const int entityID) override + { + const auto renderer3D = context->renderer3D; + renderer3D.drawBillboard(transf.pos, transf.size, material, entityID); + } + + [[nodiscard]] std::shared_ptr clone() const override + { + return std::make_shared(*this); + } + }; } diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index 6318a6c88..8c17dad28 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -82,6 +82,7 @@ namespace nexo::renderer { m_storage->vertexArray->bind(); m_storage->vertexBuffer->bind(); m_storage->textureShader->setUniformMatrix("viewProjection", viewProjection); + m_storage->cameraPosition = cameraPos; m_storage->textureShader->setUniformFloat3("camPos", cameraPos); m_storage->indexCount = 0; m_storage->vertexBufferPtr = m_storage->vertexBufferBase.data(); diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index fd85aed9c..fd87c02f3 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -78,6 +78,8 @@ namespace nexo::renderer { static constexpr unsigned int maxTextureSlots = 32; static constexpr unsigned int maxTransforms = 1024; + glm::vec3 cameraPosition; + std::shared_ptr textureShader; std::shared_ptr vertexArray; std::shared_ptr vertexBuffer; @@ -305,6 +307,9 @@ namespace nexo::renderer { void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& size, const components::Material& material, int entityID = -1) const; void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::mat4& transform, const components::Material& material, int entityID = -1) const; + void drawBillboard(const glm::vec3& position, const glm::vec2& size, const glm::vec4& color, int entityID) const; + void drawBillboard(const glm::vec3& position, const glm::vec2& size, const components::Material& material, int entityID) const; + /** * @brief Resets rendering statistics. * diff --git a/engine/src/renderer/primitives/Billboard.cpp b/engine/src/renderer/primitives/Billboard.cpp new file mode 100644 index 000000000..d8e4cc344 --- /dev/null +++ b/engine/src/renderer/primitives/Billboard.cpp @@ -0,0 +1,238 @@ +//// Billboard.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: 17/04/2025 +// Description: Source file for the billboard render function +// +/////////////////////////////////////////////////////////////////////////////// + +#include "renderer/Renderer3D.hpp" +#include "renderer/RendererExceptions.hpp" + +#include +#include +#include +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + +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 + }; + + constexpr unsigned int billboardIndices[6] = { + 0, 1, 2, 2, 3, 0 // Two triangles forming a quad + }; + + constexpr glm::vec2 billboardTexCoords[4] = { + {0.0f, 1.0f}, // Bottom left (flipped Y) + {1.0f, 1.0f}, // Bottom right (flipped Y) + {1.0f, 0.0f}, // Top right (flipped Y) + {0.0f, 0.0f} // Top left (flipped Y) + }; + + /** + * @brief Generates the vertex, texture coordinate, and normal data for a billboard mesh. + * + * Fills the provided arrays with 6 vertices, texture coordinates, and normals for a billboard quad. + * + * @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 genBillboardMesh(std::array &vertices, std::array &texCoords, std::array &normals) + { + // Vertex positions + vertices[0] = billboardPositions[0]; // Bottom left + vertices[1] = billboardPositions[1]; // Bottom right + vertices[2] = billboardPositions[2]; // Top right + vertices[3] = billboardPositions[2]; // Top right + vertices[4] = billboardPositions[3]; // Top left + vertices[5] = billboardPositions[0]; // Bottom left + + // Texture coordinates + texCoords[0] = billboardTexCoords[0]; // Bottom left + texCoords[1] = billboardTexCoords[1]; // Bottom right + texCoords[2] = billboardTexCoords[2]; // Top right + texCoords[3] = billboardTexCoords[2]; // Top right + texCoords[4] = billboardTexCoords[3]; // Top left + 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) { + normals[i] = {0.0f, 0.0f, 1.0f}; + } + } + + /** + * @brief Calculates a billboard rotation matrix that makes the quad face the camera. + * + * @param billboardPosition The position of the billboard in world space. + * @param cameraPosition The position of the camera in world space. + * @param cameraUp The up vector of the camera (usually {0,1,0}). + * @param constrainToY Whether to only rotate around Y-axis (true) or do full rotation (false). + * @return glm::mat4 The rotation matrix for the billboard. + */ + static glm::mat4 calculateBillboardRotation( + const glm::vec3& billboardPosition, + const glm::vec3& cameraPosition, + const glm::vec3& cameraUp = glm::vec3(0.0f, 1.0f, 0.0f), + bool constrainToY = false) + { + // Direction from billboard to camera + glm::vec3 look = glm::normalize(cameraPosition - billboardPosition); + + if (constrainToY) { + // For Y-axis constrained billboards (like trees) + look.y = 0.0f; // Zero out the Y component + look = glm::normalize(look); // Re-normalize + + // Calculate right vector from up and look + glm::vec3 right = glm::normalize(glm::cross(cameraUp, look)); + + // Create rotation matrix for Y-axis constrained billboard + return glm::mat4( + glm::vec4(right, 0.0f), + glm::vec4(cameraUp, 0.0f), + glm::vec4(look, 0.0f), + glm::vec4(0.0f, 0.0f, 0.0f, 1.0f) + ); + } + else { + // For full billboards (complete rotation to face camera) + // Create a look-at matrix but use it as a rotation matrix + return glm::transpose(glm::lookAt( + glm::vec3(0.0f), // Eye at origin + look, // Look in the direction of camera + cameraUp // Camera's up vector + )); + } + } + + void Renderer3D::drawBillboard( + const glm::vec3& position, + const glm::vec2& size, + const glm::vec4& color, + int entityID) const + { + if (!m_renderingScene) + { + THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + "Renderer not rendering a scene, make sure to call beginScene first"); + } + + // Get camera position from view matrix + glm::vec3 cameraPos = m_storage->cameraPosition; + + // Calculate billboard rotation to face camera + glm::mat4 billboardRotation = calculateBillboardRotation(position, cameraPos); + + // Create transformation matrix + const glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * + billboardRotation * + glm::scale(glm::mat4(1.0f), glm::vec3(size.x, size.y, 1.0f)); + + m_storage->textureShader->setUniformMatrix("matModel", transform); + + // Set material + renderer::Material mat; + mat.albedoColor = color; + setMaterialUniforms(mat); + + // Generate mesh data + std::array verts{}; + std::array texCoords{}; + std::array normals{}; + std::array indices{}; + + genBillboardMesh(verts, texCoords, normals); + for (unsigned int i = 0; i < 6; ++i) + indices[i] = i; + + // Vertex data + for (unsigned int i = 0; i < 6; ++i) + { + m_storage->vertexBufferPtr->position = glm::vec4(verts[i], 1.0f); + m_storage->vertexBufferPtr->texCoord = texCoords[i]; + m_storage->vertexBufferPtr->normal = normals[i]; + m_storage->vertexBufferPtr->entityID = entityID; + m_storage->vertexBufferPtr++; + } + + // Index data + std::ranges::for_each(indices, [this](unsigned int index) { + m_storage->indexBufferBase[m_storage->indexCount++] = index; + }); + } + + void Renderer3D::drawBillboard( + const glm::vec3& position, + const glm::vec2& size, + const components::Material& material, + int entityID) const + { + if (!m_renderingScene) + { + THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + "Renderer not rendering a scene, make sure to call beginScene first"); + } + + // Get camera position from view matrix + glm::vec3 cameraPos = m_storage->cameraPosition; + + // Calculate billboard rotation to face camera + glm::mat4 billboardRotation = calculateBillboardRotation(position, cameraPos); + + // Create transformation matrix + const glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * + billboardRotation * + glm::scale(glm::mat4(1.0f), glm::vec3(size.x, size.y, 1.0f)); + + m_storage->textureShader->setUniformMatrix("matModel", transform); + + // Set material + renderer::Material mat; + mat.albedoColor = material.albedoColor; + mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; + mat.specularColor = material.specularColor; + mat.specularTexIndex = material.metallicMap ? getTextureIndex(material.metallicMap) : 0; + setMaterialUniforms(mat); + + // Generate mesh data + std::array verts{}; + std::array texCoords{}; + std::array normals{}; + std::array indices{}; + + genBillboardMesh(verts, texCoords, normals); + for (unsigned int i = 0; i < 6; ++i) + indices[i] = i; + + // Vertex data + for (unsigned int i = 0; i < 6; ++i) + { + m_storage->vertexBufferPtr->position = glm::vec4(verts[i], 1.0f); + m_storage->vertexBufferPtr->texCoord = texCoords[i]; + m_storage->vertexBufferPtr->normal = normals[i]; + m_storage->vertexBufferPtr->entityID = entityID; + m_storage->vertexBufferPtr++; + } + + // Index data + std::ranges::for_each(indices, [this](unsigned int index) { + m_storage->indexBufferBase[m_storage->indexCount++] = index; + }); + } +} From acb5b348a3a2826a4eaca40b327cea9b1019bffd Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 14:15:38 +0900 Subject: [PATCH 121/450] feat(document-windows): add example billboard in the default scene --- editor/src/DocumentWindows/EditorScene.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index cb53c5e20..662871ec7 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -22,6 +22,7 @@ #include "LightFactory.hpp" #include "CameraFactory.hpp" #include "Nexo.hpp" +#include "Texture.hpp" #include "WindowRegistry.hpp" #include "components/Camera.hpp" #include "components/Uuid.hpp" @@ -89,6 +90,12 @@ namespace nexo::editor { const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, -5.0f, -5.0f}, {20.0f, 1.0f, 20.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, 0.5f, 0.31f, 1.0f}); app.getSceneManager().getScene(m_sceneId).addEntity(basicCube); + + std::shared_ptr billboardTexture = renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png")); + components::Material billboardMat{}; + billboardMat.albedoTexture = billboardTexture; + const ecs::Entity billboard = EntityFactory3D::createBillboard({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, billboardMat); + app.getSceneManager().getScene(m_sceneId).addEntity(billboard); } void EditorScene::setupWindow() From b5741447c44c76e16f144e9306004a611649895d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 14:15:52 +0900 Subject: [PATCH 122/450] chore(document-windows): add source files --- engine/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 5c7513d50..e16fc028c 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -37,6 +37,7 @@ set(COMMON_SOURCES engine/src/renderer/Framebuffer.cpp engine/src/renderer/primitives/Mesh.cpp engine/src/renderer/primitives/Cube.cpp + engine/src/renderer/primitives/Billboard.cpp engine/src/core/scene/Scene.cpp engine/src/core/scene/SceneManager.cpp engine/src/ecs/Entity.cpp From 52f2d4eccb7596cbfd63ca3753b53a3303aafb70 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 14:16:17 +0900 Subject: [PATCH 123/450] fix(document-windows): add condition to handle transparent textures --- resources/shaders/texture.glsl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/shaders/texture.glsl b/resources/shaders/texture.glsl index 46fa91506..9aa1575d0 100644 --- a/resources/shaders/texture.glsl +++ b/resources/shaders/texture.glsl @@ -166,6 +166,8 @@ void main() vec3 norm = normalize(vNormal); vec3 viewDir = normalize(camPos - vFragPos); vec3 result = vec3(0.0); + if (texture(uTexture[material.albedoTexIndex], vTexCoord).a < 0.1) + discard; vec3 ambient = ambientLight * material.albedoColor.rgb * vec3(texture(uTexture[material.albedoTexIndex], vTexCoord)); result += ambient; From db4f2ab61962fd98f2223fc61d28b86526b71d1a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 14:16:32 +0900 Subject: [PATCH 124/450] assets(document-windows): add camera icon --- resources/textures/cameraIcon.png | Bin 0 -> 1281999 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/textures/cameraIcon.png diff --git a/resources/textures/cameraIcon.png b/resources/textures/cameraIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..f10d48720f1cf2e2870108dd2f71373d433a3574 GIT binary patch literal 1281999 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%0D+j8J;qeGRh=GIEDJ7 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�Jtihpv&Mo3a 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

%qFz3e1JFH=ax_`)yU+Xza_j^Q{cb4A(>o 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"ETI-8@QC7 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

qs$XGSz^czRZB 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 WoH=L$)JM1EdNC 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�hv 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�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 @LY89{=kE=_Lk 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Ċ=ddGaw|JBv>I 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%0AMA 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 Date: Thu, 17 Apr 2025 14:51:58 +0900 Subject: [PATCH 125/450] feat(document-windows): add editor camera tag component --- editor/src/DocumentWindows/EditorScene.cpp | 8 ++------ engine/src/Application.cpp | 1 + engine/src/components/Camera.hpp | 2 ++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 662871ec7..9fe141a09 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -64,6 +64,8 @@ namespace nexo::editor { app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_activeCamera)); components::PerspectiveCameraController controller; Application::m_coordinator->addComponent(static_cast(m_activeCamera), controller); + components::EditorCameraTag editorCameraTag; + Application::m_coordinator->addComponent(m_activeCamera, editorCameraTag); m_sceneUuid = app.getSceneManager().getScene(m_sceneId).getUuid(); if (m_defaultScene) @@ -90,12 +92,6 @@ namespace nexo::editor { const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, -5.0f, -5.0f}, {20.0f, 1.0f, 20.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, 0.5f, 0.31f, 1.0f}); app.getSceneManager().getScene(m_sceneId).addEntity(basicCube); - - std::shared_ptr billboardTexture = renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png")); - components::Material billboardMat{}; - billboardMat.albedoTexture = billboardTexture; - const ecs::Entity billboard = EntityFactory3D::createBillboard({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, billboardMat); - app.getSceneManager().getScene(m_sceneId).addEntity(billboard); } void EditorScene::setupWindow() diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index 26aa707b4..ee214b55f 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -70,6 +70,7 @@ namespace nexo { m_coordinator->registerComponent(); m_coordinator->registerComponent(); m_coordinator->registerComponent(); + m_coordinator->registerComponent(); m_coordinator->registerSingletonComponent(); m_coordinator->registerComponent(); diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 652f2ed33..833b19a54 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -105,6 +105,8 @@ namespace nexo::components { } }; + struct EditorCameraTag {}; + /** * @brief Component used to control a perspective camera using mouse input. * From 97937711764e16e8e9ef6df1f036e6853340c6e6 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 14:52:29 +0900 Subject: [PATCH 126/450] feat(document-windows): add exclude filter in getAllEntitiesWith in order to exclude editor camera from the scene tree --- .../src/DocumentWindows/SceneTreeWindow.cpp | 3 +- engine/src/ecs/Coordinator.hpp | 69 +++++++++++++++++-- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index c76d03cbc..3ef29380e 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -14,6 +14,7 @@ #include "SceneTreeWindow.hpp" #include "ADocumentWindow.hpp" +#include "Coordinator.hpp" #include "utils/Config.hpp" #include "DocumentWindows/InspectorWindow.hpp" #include "Primitive.hpp" @@ -522,7 +523,7 @@ namespace nexo::editor { return this->newSpotLightNode(sceneId, uiId, entity); }); - generateNodes( + generateNodes>( sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return this->newCameraNode(sceneId, uiId, entity); diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 7685afabf..7b60e388e 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -25,6 +25,35 @@ namespace nexo::ecs { + template + struct Exclude { + using type = T; + }; + + // Check if type is an Exclude specialization + template + struct is_exclude : std::false_type {}; + + template + struct is_exclude> : std::true_type {}; + + template + inline constexpr bool is_exclude_v = is_exclude::value; + + // Extract the actual type from Exclude + template + struct extract_type { + using type = T; + }; + + template + struct extract_type> { + using type = T; + }; + + template + using extract_type_t = typename extract_type::type; + /** * @class Coordinator * @@ -234,26 +263,40 @@ namespace nexo::ecs { 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. - */ + * @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; - (requiredSignature.set(m_componentManager->getComponentType(), true), ...); + 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); - if ((entitySignature & requiredSignature) == requiredSignature) + + // Entity must have all required components + bool hasAllRequired = (entitySignature & requiredSignature) == requiredSignature; + + // Entity must not have any excluded components + bool hasAnyExcluded = (entitySignature & excludeSignature).any(); + + if (hasAllRequired && !hasAnyExcluded) result.push_back(entity); } + return result; } @@ -355,6 +398,18 @@ namespace nexo::ecs { } 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); + } + } + std::shared_ptr m_componentManager; std::shared_ptr m_entityManager; std::shared_ptr m_systemManager; From aaf5bffd3560be875e1533f45dd0f7e1790e5ac2 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 16:22:24 +0900 Subject: [PATCH 127/450] feat(document-windows): add renderable billboard to game cameras --- editor/src/Components/Widgets.cpp | 52 +++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/editor/src/Components/Widgets.cpp b/editor/src/Components/Widgets.cpp index 28914c523..60234965d 100644 --- a/editor/src/Components/Widgets.cpp +++ b/editor/src/Components/Widgets.cpp @@ -17,16 +17,21 @@ #include #include #include +#include "Path.hpp" #include "Components.hpp" #include "Definitions.hpp" #include "IconsFontAwesome.h" #include "Nexo.hpp" +#include "Texture.hpp" #include "components/Camera.hpp" #include "EntityPropertiesComponents.hpp" #include "CameraFactory.hpp" +#include "components/Render3D.hpp" #include "components/Transform.hpp" +#include "components/Uuid.hpp" #include "tinyfiledialogs.h" +#include "context/Selector.hpp" namespace nexo::editor { bool Widgets::drawColorEditor( @@ -191,8 +196,16 @@ namespace nexo::editor { framebufferSpecs.width = static_cast(sceneViewportSize.x); framebufferSpecs.height = static_cast(sceneViewportSize.y); const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs); - ecs::Entity defaultCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, 0.0f}, static_cast(sceneViewportSize.x), static_cast(sceneViewportSize.y), renderTarget); + ecs::Entity defaultCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, -5.0f}, static_cast(sceneViewportSize.x), static_cast(sceneViewportSize.y), renderTarget); app.getSceneManager().getScene(sceneId).addEntity(static_cast(defaultCamera)); + + components::Material billboardMat{}; + std::shared_ptr cameraIconTexture = renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png")); + billboardMat.albedoTexture = cameraIconTexture; + auto billboard = std::make_shared(); + auto renderable = std::make_shared(billboardMat, billboard); + components::RenderComponent renderComponent(renderable, components::RenderType::RENDER_3D); + app.m_coordinator->addComponent(defaultCamera, renderComponent); return defaultCamera; } @@ -312,17 +325,36 @@ namespace nexo::editor { { camera = createDefaultPerspectiveCamera(sceneId, ImVec2(previewWidth, totalHeight)); } + + static char cameraName[128] = ""; + static bool nameIsEmpty = false; ImGui::Columns(2, "CameraCreatorColumns", false); ImGui::SetColumnWidth(0, inspectorWidth); // --- Left Side: Camera Inspector --- { ImGui::BeginChild("CameraInspector", ImVec2(inspectorWidth - 4, totalHeight), true); - static char cameraName[128] = ""; ImGui::AlignTextToFramePadding(); ImGui::Text("Name"); ImGui::SameLine(); + if (nameIsEmpty) { + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.9f, 0.2f, 0.2f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + } ImGui::InputText("##CameraName", cameraName, IM_ARRAYSIZE(cameraName)); + if (nameIsEmpty) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.2f, 0.2f, 1.0f)); + ImGui::TextWrapped("Name is empty"); + ImGui::PopStyleColor(); + ImGui::Spacing(); + } else { + ImGui::Spacing(); + } + if (nameIsEmpty && cameraName[0] != '\0') + nameIsEmpty = false; ImGui::Spacing(); if (EntityPropertiesComponents::drawHeader("##CameraNode", "Camera")) @@ -375,12 +407,28 @@ namespace nexo::editor { if (ImGui::Button("OK", ImVec2(buttonWidth, 0))) { + if (cameraName[0] == '\0') { + nameIsEmpty = true; + return false; + } + nameIsEmpty = false; + auto &selector = Selector::get(); + auto &uuid = app.m_coordinator->getComponent(camera); + auto &cameraComponent = app.m_coordinator->getComponent(camera); + cameraComponent.active = false; + selector.setUiHandle(uuid.uuid, std::string(ICON_FA_CAMERA " ") + cameraName); + camera = ecs::MAX_ENTITIES; + cameraName[0] = '\0'; ImGui::CloseCurrentPopup(); return true; } ImGui::SameLine(); if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) { + nameIsEmpty = false; + app.deleteEntity(camera); + camera = ecs::MAX_ENTITIES; + cameraName[0] = '\0'; ImGui::CloseCurrentPopup(); return true; } From 7defacd3a139982da7b1c191f22f13120cb85375 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 16:22:54 +0900 Subject: [PATCH 128/450] fix(document-windows): discard camera entities for the render property --- editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index ca38ef541..05588e2b2 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -125,6 +125,8 @@ namespace nexo::editor { void RenderProperty::show(ecs::Entity entity) { + if (Application::m_coordinator->entityHasComponent(entity)) + return; auto& renderComponent = Application::getEntityComponent(entity); if (renderComponent.type == components::RenderType::RENDER_3D) From ed92df12324b86b5562abe6e7c9e0ea230974a76 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 16:23:29 +0900 Subject: [PATCH 129/450] feat(document-windows): add editor camera tag to main editor scene camera --- editor/src/DocumentWindows/EditorScene.cpp | 13 +++++++------ editor/src/DocumentWindows/EditorScene.hpp | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 9fe141a09..8d8f35691 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -57,15 +57,16 @@ namespace nexo::editor { framebufferSpecs.width = static_cast(m_viewSize.x); framebufferSpecs.height = static_cast(m_viewSize.y); const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs); - m_activeCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, 0.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget); - auto &cameraComponent = app.m_coordinator->getComponent(m_activeCamera); + m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, 0.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget); + auto &cameraComponent = app.m_coordinator->getComponent(m_editorCamera); cameraComponent.render = true; - m_cameras.insert(static_cast(m_activeCamera)); - app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_activeCamera)); + m_cameras.insert(static_cast(m_editorCamera)); + app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_editorCamera)); components::PerspectiveCameraController controller; - Application::m_coordinator->addComponent(static_cast(m_activeCamera), controller); + Application::m_coordinator->addComponent(static_cast(m_editorCamera), controller); components::EditorCameraTag editorCameraTag; - Application::m_coordinator->addComponent(m_activeCamera, editorCameraTag); + Application::m_coordinator->addComponent(m_editorCamera, editorCameraTag); + m_activeCamera = m_editorCamera; m_sceneUuid = app.getSceneManager().getScene(m_sceneId).getUuid(); if (m_defaultScene) diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp index 7e0d2f0cc..25f679cdb 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene.hpp @@ -92,6 +92,7 @@ namespace nexo::editor { std::string m_sceneUuid; std::set m_cameras; int m_activeCamera = -1; + int m_editorCamera = -1; /** * @brief Sets the main scene window's view size. From b5eb0cd80fbed012d42a1ce764bf5eb2d22f8c42 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 16:23:56 +0900 Subject: [PATCH 130/450] fix(document-windows): init size field to 1 --- engine/src/components/Transform.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/components/Transform.hpp b/engine/src/components/Transform.hpp index ff23c4008..38b5f4da6 100644 --- a/engine/src/components/Transform.hpp +++ b/engine/src/components/Transform.hpp @@ -21,7 +21,7 @@ namespace nexo::components { struct TransformComponent final { glm::vec3 pos; - glm::vec3 size; + glm::vec3 size = glm::vec3(1.0f); glm::quat quat = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); }; From f3b451d429d794b046e962d5bc808f392bdbc6e5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 17 Apr 2025 16:24:18 +0900 Subject: [PATCH 131/450] feat(document-windows): add camera preview when hovering in the scene tree --- .../src/DocumentWindows/SceneTreeWindow.cpp | 42 ++++++++++++++++++- .../src/DocumentWindows/SceneTreeWindow.hpp | 3 ++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index 3ef29380e..b27761411 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -19,6 +19,7 @@ #include "DocumentWindows/InspectorWindow.hpp" #include "Primitive.hpp" +#include #include #include @@ -77,6 +78,21 @@ namespace nexo::editor { ImGui::EndGroup(); } + void SceneTreeWindow::handleHovering(const SceneObject &obj) const + { + if (obj.type == SelectionType::CAMERA) { + static bool cameraHoveredLastFrame = false; + if (ImGui::IsItemHovered()) { + cameraHovered(obj); + cameraHoveredLastFrame = true; + } else if (cameraHoveredLastFrame) { + cameraHoveredLastFrame = false; + auto &cameraComponent = Application::getInstance().m_coordinator->getComponent(obj.data.entity); + cameraComponent.render = false; + } + } + } + bool SceneTreeWindow::handleSelection(const SceneObject &obj, const std::string &uniqueLabel, const ImGuiTreeNodeFlags baseFlags) const { @@ -170,6 +186,28 @@ namespace nexo::editor { } } + void SceneTreeWindow::cameraHovered(const SceneObject &obj) const + { + auto &app = Application::getInstance(); + auto &cameraComponent = app.m_coordinator->getComponent(obj.data.entity); + + if (cameraComponent.m_renderTarget) + { + ImGui::BeginTooltip(); + constexpr float previewSize = 200.0f; + cameraComponent.render = true; + const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); + + ImGui::Image( + static_cast(static_cast(textureId)), + ImVec2(previewSize, previewSize), + ImVec2(0, 1), ImVec2(1, 0) // Flip Y coordinates for OpenGL texture + ); + + ImGui::EndTooltip(); + } + } + void SceneTreeWindow::cameraSelected(const SceneObject &obj) const { auto &app = Application::getInstance(); @@ -217,6 +255,8 @@ namespace nexo::editor { else nodeOpen = handleSelection(object, uniqueLabel, baseFlags); + handleHovering(object); + // Handles the right click on each different type of object if (object.type != SelectionType::NONE && ImGui::BeginPopupContextItem(uniqueLabel.c_str())) { @@ -529,7 +569,7 @@ namespace nexo::editor { return this->newCameraNode(sceneId, uiId, entity); }); - generateNodes( + generateNodes>( sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return this->newEntityNode(sceneId, uiId, entity); diff --git a/editor/src/DocumentWindows/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow.hpp index 513722c0f..48e1680fc 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.hpp @@ -248,6 +248,8 @@ namespace nexo::editor { */ bool handleSelection(const SceneObject &obj, const std::string &uniqueLabel, ImGuiTreeNodeFlags baseFlags) const; + void handleHovering(const SceneObject &obj) const; + /** * @brief Displays a context menu option to delete a scene. * @@ -278,6 +280,7 @@ namespace nexo::editor { * @param obj The scene object representing the camera to be deleted. */ void cameraSelected(const SceneObject &obj) const; + void cameraHovered(const SceneObject &obj) const; void entitySelected(const SceneObject &obj) const; void showNode(SceneObject &object); void sceneContextMenu(); From d4780fce7d733d163049426b80ffd27dadd9ab49 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 06:38:46 +0900 Subject: [PATCH 132/450] feat(document-windows): add scene type to know if the scene is rendered in editor mode or game mode --- editor/src/DocumentWindows/EditorScene.cpp | 3 ++- engine/src/Application.cpp | 3 ++- engine/src/Application.hpp | 9 ++++---- engine/src/Nexo.cpp | 4 ++-- engine/src/Nexo.hpp | 2 +- engine/src/Types.hpp | 26 +++++++++++++++++++++ engine/src/components/RenderContext.hpp | 2 ++ engine/src/ecs/Access.hpp | 27 ++++++++++++++++++++++ engine/src/systems/RenderSystem.cpp | 5 +++- 9 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 engine/src/Types.hpp diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 8d8f35691..4573b4ed9 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -17,6 +17,7 @@ #include #include "ADocumentWindow.hpp" +#include "Application.hpp" #include "EntityFactory3D.hpp" #include "IconsFontAwesome.h" #include "LightFactory.hpp" @@ -249,7 +250,7 @@ namespace nexo::editor { return; if (m_focused && m_hovered) handleKeyEvents(); - runEngine(m_sceneId, RenderingType::FRAMEBUFFER); + runEngine(m_sceneId, RenderingType::FRAMEBUFFER, SceneType::EDITOR); auto const &cameraComponent = Application::m_coordinator->getComponent(static_cast(m_activeCamera)); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsUsing() && m_focused) { diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index ee214b55f..eb74c869d 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -241,13 +241,14 @@ namespace nexo { m_lastFrameTime = time; } - void Application::run(const scene::SceneId sceneId, const RenderingType renderingType) + void Application::run(const scene::SceneId sceneId, const RenderingType renderingType, const SceneType sceneType) { auto &renderContext = m_coordinator->getSingletonComponent(); if (!m_isMinimized) { renderContext.sceneRendered = sceneId; + renderContext.sceneType = sceneType; if (m_SceneManager.getScene(sceneId).isRendered()) { m_cameraContextSystem->update(); diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index da26f3027..33658d985 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -18,6 +18,7 @@ #include #include +#include "Types.hpp" #include "renderer/Window.hpp" #include "core/event/WindowEvent.hpp" #include "core/event/SignalEvent.hpp" @@ -37,10 +38,7 @@ namespace nexo { - enum class RenderingType { - WINDOW, - FRAMEBUFFER - }; + enum EventDebugFlags { DEBUG_LOG_RESIZE_EVENT = 1 << 0, @@ -93,8 +91,9 @@ namespace nexo { * * @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(scene::SceneId sceneId, RenderingType renderingType); + void run(scene::SceneId sceneId, RenderingType renderingType, SceneType sceneType = SceneType::GAME); /** * @brief Ends the current frame by clearing processed events. diff --git a/engine/src/Nexo.cpp b/engine/src/Nexo.cpp index 1496ef2b3..7447a1af2 100644 --- a/engine/src/Nexo.cpp +++ b/engine/src/Nexo.cpp @@ -28,10 +28,10 @@ namespace nexo { return Application::getInstance(); } - void runEngine(const scene::SceneId id, const RenderingType renderingType) + void runEngine(const scene::SceneId id, const RenderingType renderingType, SceneType sceneType) { Application &app = Application::getInstance(); - app.run(id, renderingType); + app.run(id, renderingType, sceneType); } } diff --git a/engine/src/Nexo.hpp b/engine/src/Nexo.hpp index de468cee9..395f5a3c6 100644 --- a/engine/src/Nexo.hpp +++ b/engine/src/Nexo.hpp @@ -45,5 +45,5 @@ namespace nexo { Application &getApp(); - void runEngine(scene::SceneId id, RenderingType renderingType = RenderingType::WINDOW); + void runEngine(scene::SceneId id, RenderingType renderingType = RenderingType::WINDOW, SceneType sceneType = SceneType::GAME); } diff --git a/engine/src/Types.hpp b/engine/src/Types.hpp new file mode 100644 index 000000000..c0e41fccd --- /dev/null +++ b/engine/src/Types.hpp @@ -0,0 +1,26 @@ +//// Types.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: Mehdy MORVAN +// Date: 18/04/2025 +// Description: Header file for the common engine types +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +namespace nexo { + enum class RenderingType { + WINDOW, + FRAMEBUFFER + }; + + enum class SceneType { + EDITOR, + GAME + }; +} diff --git a/engine/src/components/RenderContext.hpp b/engine/src/components/RenderContext.hpp index 1e4d76965..fa96e12b4 100644 --- a/engine/src/components/RenderContext.hpp +++ b/engine/src/components/RenderContext.hpp @@ -15,12 +15,14 @@ #include #include "Camera.hpp" +#include "Types.hpp" #include "renderer/Renderer3D.hpp" #include "Light.hpp" namespace nexo::components { struct RenderContext { int sceneRendered = -1; + SceneType sceneType; renderer::Renderer3D renderer3D; std::queue cameras; LightContext sceneLights; diff --git a/engine/src/ecs/Access.hpp b/engine/src/ecs/Access.hpp index b198b4928..99471d0b5 100644 --- a/engine/src/ecs/Access.hpp +++ b/engine/src/ecs/Access.hpp @@ -149,4 +149,31 @@ namespace nexo::ecs { */ template struct IsSingleton : std::bool_constant::value || IsWriteSingleton::value> {}; + + /** + * @brief Wrapper for component types that should exclude entities from a group + * + * Used to specify component types that, if present on an entity, will exclude + * that entity from the group even if it has all the required components. + * + * @tparam Components Component types that exclude entities + */ + template + struct ExcludeComponents { + using ComponentTypes = std::tuple; + }; + + /** + * @brief Helper function to create an Exclude object + * + * Similar to the get<> helper that already exists in your codebase, + * this provides a cleaner syntax for creating exclusions. + * + * @tparam Components Component types to exclude + * @return Exclude object + */ + template + ExcludeComponents exclude() { + return ExcludeComponents{}; + } } diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 23547de88..bdcb27274 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -91,6 +91,7 @@ namespace nexo::system { return; const auto sceneRendered = static_cast(renderContext.sceneRendered); + const SceneType sceneType = renderContext.sceneType; setupLights(renderContext.renderer3D.getShader(), renderContext.sceneLights); @@ -129,9 +130,11 @@ namespace nexo::system { 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 &render = renderSpan[i]; - const ecs::Entity entity = entitySpan[i]; if (render.isRendered) { renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition); From 331a78fbebfa636c67818dd169c2d70c7ab2248d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 06:57:38 +0900 Subject: [PATCH 133/450] fix(document-windows): fix winows compilation --- editor/src/Components/Widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/Components/Widgets.cpp b/editor/src/Components/Widgets.cpp index 60234965d..52af6ba22 100644 --- a/editor/src/Components/Widgets.cpp +++ b/editor/src/Components/Widgets.cpp @@ -200,7 +200,7 @@ namespace nexo::editor { app.getSceneManager().getScene(sceneId).addEntity(static_cast(defaultCamera)); components::Material billboardMat{}; - std::shared_ptr cameraIconTexture = renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png")); + std::shared_ptr cameraIconTexture = renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png").string()); billboardMat.albedoTexture = cameraIconTexture; auto billboard = std::make_shared(); auto renderable = std::make_shared(billboardMat, billboard); From 83c3034703d3e72b2ec171b7af841fa68b2a60a3 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 09:10:29 +0900 Subject: [PATCH 134/450] feat(document-windows): add camera target property --- .../EntityProperties/CameraTarget.cpp | 164 ++++++++++++++++++ .../EntityProperties/CameraTarget.hpp | 25 +++ 2 files changed, 189 insertions(+) create mode 100644 editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp create mode 100644 editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp diff --git a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp new file mode 100644 index 000000000..2afe3aaac --- /dev/null +++ b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp @@ -0,0 +1,164 @@ +//// CameraTarget.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: 18/04/2025 +// Description: Source file for the camera target property +// +/////////////////////////////////////////////////////////////////////////////// + +#include "CameraTarget.hpp" +#include "Coordinator.hpp" +#include "Definitions.hpp" +#include "components/Camera.hpp" +#include "Components/EntityPropertiesComponents.hpp" +#include "components/Light.hpp" +#include "components/Transform.hpp" +#include "components/Uuid.hpp" +#include "context/Selector.hpp" + +namespace nexo::editor { + + /** + * @brief Draw a dropdown to select an entity with proper UI name display + * + * @param label The label to display + * @param targetEntity Reference to the entity ID that will be updated on selection + * @param entities Vector of available entities to choose from + * @param getNameFunc Function to get UI name from entity ID + * @return true if the selection changed, false otherwise + */ + static bool drawRowEntityDropdown(const std::string &label, ecs::Entity &targetEntity, + const std::vector& entities, + const std::function& getNameFunc) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(label.c_str()); + + ImGui::TableNextColumn(); + + // Create a unique ID for this widget + ImGui::PushID(label.c_str()); + + ImGui::SetNextItemWidth(-FLT_MIN); // Use all available width + + bool changed = false; + + // Build entity-name mapping + static std::vector> entityNamePairs; + static ecs::Entity lastTargetEntity = 0; + static std::vector lastEntities; + + // Only rebuild the mapping if entities list changed or target entity changed + bool needRebuild = lastTargetEntity != targetEntity || + lastEntities.size() != entities.size(); + + if (!needRebuild) { + for (size_t i = 0; i < entities.size() && !needRebuild; i++) { + needRebuild = lastEntities[i] != entities[i]; + } + } + + if (needRebuild) { + entityNamePairs.clear(); + entityNamePairs.reserve(entities.size()); + lastEntities = entities; + lastTargetEntity = targetEntity; + + for (ecs::Entity entity : entities) { + std::string name = getNameFunc(entity); + entityNamePairs.emplace_back(entity, name); + } + } + + // 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; + } + } + + // Add a "None" option if we want to allow null selection + std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; + + // Draw the combo box + if (ImGui::BeginCombo("##entity_dropdown", currentItemName.c_str())) + { + // Optional: Add a "None" option for clearing the target + if (ImGui::Selectable("None", targetEntity == ecs::MAX_ENTITIES)) { + targetEntity = ecs::MAX_ENTITIES; + changed = true; + } + + 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; + } + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + ImGui::PopID(); + return changed; + } + + void CameraTarget::show(const ecs::Entity entity) + { + auto& targetComponent = Application::getEntityComponent(entity); + + if (EntityPropertiesComponents::drawHeader("##ControllerNode", "Camera Controller")) + { + ImGui::Spacing(); + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); + if (ImGui::BeginTable("InspectorControllerTable", 2, + ImGuiTableFlags_SizingStretchProp)) + { + auto &app = getApp(); + auto &selector = Selector::get(); + ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + const std::vector &entities = app.m_coordinator->getAllEntitiesWith< + components::TransformComponent, + ecs::Exclude, + ecs::Exclude, + ecs::Exclude, + ecs::Exclude, + ecs::Exclude>(); + + EntityPropertiesComponents::drawRowDragFloat1("Mouse sensitivity", "", &targetComponent.mouseSensitivity, 0.1f); + EntityPropertiesComponents::drawRowDragFloat1("Distance", "", &targetComponent.distance, 0.1f); + drawRowEntityDropdown( + "Target Entity", + targetComponent.targetEntity, entities, + [&app, &selector](ecs::Entity e) { + return selector.getUiHandle( + app.m_coordinator->getComponent(e).uuid, + std::to_string(e) + ); + }); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + } +} diff --git a/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp b/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp new file mode 100644 index 000000000..bf207a495 --- /dev/null +++ b/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp @@ -0,0 +1,25 @@ +//// CameraTarget.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: Mehdy MORVAN +// Date: 18/04/2025 +// Description: Header file for the camera target component +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "AEntityProperty.hpp" + +namespace nexo::editor { + class CameraTarget : public AEntityProperty { + public: + using AEntityProperty::AEntityProperty; + + void show(ecs::Entity entity) override; + }; +} From 80f86f7f6fae0be79835bc6eeccb17ddd02cb094 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 09:11:13 +0900 Subject: [PATCH 135/450] feat(document-windows): add component button + entity dropdown selection --- editor/src/Components/Components.cpp | 184 +++++++++++++++++++++++++++ editor/src/Components/Components.hpp | 10 ++ 2 files changed, 194 insertions(+) diff --git a/editor/src/Components/Components.cpp b/editor/src/Components/Components.cpp index f38fef1f3..ebd161711 100644 --- a/editor/src/Components/Components.cpp +++ b/editor/src/Components/Components.cpp @@ -40,6 +40,105 @@ namespace nexo::editor { return clicked; } + bool Components::drawComponentButton( + const std::string &uniqueId, + const std::string &icon, + const std::string &label, + const ImVec2 &itemSize + ) { + ImGui::PushID(uniqueId.c_str()); + std::string invisButtonLabel = "##" + uniqueId; + // Create invisible button + if (ImGui::InvisibleButton(invisButtonLabel.c_str(), itemSize)) + { + ImGui::PopID(); + return true; + // app.m_coordinator->addComponent(camera, components::PerspectiveCameraTarget{}); + //showComponentSelector = false; + } + + // Draw the background (like a button would have) + ImVec2 p0 = ImGui::GetItemRectMin(); + ImVec2 p1 = ImGui::GetItemRectMax(); + ImGui::GetWindowDrawList()->AddRectFilled( + p0, p1, + ImGui::GetColorU32(ImGui::IsItemHovered() ? ImGuiCol_ButtonHovered : ImGuiCol_Button), + ImGui::GetStyle().FrameRounding + ); + + // Increase icon size even further (2x larger) + float iconScale = 1.5f; + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); + ImGui::SetWindowFontScale(iconScale); + ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str()); + ImGui::SetWindowFontScale(1.0f); + ImGui::PopFont(); + + // Draw the icon centered + ImVec2 iconPos = ImVec2( + p0.x + (p1.x - p0.x - iconSize.x) * 0.45f, + p0.y + (p1.y - p0.y) * 0.25f - iconSize.y * 0.5f // Position at 35% from top + ); + ImGui::GetWindowDrawList()->AddText( + ImGui::GetFont(), ImGui::GetFontSize() * iconScale, + iconPos, ImGui::GetColorU32(ImGuiCol_Text), + icon.c_str() + ); + + // Implement text wrapping for the label + float wrapWidth = p1.x - p0.x - 10.0f; // Available width with padding + float textHeight = ImGui::GetFontSize(); + float textY = p0.y + (p1.y - p0.y) * 0.60f; // Position at 75% from top + + // Calculate if we need to wrap (if text is wider than button) + ImVec2 textSize = ImGui::CalcTextSize(label.c_str()); + if (textSize.x > wrapWidth) { + // Find where to split the text + size_t splitPos = label.find(" "); + + if (splitPos != std::string::npos) { + // Split at space + std::string line1 = label.substr(0, splitPos); + std::string line2 = label.substr(splitPos + 1); + + // Draw first line + ImVec2 line1Pos = ImVec2( + p0.x + (p1.x - p0.x - ImGui::CalcTextSize(line1.c_str()).x) * 0.5f, + textY - textHeight * 0.5f + ); + ImGui::GetWindowDrawList()->AddText(line1Pos, ImGui::GetColorU32(ImGuiCol_Text), line1.c_str()); + + // Draw second line + ImVec2 line2Pos = ImVec2( + p0.x + (p1.x - p0.x - ImGui::CalcTextSize(line2.c_str()).x) * 0.5f, + textY + textHeight * 0.5f + ); + ImGui::GetWindowDrawList()->AddText(line2Pos, ImGui::GetColorU32(ImGuiCol_Text), line2.c_str()); + } else { + // No space to split, just draw the text with possible cutoff + ImVec2 textPos = ImVec2( + p0.x + (p1.x - p0.x - textSize.x) * 0.5f, + textY + ); + ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGuiCol_Text), label.c_str()); + } + } else { + // No wrapping needed, just center the text + ImVec2 textPos = ImVec2( + p0.x + (p1.x - p0.x - textSize.x) * 0.5f, + textY + ); + ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGuiCol_Text), label.c_str()); + } + + // Add border when hovered or active + if (ImGui::IsItemHovered() || ImGui::IsItemActive()) + ImGui::GetWindowDrawList()->AddRect(p0, p1, ImGui::GetColorU32(ImGuiCol_ButtonActive), ImGui::GetStyle().FrameRounding); + + ImGui::PopID(); + return false; + } + void Components::drawButtonBorder( const ImU32 borderColor, const ImU32 borderColorHovered, @@ -320,4 +419,89 @@ namespace nexo::editor { fillConvexPolygon(drawList, segPoly, polyColors); } } + + bool Components::drawRowEntityDropdown(const std::string &label, ecs::Entity &targetEntity, + const std::vector& entities, + const std::function& getNameFunc) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(label.c_str()); + + ImGui::TableNextColumn(); + + // Create a unique ID for this widget + ImGui::PushID(label.c_str()); + + //ImGui::SetNextItemWidth(-FLT_MIN); // Use all available width + + bool changed = false; + + // Build entity-name mapping + static std::vector> entityNamePairs; + static ecs::Entity lastTargetEntity = 0; + static std::vector lastEntities; + + // Only rebuild the mapping if entities list changed or target entity changed + bool needRebuild = lastTargetEntity != targetEntity || + lastEntities.size() != entities.size(); + + if (!needRebuild) { + for (size_t i = 0; i < entities.size() && !needRebuild; i++) { + needRebuild = lastEntities[i] != entities[i]; + } + } + + if (needRebuild) { + entityNamePairs.clear(); + entityNamePairs.reserve(entities.size()); + lastEntities = entities; + lastTargetEntity = targetEntity; + + for (ecs::Entity entity : entities) { + std::string name = getNameFunc(entity); + entityNamePairs.emplace_back(entity, name); + } + } + + // 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; + } + } + + // Add a "None" option if we want to allow null selection + std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; + + // Draw the combo box + if (ImGui::BeginCombo("##entity_dropdown", currentItemName.c_str())) + { + // Optional: Add a "None" option for clearing the target + if (ImGui::Selectable("None", targetEntity == ecs::MAX_ENTITIES)) { + targetEntity = ecs::MAX_ENTITIES; + changed = true; + } + + 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; + } + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + ImGui::PopID(); + return changed; + } } diff --git a/editor/src/Components/Components.hpp b/editor/src/Components/Components.hpp index b0b29d955..f303606aa 100644 --- a/editor/src/Components/Components.hpp +++ b/editor/src/Components/Components.hpp @@ -16,6 +16,9 @@ #include #include #include +#include + +#include "ecs/Coordinator.hpp" namespace nexo::editor { @@ -43,6 +46,8 @@ namespace nexo::editor { */ static bool drawButton(const std::string &label, const ImVec2& size = ImVec2(0, 0), ImU32 bg = 0, ImU32 bgHovered = 0, ImU32 bgActive = 0, ImU32 txtColor = 0); + static bool drawComponentButton(const std::string &uniqueId, const std::string &icon, const std::string &label, const ImVec2 &itemSize); + /** * @brief Draws a border around the last item. * @@ -149,5 +154,10 @@ namespace nexo::editor { * @param stops Vector of gradient stops, each defined by a position (0.0f to 1.0f) and a color */ static void drawRectFilledLinearGradient(const ImVec2& pMin, const ImVec2& pMax, float angle, std::vector stops, ImDrawList* drawList = nullptr); + + + static bool drawRowEntityDropdown(const std::string &label, ecs::Entity &targetEntity, + const std::vector& entities, + const std::function& getNameFunc); }; } From 50cf8365b3845dd3bfa8f03d33696c8e22c07b6c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 09:11:51 +0900 Subject: [PATCH 136/450] feat(document-windows): add draw camera controller and target + add add component button to camera creator --- editor/src/Components/Widgets.cpp | 156 +++++++++++++++++++++++++++++- editor/src/Components/Widgets.hpp | 2 + 2 files changed, 157 insertions(+), 1 deletion(-) diff --git a/editor/src/Components/Widgets.cpp b/editor/src/Components/Widgets.cpp index 52af6ba22..e1ac0e087 100644 --- a/editor/src/Components/Widgets.cpp +++ b/editor/src/Components/Widgets.cpp @@ -309,6 +309,60 @@ namespace nexo::editor { Widgets::drawColorEditor("##ColorEditor Spot light", &cameraComponent.clearColor, &colorPickerMode, &showColorPicker); } + void Widgets::drawCameraTargetProperties(components::PerspectiveCameraTarget &cameraTargetComponent) + { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); + if (ImGui::BeginTable("InspectorControllerTable", 2, + ImGuiTableFlags_SizingStretchProp)) + { + auto &app = getApp(); + auto &selector = Selector::get(); + ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + const std::vector &entities = app.m_coordinator->getAllEntitiesWith< + components::TransformComponent, + ecs::Exclude, + ecs::Exclude, + ecs::Exclude, + ecs::Exclude, + ecs::Exclude>(); + + EntityPropertiesComponents::drawRowDragFloat1("Mouse sensitivity", "", &cameraTargetComponent.mouseSensitivity, 0.1f); + EntityPropertiesComponents::drawRowDragFloat1("Distance", "", &cameraTargetComponent.distance, 0.1f); + Components::drawRowEntityDropdown( + "Target Entity", + cameraTargetComponent.targetEntity, entities, + [&app, &selector](ecs::Entity e) { + return selector.getUiHandle( + app.m_coordinator->getComponent(e).uuid, + std::to_string(e) + ); + }); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + + void Widgets::drawCameraControllerProperties(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); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + EntityPropertiesComponents::drawRowDragFloat1("Mouse sensitivity", "", &cameraControllerComponent.mouseSensitivity); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + bool Widgets::drawCameraCreator(const scene::SceneId sceneId, ImVec2 sceneViewportSize) { auto &app = getApp(); @@ -377,7 +431,107 @@ namespace nexo::editor { ImGui::TreePop(); } - ImGui::EndChild(); + if (app.m_coordinator->entityHasComponent(camera) && + EntityPropertiesComponents::drawHeader("##PerspectiveCameraTarget", "Camera Target Component")) + { + auto &cameraTargetComponent = app.m_coordinator->getComponent(camera); + Widgets::drawCameraTargetProperties(cameraTargetComponent); + ImGui::TreePop(); + } + + if (app.m_coordinator->entityHasComponent(camera) && + EntityPropertiesComponents::drawHeader("##PerspectiveCameraController", "Camera Controller Component")) + { + auto &cameraControllerComponent = app.m_coordinator->getComponent(camera); + Widgets::drawCameraControllerProperties(cameraControllerComponent); + ImGui::TreePop(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + // Add Component button + const float buttonWidth = inspectorWidth - 16; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 4)); + float centeredX = (inspectorWidth - buttonWidth) * 0.5f; + ImGui::SetCursorPosX(centeredX); + + // Static variables for state tracking + static bool showComponentSelector = false; + static float animProgress = 0.0f; + static float lastClickTime = 0.0f; + + // Button with arrow indicating state + std::string buttonText = "Add Component " + std::string(showComponentSelector ? ICON_FA_CHEVRON_UP : ICON_FA_CHEVRON_DOWN); + + if (Components::drawButton(buttonText.c_str(), ImVec2(buttonWidth, 0))) + { + showComponentSelector = !showComponentSelector; + if (showComponentSelector) { + lastClickTime = ImGui::GetTime(); + animProgress = 0.0f; + } + } + ImGui::PopStyleVar(); + + // Component selector with just two options + if (showComponentSelector) + { + // Animation calculation + const float animDuration = 0.25f; + float timeSinceClick = ImGui::GetTime() - lastClickTime; + animProgress = std::min(timeSinceClick / animDuration, 1.0f); + + // Simplified component grid with compact layout + const float maxGridHeight = 90.0f; + 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_FramePadding, ImVec2(8, 8)); // Better padding inside items + + ImGui::BeginChild("ComponentSelector", ImVec2(buttonWidth, currentHeight), 0, ImGuiWindowFlags_NoScrollbar); + + if (animProgress > 0.5f) + { + // Center elements horizontally with proper spacing + const float itemSize = 75.0f; + + // Draw component buttons side-by-side with controlled spacing + ImGui::BeginGroup(); + + if (!app.m_coordinator->entityHasComponent(camera) && + !app.m_coordinator->entityHasComponent(camera) && + Components::drawComponentButton("camera_target", ICON_FA_CAMERA, "Camera target", ImVec2(75.0f, 75.0f))) + { + components::PerspectiveCameraTarget cameraTarget{}; + app.m_coordinator->addComponent(camera, cameraTarget); + showComponentSelector = false; + } + ImGui::SameLine(); + if (!app.m_coordinator->entityHasComponent(camera) && + !app.m_coordinator->entityHasComponent(camera) && + Components::drawComponentButton("camera_controller", ICON_FA_GAMEPAD, "Camera Controller", ImVec2(75.0f, 75.0f))) + { + components::PerspectiveCameraController cameraController{}; + app.m_coordinator->addComponent(camera, cameraController); + showComponentSelector = false; + } + ImGui::EndGroup(); + } + + ImGui::EndChild(); + ImGui::PopStyleVar(3); + + // Reset animation if needed + if (!showComponentSelector && animProgress >= 1.0f) { + animProgress = 0.0f; + } + } + + ImGui::EndChild(); // End CameraInspector } ImGui::NextColumn(); // --- Right Side: Camera Preview --- diff --git a/editor/src/Components/Widgets.hpp b/editor/src/Components/Widgets.hpp index 771085164..7e7db5955 100644 --- a/editor/src/Components/Widgets.hpp +++ b/editor/src/Components/Widgets.hpp @@ -79,6 +79,8 @@ namespace nexo::editor { static void drawTransformProperties(components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler); static void drawCameraProperties(components::CameraComponent &cameraComponent); + static void drawCameraTargetProperties(components::PerspectiveCameraTarget &cameraTargetComponent); + static void drawCameraControllerProperties(components::PerspectiveCameraController &cameraControllerComponent); static bool drawCameraCreator(const scene::SceneId sceneId, ImVec2 sceneViewportSize); }; From e08f42af8ea150813cb9fd5556a2f524fd1e4b4b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 09:12:07 +0900 Subject: [PATCH 137/450] chore(document-windows): add source files --- editor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index cc9337893..842b86d5d 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -39,6 +39,7 @@ set(SRCS editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp editor/src/DocumentWindows/EntityProperties/CameraController.cpp + editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp editor/src/DocumentWindows/AssetManagerWindow.cpp ) From acc5f2bf2c67e866e743664d841a08a1628bf56d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 09:12:27 +0900 Subject: [PATCH 138/450] rm(document-windows): remove useless helper func --- engine/src/ecs/Access.hpp | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/engine/src/ecs/Access.hpp b/engine/src/ecs/Access.hpp index 99471d0b5..b198b4928 100644 --- a/engine/src/ecs/Access.hpp +++ b/engine/src/ecs/Access.hpp @@ -149,31 +149,4 @@ namespace nexo::ecs { */ template struct IsSingleton : std::bool_constant::value || IsWriteSingleton::value> {}; - - /** - * @brief Wrapper for component types that should exclude entities from a group - * - * Used to specify component types that, if present on an entity, will exclude - * that entity from the group even if it has all the required components. - * - * @tparam Components Component types that exclude entities - */ - template - struct ExcludeComponents { - using ComponentTypes = std::tuple; - }; - - /** - * @brief Helper function to create an Exclude object - * - * Similar to the get<> helper that already exists in your codebase, - * this provides a cleaner syntax for creating exclusions. - * - * @tparam Components Component types to exclude - * @return Exclude object - */ - template - ExcludeComponents exclude() { - return ExcludeComponents{}; - } } From d165ecac769a3612c93631af9e05c78e090eb978 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 09:31:51 +0900 Subject: [PATCH 139/450] feat(document-windows): add switch to camera --- editor/src/DocumentWindows/EditorScene.cpp | 16 ++++++------- editor/src/DocumentWindows/EditorScene.hpp | 13 +--------- .../src/DocumentWindows/SceneTreeWindow.cpp | 16 +++++++++++-- editor/src/WindowRegistry.hpp | 24 +++++++++++++++++++ 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 4573b4ed9..286600cea 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -61,7 +61,6 @@ namespace nexo::editor { m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, 0.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget); auto &cameraComponent = app.m_coordinator->getComponent(m_editorCamera); cameraComponent.render = true; - m_cameras.insert(static_cast(m_editorCamera)); app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_editorCamera)); components::PerspectiveCameraController controller; Application::m_coordinator->addComponent(static_cast(m_editorCamera), controller); @@ -112,13 +111,13 @@ namespace nexo::editor { // Will be implemeneted later } - void EditorScene::deleteCamera(const ecs::Entity cameraId) + void EditorScene::setCamera(ecs::Entity cameraId) { - if (static_cast(cameraId) == m_activeCamera) - m_activeCamera = -1; - m_cameras.erase(cameraId); - if (!m_cameras.empty()) - m_activeCamera = *m_cameras.begin(); + auto &app = getApp(); + auto &oldCameraComponent = app.m_coordinator->getComponent(m_activeCamera); + oldCameraComponent.active = false; + oldCameraComponent.render = false; + m_activeCamera = cameraId; } void EditorScene::renderToolbar() const @@ -250,7 +249,8 @@ namespace nexo::editor { return; if (m_focused && m_hovered) handleKeyEvents(); - runEngine(m_sceneId, RenderingType::FRAMEBUFFER, SceneType::EDITOR); + SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; + runEngine(m_sceneId, RenderingType::FRAMEBUFFER, sceneType); auto const &cameraComponent = Application::m_coordinator->getComponent(static_cast(m_activeCamera)); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsUsing() && m_focused) { diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp index 25f679cdb..2b2faddec 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene.hpp @@ -66,17 +66,7 @@ namespace nexo::editor { [[nodiscard]] scene::SceneId getSceneId() const {return m_sceneId;}; [[nodiscard]] ImVec2 getViewportSize() const {return m_viewSize;}; - - /** - * @brief Removes a camera from the scene and updates the active camera. - * - * Removes the specified camera entity from the collection. If the removed camera was the active one, - * it resets the active camera to an invalid state (-1) and, if any cameras remain, sets the active camera - * to the first available camera in the collection. - * - * @param cameraId The identifier of the camera entity to delete. - */ - void deleteCamera(ecs::Entity cameraId); + void setCamera(ecs::Entity cameraId); void setDefault() { m_defaultScene = true; }; @@ -90,7 +80,6 @@ namespace nexo::editor { int m_sceneId = -1; std::string m_sceneUuid; - std::set m_cameras; int m_activeCamera = -1; int m_editorCamera = -1; diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index b27761411..c1a308d3c 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -215,11 +215,23 @@ namespace nexo::editor { if (ImGui::MenuItem("Delete Camera")) { const auto &scenes = m_windowRegistry.getWindows(); - for (const auto &scene : scenes) - scene->deleteCamera(obj.data.entity); selector.unselectEntity(); app.deleteEntity(obj.data.entity); } + + if (ImGui::MenuItem("Switch to")) + { + auto &cameraComponent = app.m_coordinator->getComponent(obj.data.entity); + cameraComponent.render = true; + cameraComponent.active = true; + const auto &scenes = m_windowRegistry.getWindows(); + for (const auto &scene : scenes) { + if (scene->getSceneId() == obj.data.sceneProperties.sceneId) { + scene->setCamera(obj.data.entity); + break; + } + } + } } void SceneTreeWindow::entitySelected(const SceneObject &obj) const diff --git a/editor/src/WindowRegistry.hpp b/editor/src/WindowRegistry.hpp index 9435ff9e9..2c1d775f3 100644 --- a/editor/src/WindowRegistry.hpp +++ b/editor/src/WindowRegistry.hpp @@ -32,6 +32,11 @@ namespace nexo::editor { return std::static_pointer_cast(ptr); } + template + std::shared_ptr castWindow(std::shared_ptr& ptr) { + return std::static_pointer_cast(ptr); + } + class WindowRegistry { public: @@ -150,6 +155,25 @@ namespace nexo::editor { return std::ranges::transform_view(std::ranges::ref_view(it->second), caster); } + 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; + + 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. * From 0e99333d8de98fd54d4b3616bdfabafe6ba661fb Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 09:33:37 +0900 Subject: [PATCH 140/450] fix(document-windows): camera is now locked when using gizmo --- editor/src/DocumentWindows/EditorScene.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 286600cea..fb750df77 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -134,7 +134,7 @@ namespace nexo::editor { return; const ecs::Entity entity = selector.getSelectedEntity(); const auto &transformCameraComponent = coord->getComponent(m_activeCamera); - const auto &cameraComponent = coord->getComponent(m_activeCamera); + auto &cameraComponent = coord->getComponent(m_activeCamera); ImGuizmo::SetOrthographic(cameraComponent.type == components::CameraType::ORTHOGRAPHIC); ImGuizmo::SetDrawlist(); ImGuizmo::SetID(static_cast(entity)); @@ -162,10 +162,12 @@ namespace nexo::editor { if (ImGuizmo::IsUsing()) { + cameraComponent.active = false; transf->get().pos = translation; transf->get().quat = quaternion; transf->get().size = scale; - } + } else + cameraComponent.active = true; } void EditorScene::renderView() From f847de0d319162e4504818b9d93c0384b31380c8 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 16:44:39 +0900 Subject: [PATCH 141/450] feat(document-windows): add toolbar --- editor/src/Components/Components.cpp | 49 +++ editor/src/Components/Components.hpp | 14 + editor/src/Components/Widgets.cpp | 81 +++++ editor/src/Components/Widgets.hpp | 22 ++ editor/src/DocumentWindows/EditorScene.cpp | 380 ++++++++++++++++++++- editor/src/DocumentWindows/EditorScene.hpp | 35 +- 6 files changed, 574 insertions(+), 7 deletions(-) diff --git a/editor/src/Components/Components.cpp b/editor/src/Components/Components.cpp index ebd161711..611fcf1ac 100644 --- a/editor/src/Components/Components.cpp +++ b/editor/src/Components/Components.cpp @@ -420,6 +420,55 @@ namespace nexo::editor { } } + + bool Components::drawToolbarButton(const std::string& uniqueId, const std::string& icon, + const ImVec2& size, + const std::vector& gradientStops, + float gradientAngle, + const ImU32 borderColor, + const ImU32 borderColorHovered, + const ImU32 borderColorActive, + const ImU32 iconColor) + { + ImGui::PushID(uniqueId.c_str()); + + // Create invisible button for interaction + bool clicked = ImGui::InvisibleButton(("##" + uniqueId).c_str(), size); + + // Get button rectangle coordinates + ImVec2 p_min = ImGui::GetItemRectMin(); + ImVec2 p_max = ImGui::GetItemRectMax(); + + // Draw the gradient background + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawRectFilledLinearGradient(p_min, p_max, gradientAngle, gradientStops, drawList); + + // Draw the icon + //ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); + ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str()); + ImVec2 iconPos = ImVec2( + p_min.x + (p_max.x - p_min.x - iconSize.x) * 0.5f, + p_min.y + (p_max.y - p_min.y - iconSize.y) * 0.5f + ); + + // Draw the icon with specified color + drawList->AddText(iconPos, iconColor, icon.c_str()); + //ImGui::PopFont(); + + // Draw border with highlighting + ImU32 currentBorderColor = borderColor; + if (ImGui::IsItemHovered()) + currentBorderColor = borderColorHovered; + if (ImGui::IsItemActive()) + currentBorderColor = borderColorActive; + + const float borderThickness = 1.5f; + drawList->AddRect(p_min, p_max, currentBorderColor, 3.0f, 0, borderThickness); + + ImGui::PopID(); + return clicked; + } + bool Components::drawRowEntityDropdown(const std::string &label, ecs::Entity &targetEntity, const std::vector& entities, const std::function& getNameFunc) diff --git a/editor/src/Components/Components.hpp b/editor/src/Components/Components.hpp index f303606aa..082714990 100644 --- a/editor/src/Components/Components.hpp +++ b/editor/src/Components/Components.hpp @@ -155,6 +155,20 @@ namespace nexo::editor { */ static void drawRectFilledLinearGradient(const ImVec2& pMin, const ImVec2& pMax, float angle, std::vector stops, ImDrawList* drawList = nullptr); + static bool drawToolbarButton(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, + const ImU32 borderColor = IM_COL32(100, 100, 120, 255), + const ImU32 borderColorHovered = IM_COL32(150, 150, 200, 255), + const ImU32 borderColorActive = IM_COL32(200, 200, 255, 255), + const ImU32 iconColor = IM_COL32(255, 255, 255, 255) + ); + + static bool drawRowEntityDropdown(const std::string &label, ecs::Entity &targetEntity, const std::vector& entities, diff --git a/editor/src/Components/Widgets.cpp b/editor/src/Components/Widgets.cpp index e1ac0e087..f94ca9be0 100644 --- a/editor/src/Components/Widgets.cpp +++ b/editor/src/Components/Widgets.cpp @@ -588,4 +588,85 @@ namespace nexo::editor { } return false; } + + void Widgets::drawVerticalButtonDropDown(const ImVec2& buttonPos, const ImVec2 buttonSize, const std::vector &buttonProps, bool &closure, DropdownOrientation orientation) + { + constexpr float buttonSpacing = 5.0f; + constexpr float padding = 10.0f; + const auto &app = getApp(); + + // Calculate menu dimensions + const float menuWidth = buttonSize.x + padding; // Add padding + const float menuHeight = buttonProps.size() * buttonSize.y + + (buttonProps.size() - 1) * 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; + } + + // Adjust layout for horizontal orientations + bool isHorizontal = (orientation == DropdownOrientation::LEFT || + orientation == DropdownOrientation::RIGHT); + + // For horizontal layouts, swap width and height + ImVec2 menuSize = isHorizontal ? + ImVec2(menuHeight, buttonSize.y + 10.0f) : + ImVec2(menuWidth, menuHeight); + + // Create a separate overlay child window for the primitive buttons + ImGui::SetNextWindowPos(menuPos); + ImGui::SetNextWindowSize(menuSize); + ImGui::SetNextWindowBgAlpha(0.2f); + + // Style adjustments + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5.0f, buttonSpacing)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, + isHorizontal ? ImVec2(buttonSpacing, 0) : ImVec2(0, buttonSpacing)); + 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) + { + // Strangely here the clicked inside here does not seem to work + Components::drawToolbarButton(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)) + { + button.onClick(); + closure = false; + } + if (button.onRightClick && ImGui::IsItemClicked(ImGuiMouseButton_Right)) + { + button.onRightClick(); + } + if (!button.tooltip.empty() && ImGui::IsItemHovered()) + ImGui::SetTooltip(button.tooltip.c_str()); + } + } + // Check for clicks outside to close menu + if (ImGui::IsMouseClicked(0) && !ImGui::IsWindowHovered()) + { + closure = false; + } + ImGui::End(); + + ImGui::PopStyleVar(3); + } } diff --git a/editor/src/Components/Widgets.hpp b/editor/src/Components/Widgets.hpp index 7e7db5955..6791c0a85 100644 --- a/editor/src/Components/Widgets.hpp +++ b/editor/src/Components/Widgets.hpp @@ -17,6 +17,7 @@ #include #include +#include "Components/Components.hpp" #include "components/Camera.hpp" #include "components/Render3D.hpp" #include "components/Transform.hpp" @@ -83,5 +84,26 @@ namespace nexo::editor { static void drawCameraControllerProperties(components::PerspectiveCameraController &cameraControllerComponent); static bool drawCameraCreator(const scene::SceneId sceneId, ImVec2 sceneViewportSize); + + + struct ButtonProps { + std::string uniqueId; + std::string icon; + std::function onClick = nullptr; + std::function onRightClick = nullptr; + std::string tooltip = ""; + + 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 + }; + static void drawVerticalButtonDropDown(const ImVec2& buttonPos, const ImVec2 buttonSize, const std::vector &buttonProps, bool &closure, const DropdownOrientation orientation = DropdownOrientation::DOWN); }; } diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index fb750df77..3c5a3cfed 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -14,6 +14,7 @@ #include "EditorScene.hpp" +#include #include #include "ADocumentWindow.hpp" @@ -25,6 +26,8 @@ #include "Nexo.hpp" #include "Texture.hpp" #include "WindowRegistry.hpp" +#include "Components/Widgets.hpp" +#include "Components/EntityPropertiesComponents.hpp" #include "components/Camera.hpp" #include "components/Uuid.hpp" #include "math/Matrix.hpp" @@ -34,6 +37,7 @@ #include #include #include +#include #include namespace nexo::editor { @@ -120,12 +124,349 @@ namespace nexo::editor { m_activeCamera = cameraId; } - void EditorScene::renderToolbar() const + void EditorScene::initialToolbarSetup(const float buttonWidth, const float buttonHeight) { - // Empty for now, will add it later + ImVec2 toolbarPos = m_viewPosition; + toolbarPos.x += 10.0f; + + ImGui::SetCursorScreenPos(toolbarPos); + + ImVec2 toolbarSize = ImVec2(m_viewSize.x - buttonWidth, 50.0f); + + 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); + + ImGui::SetCursorPosY((ImGui::GetWindowHeight() - ImGui::GetFrameHeight()) * 0.5f); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 0)); + } + + bool EditorScene::renderToolbarButton(const std::string &uniqueId, const std::string &icon, const std::string &tooltip, const std::vector & gradientStop) + { + constexpr float buttonWidth = 35.0f; + constexpr float buttonHeight = 35.0f; + bool clicked = Components::drawToolbarButton(uniqueId, icon, ImVec2(buttonWidth, buttonHeight), gradientStop); + if (!tooltip.empty() && ImGui::IsItemHovered()) + ImGui::SetTooltip(tooltip.c_str()); + 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, -5.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); + }, + .tooltip = "Create Cube" + } + }; + Widgets::drawVerticalButtonDropDown(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 + } + // Snap on scale is kinda strange, the IsOver is not able to detect it, so for now we disable it + // { + // .uniqueId = "toggle_scale_snap", + // .icon = ICON_FA_EXPAND, + // .onClick = [this]() + // { + // this->m_snapScaleOn = !m_snapScaleOn; + // }, + // .onRightClick = [this]() + // { + // this->m_popupManager.openPopup("Snap settings popup", ImVec2(400, 180)); + // }, + // .tooltip = "Toggle Scale Snap", + // .buttonGradient = (m_snapScaleOn) ? m_selectedGradient : buttonGradient + // } + }; + Widgets::drawVerticalButtonDropDown(snapButtonPos, buttonSize, buttonProps, showSnapMenu); + } + + void EditorScene::snapSettingsPopup() + { + 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); + EntityPropertiesComponents::drawRowDragFloat3("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); + // Empty columns to match the first table's structure + ImGui::TableSetupColumn("##Empty1", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Empty2", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + EntityPropertiesComponents::drawRowDragFloat1("Rotate Snap", "", &this->m_angleSnap); + ImGui::EndTable(); + } + ImGui::Spacing(); + ImGui::Spacing(); + + float buttonWidth = 120.0f; + float windowWidth = ImGui::GetWindowSize().x; + ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); + + if (ImGui::Button("OK", ImVec2(buttonWidth, 0.0f))) + { + m_popupManager.closePopupInContext(); + } + ImGui::Unindent(10.0f); + ImGui::PopStyleVar(); + m_popupManager.closePopup(); + } + } + + void EditorScene::renderEditorCameraToolbarButton() + { + auto &app = getApp(); + auto &selector = Selector::get(); + bool editorMode = m_activeCamera == m_editorCamera; + if (m_activeCamera == m_editorCamera) { + if (renderToolbarButton("editor_camera", ICON_FA_CAMERA, "Edit Editor Camera Setting", m_buttonGradient)) { + const auto &uuidComponent = app.m_coordinator->getComponent(m_editorCamera); + selector.setSelectedEntity(uuidComponent.uuid, m_editorCamera); + } + } else { + if (renderToolbarButton("switch_back", ICON_FA_EXCHANGE, "Switch back to editor camera", m_buttonGradient)) { + auto &oldCameraComponent = app.m_coordinator->getComponent(m_activeCamera); + oldCameraComponent.active = false; + oldCameraComponent.render = false; + m_activeCamera = m_editorCamera; + auto &editorCameraComponent = app.m_coordinator->getComponent(m_activeCamera); + editorCameraComponent.render = true; + editorCameraComponent.active = true; + } + } + } + + bool EditorScene::renderGizmoModeToolbarButton(const bool showGizmoModeMenu, Widgets::ButtonProps &activeGizmoMode, Widgets::ButtonProps &inactiveGizmoMode) + { + static const Widgets::ButtonProps gizmoLocalModeButtonProps = {"local_coords", ICON_FA_CROSSHAIRS, [this]() {this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL;}, nullptr, "Local coordinates"}; + static const Widgets::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; + inactiveGizmoMode = gizmoWorldModeButtonProps; + } else { + activeGizmoMode = gizmoWorldModeButtonProps; + inactiveGizmoMode = gizmoLocalModeButtonProps; + } + return renderToolbarButton(activeGizmoMode.uniqueId, activeGizmoMode.icon, + activeGizmoMode.tooltip, showGizmoModeMenu ? m_selectedGradient : m_buttonGradient); + } + + void EditorScene::renderToolbar() + { + constexpr float buttonWidth = 35.0f; + constexpr float buttonHeight = 35.0f; + constexpr ImVec2 buttonSize{buttonWidth, buttonHeight}; + ImVec2 originalCursorPos = ImGui::GetCursorPos(); + + initialToolbarSetup(buttonWidth, buttonHeight); + + // -------------------------------- BUTTONS ------------------------------- + // -------- Add primitve button -------- + // This can open a submenu, see at the end + 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; + + ImGui::SameLine(); + + // -------- Editor camera settings / Switch back to editor camera button -------- + renderEditorCameraToolbarButton(); + + ImGui::SameLine(); + + // -------- Gizmo operation button -------- + static const Widgets::ButtonProps gizmoTranslateButtonProps = Widgets::ButtonProps{"translate", ICON_FA_ARROWS, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE;}, nullptr, "Translate"}; + static const Widgets::ButtonProps gizmoRotateButtonProps = Widgets::ButtonProps{"rotate", ICON_FA_REFRESH, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE;}, nullptr, "Rotate"}; + static const Widgets::ButtonProps gizmoScaleButtonProps = Widgets::ButtonProps{"scale", ICON_FA_EXPAND, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE;}, nullptr, "Scale"}; + static const Widgets::ButtonProps gizmoUniversalButtonProps = Widgets::ButtonProps{"universal", ICON_FA_ARROWS_ALT, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL;}, nullptr, "Universal"}; + std::vector gizmoButtons = { + gizmoTranslateButtonProps, + gizmoRotateButtonProps, + gizmoScaleButtonProps, + gizmoUniversalButtonProps + }; + + Widgets::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; + } + + 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; + + ImGui::SameLine(); + + // -------- Gizmo operation button -------- + Widgets::ButtonProps activeGizmoMode; + Widgets::ButtonProps inactiveGizmoMode; + ImVec2 changeGizmoModePos = ImGui::GetCursorScreenPos(); + static bool showGizmoModeMenu = false; + 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(); + 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; + + ImGui::SameLine(); + + // -------- Grid enabled button -------- + if (renderToolbarButton("grid_enabled", ICON_FA_TH_LARGE, "Enable / Disable grid", m_gridEnabled ? m_selectedGradient : m_buttonGradient)) + { + m_gridEnabled = !m_gridEnabled; + } + + ImGui::SameLine(); + + // -------- Snap to gridbutton -------- + if (renderToolbarButton("snap_to_grid", ICON_FA_TH, "Enable snapping to grid", m_snapToGrid ? m_selectedGradient : m_buttonGradient)) + { + m_snapToGrid = !m_snapToGrid; + } + + ImGui::SameLine(); + + // -------- Enable wireframe button -------- + if (renderToolbarButton("wireframe", ICON_FA_CUBE, "Enable / Disable wireframe", m_wireframeEnabled ? m_selectedGradient : m_buttonGradient)) + { + m_wireframeEnabled = !m_wireframeEnabled; + } + + ImGui::SameLine(); + + // -------- Play button button -------- + renderToolbarButton("play", ICON_FA_PLAY, "Play scene", m_buttonGradient); + + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::PopStyleColor(); + + // -------------------------------- SUB-MENUS ------------------------------- + // -------- Primitives sub-menus -------- + if (showPrimitiveMenu) + { + renderPrimitiveSubMenu(addPrimButtonPos, buttonSize, showPrimitiveMenu); + } + + // -------- Gizmo operation sub-menu -------- + if (showGizmoOpMenu) + { + Widgets::drawVerticalButtonDropDown(changeGizmoOpPos, buttonSize, gizmoButtons, showGizmoOpMenu); + } + + // -------- Gizmo mode sub-menu -------- + if (showGizmoModeMenu) + { + Widgets::drawVerticalButtonDropDown(changeGizmoModePos, buttonSize, {inactiveGizmoMode}, showGizmoModeMenu); + } + + // -------- Snap sub-menu -------- + if (showSnapToggleMenu) + { + renderSnapSubMenu(toggleSnapPos, buttonSize, showSnapToggleMenu); + } + + // -------- Snap settings popup -------- + snapSettingsPopup(); + + // IMPORTANT: Restore original cursor position so we don't affect layout + ImGui::SetCursorPos(originalCursorPos); } - void EditorScene::renderGizmo() const + void EditorScene::renderGizmo() { const auto &coord = nexo::Application::m_coordinator; auto const &selector = Selector::get(); @@ -148,11 +489,34 @@ namespace nexo::editor { glm::mat4 transformMatrix = glm::translate(glm::mat4(1.0f), transf->get().pos) * rotationMat * glm::scale(glm::mat4(1.0f), {transf->get().size.x, transf->get().size.y, transf->get().size.z}); + + static ImGuizmo::OPERATION lastOperation; + if (!ImGuizmo::IsUsing()) + { + if (ImGuizmo::IsOver(ImGuizmo::OPERATION::TRANSLATE)) + { + lastOperation = ImGuizmo::OPERATION::TRANSLATE; + } + else if (ImGuizmo::IsOver(ImGuizmo::OPERATION::ROTATE)) + { + lastOperation = ImGuizmo::OPERATION::ROTATE; + } + } + + + float *snap = nullptr; + if (m_snapTranslateOn && lastOperation == ImGuizmo::OPERATION::TRANSLATE) { + snap = &m_snapTranslate.x; + } else if (m_snapRotateOn && lastOperation == ImGuizmo::OPERATION::ROTATE) { + snap = &m_angleSnap; + } + ImGuizmo::Enable(true); ImGuizmo::Manipulate(glm::value_ptr(viewMatrix), glm::value_ptr(projectionMatrix), m_currentGizmoOperation, - ImGuizmo::MODE::WORLD, - glm::value_ptr(transformMatrix)); + m_currentGizmoMode, + glm::value_ptr(transformMatrix), + nullptr, snap); glm::vec3 translation(0); glm::vec3 scale(0); @@ -213,8 +577,12 @@ namespace nexo::editor { { firstDockSetup(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId)); auto &app = getApp(); + + // Add some spacing after the toolbar + ImGui::Dummy(ImVec2(0, 5)); m_viewPosition = ImGui::GetCursorScreenPos(); + m_focused = ImGui::IsWindowFocused(); m_hovered = ImGui::IsWindowHovered(); app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused); @@ -237,6 +605,8 @@ namespace nexo::editor { { renderView(); renderGizmo(); + renderToolbar(); + } } ImGui::End(); diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp index 2b2faddec..59b0ebc27 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene.hpp @@ -16,10 +16,14 @@ #include "ADocumentWindow.hpp" #include "IDocumentWindow.hpp" #include "core/scene/SceneManager.hpp" +#include "Components/Components.hpp" +#include "Components/Widgets.hpp" +#include "PopupManager.hpp" #include #include namespace nexo::editor { + class EditorScene : public ADocumentWindow { public: using ADocumentWindow::ADocumentWindow; @@ -77,12 +81,32 @@ namespace nexo::editor { 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_gridEnabled = false; + bool m_snapToGrid = false; + bool m_wireframeEnabled = false; int m_sceneId = -1; std::string m_sceneUuid; int m_activeCamera = -1; int m_editorCamera = -1; + PopupManager m_popupManager; + + const std::vector m_buttonGradient = { + {0.0f, IM_COL32(50, 50, 70, 230)}, + {1.0f, IM_COL32(30, 30, 45, 230)} + }; + + // 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)} + }; + /** * @brief Sets the main scene window's view size. * @@ -102,7 +126,14 @@ namespace nexo::editor { * 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() const; + void renderToolbar(); + void initialToolbarSetup(const float buttonWidth, const float buttonHeight); + void renderEditorCameraToolbarButton(); + bool renderGizmoModeToolbarButton(const bool showGizmoModeMenu, Widgets::ButtonProps &activeGizmoMode, Widgets::ButtonProps &inactiveGizmoMode); + void renderPrimitiveSubMenu(const ImVec2 &primitiveButtonPos, const ImVec2 &buttonSize, bool &showPrimitiveMenu); + void renderSnapSubMenu(const ImVec2 &snapButtonPos, const ImVec2 &buttonSize, bool &showSnapMenu); + void snapSettingsPopup(); + bool renderToolbarButton(const std::string &uniqueId, const std::string &icon, const std::string &tooltip, const std::vector & gradientStop); /** * @brief Renders the transformation gizmo for the selected entity. @@ -115,7 +146,7 @@ namespace nexo::editor { * * If the gizmo is actively manipulated, the entity's transform component is updated with the new values. */ - void renderGizmo() const; + void renderGizmo(); void renderView(); }; } From 2bf3d7ca63083c8239e3c4fcfce2d856c9bf8a0c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 18 Apr 2025 23:48:30 +0900 Subject: [PATCH 142/450] refactor(document-windows): add ImNexo namespace for imgui ui related functions --- editor/CMakeLists.txt | 9 +- editor/src/Components/Components.cpp | 556 --------------- editor/src/Components/Components.hpp | 177 ----- .../Components/EntityPropertiesComponents.cpp | 305 -------- .../Components/EntityPropertiesComponents.hpp | 202 ------ editor/src/Components/Widgets.cpp | 672 ------------------ editor/src/Components/Widgets.hpp | 109 --- editor/src/DockingRegistry.hpp | 23 + editor/src/DocumentWindows/ConsoleWindow.cpp | 5 +- editor/src/DocumentWindows/ConsoleWindow.hpp | 84 ++- editor/src/DocumentWindows/EditorScene.cpp | 56 +- editor/src/DocumentWindows/EditorScene.hpp | 141 +++- .../EntityProperties/AEntityProperty.hpp | 24 +- .../EntityProperties/AmbientLightProperty.cpp | 7 +- .../EntityProperties/CameraController.cpp | 22 +- .../EntityProperties/CameraProperty.cpp | 70 +- .../EntityProperties/CameraTarget.cpp | 142 +--- .../DirectionalLightProperty.cpp | 38 +- .../EntityProperties/PointLightProperty.cpp | 9 +- .../EntityProperties/RenderProperty.cpp | 22 +- .../EntityProperties/SpotLightProperty.cpp | 19 +- .../EntityProperties/TransformProperty.cpp | 44 +- .../src/DocumentWindows/InspectorWindow.cpp | 7 +- .../src/DocumentWindows/InspectorWindow.hpp | 4 + .../src/DocumentWindows/MaterialInspector.cpp | 4 +- .../src/DocumentWindows/MaterialInspector.hpp | 4 + editor/src/DocumentWindows/PopupManager.cpp | 6 +- editor/src/DocumentWindows/PopupManager.hpp | 33 +- .../src/DocumentWindows/SceneTreeWindow.cpp | 18 +- .../src/DocumentWindows/SceneTreeWindow.hpp | 86 ++- editor/src/Editor.cpp | 6 +- editor/src/ImNexo/Components.cpp | 520 ++++++++++++++ editor/src/ImNexo/Components.hpp | 210 ++++++ editor/src/ImNexo/Elements.cpp | 400 +++++++++++ editor/src/ImNexo/Elements.hpp | 287 ++++++++ editor/src/ImNexo/EntityProperties.cpp | 178 +++++ editor/src/ImNexo/EntityProperties.hpp | 80 +++ editor/src/ImNexo/Guard.hpp | 192 +++++ editor/src/ImNexo/Panels.cpp | 343 +++++++++ editor/src/ImNexo/Panels.hpp | 54 ++ editor/src/ImNexo/Utils.cpp | 98 +++ editor/src/ImNexo/Utils.hpp | 60 ++ editor/src/ImNexo/Widgets.cpp | 169 +++++ editor/src/ImNexo/Widgets.hpp | 103 +++ editor/src/WindowRegistry.hpp | 51 ++ editor/src/utils/Config.hpp | 23 + editor/src/utils/FileSystem.hpp | 21 + editor/src/utils/ScenePreview.cpp | 16 - 48 files changed, 3270 insertions(+), 2439 deletions(-) delete mode 100644 editor/src/Components/Components.cpp delete mode 100644 editor/src/Components/Components.hpp delete mode 100644 editor/src/Components/EntityPropertiesComponents.cpp delete mode 100644 editor/src/Components/EntityPropertiesComponents.hpp delete mode 100644 editor/src/Components/Widgets.cpp delete mode 100644 editor/src/Components/Widgets.hpp create mode 100644 editor/src/ImNexo/Components.cpp create mode 100644 editor/src/ImNexo/Components.hpp create mode 100644 editor/src/ImNexo/Elements.cpp create mode 100644 editor/src/ImNexo/Elements.hpp create mode 100644 editor/src/ImNexo/EntityProperties.cpp create mode 100644 editor/src/ImNexo/EntityProperties.hpp create mode 100644 editor/src/ImNexo/Guard.hpp create mode 100644 editor/src/ImNexo/Panels.cpp create mode 100644 editor/src/ImNexo/Panels.hpp create mode 100644 editor/src/ImNexo/Utils.cpp create mode 100644 editor/src/ImNexo/Utils.hpp create mode 100644 editor/src/ImNexo/Widgets.cpp create mode 100644 editor/src/ImNexo/Widgets.hpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 842b86d5d..5741a5354 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -15,9 +15,12 @@ set(SRCS editor/src/backends/ImGuiBackend.cpp editor/src/backends/opengl/openglImGuiBackend.cpp editor/src/context/Selector.cpp - editor/src/Components/Components.cpp - editor/src/Components/EntityPropertiesComponents.cpp - editor/src/Components/Widgets.cpp + editor/src/ImNexo/EntityProperties.cpp + editor/src/ImNexo/Components.cpp + editor/src/ImNexo/Elements.cpp + editor/src/ImNexo/Panels.cpp + editor/src/ImNexo/Utils.cpp + editor/src/ImNexo/Widgets.cpp editor/src/utils/ScenePreview.cpp editor/src/utils/Config.cpp editor/src/utils/String.cpp diff --git a/editor/src/Components/Components.cpp b/editor/src/Components/Components.cpp deleted file mode 100644 index 611fcf1ac..000000000 --- a/editor/src/Components/Components.cpp +++ /dev/null @@ -1,556 +0,0 @@ -//// Components.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: 17/02/2025 -// Description: Source file for the utilitary ImGui functions -// -/////////////////////////////////////////////////////////////////////////////// - -#include "Components.hpp" - -#include -#include -#include -#include - -namespace nexo::editor { - - bool Components::drawButton( - const std::string &label, - const ImVec2 &size, - const ImU32 bg, - const ImU32 bgHovered, - const ImU32 bgActive, const ImU32 txtColor - ) { - if (bg != 0) ImGui::PushStyleColor(ImGuiCol_Button, bg); - if (bgHovered != 0) ImGui::PushStyleColor(ImGuiCol_ButtonHovered, bgHovered); - if (bgActive != 0) ImGui::PushStyleColor(ImGuiCol_ButtonActive, bgActive); - if (txtColor != 0) ImGui::PushStyleColor(ImGuiCol_Text, txtColor); - - const bool clicked = ImGui::Button(label.c_str(), size); - - const int popCount = (bg != 0) + (bgHovered != 0) + (bgActive != 0) + (txtColor != 0); - ImGui::PopStyleColor(popCount); - return clicked; - } - - bool Components::drawComponentButton( - const std::string &uniqueId, - const std::string &icon, - const std::string &label, - const ImVec2 &itemSize - ) { - ImGui::PushID(uniqueId.c_str()); - std::string invisButtonLabel = "##" + uniqueId; - // Create invisible button - if (ImGui::InvisibleButton(invisButtonLabel.c_str(), itemSize)) - { - ImGui::PopID(); - return true; - // app.m_coordinator->addComponent(camera, components::PerspectiveCameraTarget{}); - //showComponentSelector = false; - } - - // Draw the background (like a button would have) - ImVec2 p0 = ImGui::GetItemRectMin(); - ImVec2 p1 = ImGui::GetItemRectMax(); - ImGui::GetWindowDrawList()->AddRectFilled( - p0, p1, - ImGui::GetColorU32(ImGui::IsItemHovered() ? ImGuiCol_ButtonHovered : ImGuiCol_Button), - ImGui::GetStyle().FrameRounding - ); - - // Increase icon size even further (2x larger) - float iconScale = 1.5f; - ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); - ImGui::SetWindowFontScale(iconScale); - ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str()); - ImGui::SetWindowFontScale(1.0f); - ImGui::PopFont(); - - // Draw the icon centered - ImVec2 iconPos = ImVec2( - p0.x + (p1.x - p0.x - iconSize.x) * 0.45f, - p0.y + (p1.y - p0.y) * 0.25f - iconSize.y * 0.5f // Position at 35% from top - ); - ImGui::GetWindowDrawList()->AddText( - ImGui::GetFont(), ImGui::GetFontSize() * iconScale, - iconPos, ImGui::GetColorU32(ImGuiCol_Text), - icon.c_str() - ); - - // Implement text wrapping for the label - float wrapWidth = p1.x - p0.x - 10.0f; // Available width with padding - float textHeight = ImGui::GetFontSize(); - float textY = p0.y + (p1.y - p0.y) * 0.60f; // Position at 75% from top - - // Calculate if we need to wrap (if text is wider than button) - ImVec2 textSize = ImGui::CalcTextSize(label.c_str()); - if (textSize.x > wrapWidth) { - // Find where to split the text - size_t splitPos = label.find(" "); - - if (splitPos != std::string::npos) { - // Split at space - std::string line1 = label.substr(0, splitPos); - std::string line2 = label.substr(splitPos + 1); - - // Draw first line - ImVec2 line1Pos = ImVec2( - p0.x + (p1.x - p0.x - ImGui::CalcTextSize(line1.c_str()).x) * 0.5f, - textY - textHeight * 0.5f - ); - ImGui::GetWindowDrawList()->AddText(line1Pos, ImGui::GetColorU32(ImGuiCol_Text), line1.c_str()); - - // Draw second line - ImVec2 line2Pos = ImVec2( - p0.x + (p1.x - p0.x - ImGui::CalcTextSize(line2.c_str()).x) * 0.5f, - textY + textHeight * 0.5f - ); - ImGui::GetWindowDrawList()->AddText(line2Pos, ImGui::GetColorU32(ImGuiCol_Text), line2.c_str()); - } else { - // No space to split, just draw the text with possible cutoff - ImVec2 textPos = ImVec2( - p0.x + (p1.x - p0.x - textSize.x) * 0.5f, - textY - ); - ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGuiCol_Text), label.c_str()); - } - } else { - // No wrapping needed, just center the text - ImVec2 textPos = ImVec2( - p0.x + (p1.x - p0.x - textSize.x) * 0.5f, - textY - ); - ImGui::GetWindowDrawList()->AddText(textPos, ImGui::GetColorU32(ImGuiCol_Text), label.c_str()); - } - - // Add border when hovered or active - if (ImGui::IsItemHovered() || ImGui::IsItemActive()) - ImGui::GetWindowDrawList()->AddRect(p0, p1, ImGui::GetColorU32(ImGuiCol_ButtonActive), ImGui::GetStyle().FrameRounding); - - ImGui::PopID(); - return false; - } - - void Components::drawButtonBorder( - const ImU32 borderColor, - const ImU32 borderColorHovered, - const ImU32 borderColorActive, - const float rounding, - const ImDrawFlags flags, - const float thickness - ) { - const ImVec2 p_min = ImGui::GetItemRectMin(); - const ImVec2 p_max = ImGui::GetItemRectMax(); - ImU32 color = borderColor ? borderColor : ImGui::GetColorU32(ImGuiCol_Button); - if (ImGui::IsItemHovered()) - color = borderColorHovered ? borderColorHovered : ImGui::GetColorU32(ImGuiCol_ButtonHovered); - if (ImGui::IsItemActive()) - color = borderColorActive ? borderColorActive : ImGui::GetColorU32(ImGuiCol_ButtonActive); - - ImGui::GetWindowDrawList()->AddRect(p_min, p_max, color, rounding, flags, thickness); - } - - void Components::drawButtonInnerBorder( - const ImU32 borderColor, - const ImU32 borderColorHovered, - const ImU32 borderColorActive, - const float rounding, - const ImDrawFlags flags, - const float thickness - ) { - ImVec2 p_min = ImGui::GetItemRectMin(); - ImVec2 p_max = ImGui::GetItemRectMax(); - ImU32 color = borderColor ? borderColor : ImGui::GetColorU32(ImGuiCol_Button); - if (ImGui::IsItemHovered()) - color = borderColorHovered ? borderColorHovered : ImGui::GetColorU32(ImGuiCol_ButtonHovered); - if (ImGui::IsItemActive()) - color = borderColorActive ? borderColorActive : ImGui::GetColorU32(ImGuiCol_ButtonActive); - - ImGui::GetWindowDrawList()->AddRect( - ImVec2(p_min.x + thickness, p_min.y + thickness), - ImVec2(p_max.x - thickness, p_max.y - thickness), - color, rounding, flags, thickness); - } - - bool Components::drawDragFloat( - 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 - ) { - if (bg) ImGui::PushStyleColor(ImGuiCol_FrameBg, bg); - if (bgHovered) ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, bgHovered); - if (bgActive) ImGui::PushStyleColor(ImGuiCol_FrameBgActive, bgActive); - if (textColor) ImGui::PushStyleColor(ImGuiCol_Text, textColor); - const bool clicked = ImGui::DragFloat(label.c_str(), values, speed, min, max, format.c_str()); - - const int popCount = (bg != 0) + (bgHovered != 0) + (bgActive != 0) + (textColor != 0); - ImGui::PopStyleColor(popCount); - return clicked; - } - - void Components::drawColorButton(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); - if (ImGui::ColorButton(label.c_str(), - color, - flags, - calculatedSize) && clicked) - { - *clicked = !*clicked; - } - Components::drawButtonBorder(ImGui::GetColorU32(ImGuiCol_Button), ImGui::GetColorU32(ImGuiCol_ButtonHovered), ImGui::GetColorU32(ImGuiCol_ButtonActive), borderThickness); - } - - void Components::drawCustomSeparatorText(const std::string &text, const float textPadding, const float leftSpacing, const float thickness, ImU32 lineColor, ImU32 textColor) - { - const ImVec2 pos = ImGui::GetCursorScreenPos(); - const float availWidth = ImGui::GetContentRegionAvail().x; - const float textWidth = ImGui::CalcTextSize(text.c_str()).x; - - // Compute the length of each line. Clamp to zero if the region is too small. - float lineWidth = (availWidth - textWidth - 2 * textPadding) * leftSpacing; - if (lineWidth < 0.0f) - lineWidth = 0.0f; - - // Compute Y coordinate to draw lines so they align with the text center. - const float lineY = pos.y + ImGui::GetTextLineHeight() * 0.5f; - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - const ImVec2 lineStart(pos.x, lineY); - const ImVec2 lineEnd(pos.x + lineWidth, lineY); - draw_list->AddLine(lineStart, lineEnd, lineColor, thickness); - - const ImVec2 textPos(pos.x + lineWidth + textPadding, pos.y); - draw_list->AddText(textPos, textColor, text.c_str()); - - const ImVec2 rightLineStart(pos.x + lineWidth + textPadding + textWidth + textPadding, lineY); - const ImVec2 rightLineEnd(pos.x + availWidth, lineY); - draw_list->AddLine(rightLineStart, rightLineEnd, lineColor, thickness); - - ImGui::Dummy(ImVec2(0, ImGui::GetTextLineHeight())); - } - - /** - * @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). - */ - static ImU32 imLerpColor(const ImU32 colA, const ImU32 colB, const float t) - { - const unsigned char a0 = (colA >> 24) & 0xFF, r0 = (colA >> 16) & 0xFF, g0 = (colA >> 8) & 0xFF, b0 = colA & 0xFF; - const unsigned char a1 = (colB >> 24) & 0xFF, r1 = (colB >> 16) & 0xFF, g1 = (colB >> 8) & 0xFF, 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)); - return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); - } - - /** - * @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. - */ - static void clipPolygonWithLine(const std::vector& poly, const ImVec2& normal, float offset, std::vector& outPoly) - { - outPoly.clear(); - const auto count = poly.size(); - outPoly.reserve(count * 2); // Preallocate space for the output polygon (prepare worst case) - 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); - // if the edge spans the boundary, compute intersection - if ((da >= 0 && db < 0) || (da < 0 && db >= 0)) { - const float t = da / (da - db); - ImVec2 inter; - inter.x = a.x + t * (b.x - a.x); - inter.y = a.y + t * (b.y - a.y); - outPoly.push_back(inter); - } - } - } - - /** - * @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. - */ - static void fillConvexPolygon(ImDrawList* drawList, const std::vector& poly, const std::vector& polyColors) - { - if (poly.size() < 3) - return; - const auto count = static_cast(poly.size()); - drawList->PrimReserve((count - 2) * 3, count); - // Use the first vertex as pivot. - for (int i = 1; i < count - 1; i++) { - const auto currentIdx = drawList->_VtxCurrentIdx; - drawList->PrimWriteIdx(static_cast(currentIdx)); - drawList->PrimWriteIdx(static_cast(currentIdx + i)); - drawList->PrimWriteIdx(static_cast(currentIdx + i + 1)); - } - // Write vertices with their computed colors. - for (int i = 0; i < count; i++) { - // For a vertex, we determine its position t between the segment boundaries later. - // Here we assume the provided poly_colors already correspond vertex-by-vertex. - drawList->PrimWriteVtx(poly[i], drawList->_Data->TexUvWhitePixel, polyColors[i]); - } - } - - - void Components::drawRectFilledLinearGradient(const ImVec2& pMin, const ImVec2& pMax, float angle, - std::vector stops, ImDrawList* drawList) - { - if (!drawList) - drawList = ImGui::GetWindowDrawList(); - - // Check if we have at least two stops. - // If not, we can't create a gradient. - if (stops.size() < 2) - return; - - angle -= 90.0f; // rotate 90 degrees to match the CSS gradients rotations - - // Convert angle from degrees to radians. Also keep it in range of radians - // [0, 2*PI) for consistency. - angle = fmodf(angle, 360.0f); - if (angle < 0.0f) - angle += 360.0f; - angle = angle * std::numbers::pi_v / 180.0f; - - const auto gradDir = ImVec2(cosf(angle), sinf(angle)); - - // Define rectangle polygon (clockwise order). - const std::vector rectPoly = { pMin, ImVec2(pMax.x, pMin.y), pMax, ImVec2(pMin.x, pMax.y) }; - - // Compute projection range (d_min, d_max) for the rectangle. - float d_min = std::numeric_limits::max(); - float d_max = -std::numeric_limits::max(); - for (auto const& v : rectPoly) { - const float d = ImDot(v, gradDir); - if (d < d_min) d_min = d; - if (d > d_max) d_max = d; - } - - // sanitize stops - float stop_max = 0.0f; - for (auto& [pos, color] : stops) { - (void)color; // ignore color for now - // Clamp stop position to [0.0f, 1.0f] - if (pos < 0.0f) pos = 0.0f; - if (pos > 1.0f) pos = 1.0f; - - // Clamp stop position to [stop_max, 1.0f] - if (pos < stop_max) { - pos = stop_max; - } else { - stop_max = pos; - } - } - - // if first stop does not start at 0.0f, we need to add a stop at 0.0f - if (stops[0].pos > 0.0f) { - stops.insert(stops.begin(), { 0.0f, stops[0].color }); - } - // if last stop does not end at 1.0f, we need to add a stop at 1.0f - if (stops[stops.size() - 1].pos < 1.0f) { - stops.push_back({ 1.0f, stops[stops.size() - 1].color }); - } - - // For each segment defined by consecutive stops: - for (long i = static_cast(stops.size()) - 1; i > 0; i--) { - const long posStart = i - 1; - const long posEnd = i; - // Compute threshold projections for the current segment. - const float segStart = d_min + stops[posStart].pos * (d_max - d_min); - const float segEnd = d_min + stops[posEnd].pos * (d_max - d_min); - - // Start with the whole rectangle. - std::vector segPoly = rectPoly; - std::vector tempPoly; - // Clip against lower boundary: d >= seg_start - clipPolygonWithLine(segPoly, gradDir, segStart, tempPoly); - segPoly = tempPoly; // copy result - // Clip against upper boundary: d <= seg_end - // To clip with an upper-bound, invert the normal. - clipPolygonWithLine(segPoly, ImVec2(-gradDir.x, -gradDir.y), -segEnd, tempPoly); - segPoly = tempPoly; - - if (segPoly.empty()) - continue; - - // Now, compute per-vertex colors for the segment polygon. - std::vector polyColors; - polyColors.reserve(segPoly.size()); - for (const ImVec2& v : segPoly) { - // Compute projection for the vertex. - const float d = ImDot(v, gradDir); - // Map projection to [0,1] relative to current segment boundaries. - const float t = (d - segStart) / (segEnd - segStart); - // Interpolate the color between the two stops. - polyColors.push_back(imLerpColor(stops[posStart].color, stops[posEnd].color, t)); - } - - // Draw the filled and colored polygon. - fillConvexPolygon(drawList, segPoly, polyColors); - } - } - - - bool Components::drawToolbarButton(const std::string& uniqueId, const std::string& icon, - const ImVec2& size, - const std::vector& gradientStops, - float gradientAngle, - const ImU32 borderColor, - const ImU32 borderColorHovered, - const ImU32 borderColorActive, - const ImU32 iconColor) - { - ImGui::PushID(uniqueId.c_str()); - - // Create invisible button for interaction - bool clicked = ImGui::InvisibleButton(("##" + uniqueId).c_str(), size); - - // Get button rectangle coordinates - ImVec2 p_min = ImGui::GetItemRectMin(); - ImVec2 p_max = ImGui::GetItemRectMax(); - - // Draw the gradient background - ImDrawList* drawList = ImGui::GetWindowDrawList(); - drawRectFilledLinearGradient(p_min, p_max, gradientAngle, gradientStops, drawList); - - // Draw the icon - //ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); - ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str()); - ImVec2 iconPos = ImVec2( - p_min.x + (p_max.x - p_min.x - iconSize.x) * 0.5f, - p_min.y + (p_max.y - p_min.y - iconSize.y) * 0.5f - ); - - // Draw the icon with specified color - drawList->AddText(iconPos, iconColor, icon.c_str()); - //ImGui::PopFont(); - - // Draw border with highlighting - ImU32 currentBorderColor = borderColor; - if (ImGui::IsItemHovered()) - currentBorderColor = borderColorHovered; - if (ImGui::IsItemActive()) - currentBorderColor = borderColorActive; - - const float borderThickness = 1.5f; - drawList->AddRect(p_min, p_max, currentBorderColor, 3.0f, 0, borderThickness); - - ImGui::PopID(); - return clicked; - } - - bool Components::drawRowEntityDropdown(const std::string &label, ecs::Entity &targetEntity, - const std::vector& entities, - const std::function& getNameFunc) - { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(label.c_str()); - - ImGui::TableNextColumn(); - - // Create a unique ID for this widget - ImGui::PushID(label.c_str()); - - //ImGui::SetNextItemWidth(-FLT_MIN); // Use all available width - - bool changed = false; - - // Build entity-name mapping - static std::vector> entityNamePairs; - static ecs::Entity lastTargetEntity = 0; - static std::vector lastEntities; - - // Only rebuild the mapping if entities list changed or target entity changed - bool needRebuild = lastTargetEntity != targetEntity || - lastEntities.size() != entities.size(); - - if (!needRebuild) { - for (size_t i = 0; i < entities.size() && !needRebuild; i++) { - needRebuild = lastEntities[i] != entities[i]; - } - } - - if (needRebuild) { - entityNamePairs.clear(); - entityNamePairs.reserve(entities.size()); - lastEntities = entities; - lastTargetEntity = targetEntity; - - for (ecs::Entity entity : entities) { - std::string name = getNameFunc(entity); - entityNamePairs.emplace_back(entity, name); - } - } - - // 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; - } - } - - // Add a "None" option if we want to allow null selection - std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; - - // Draw the combo box - if (ImGui::BeginCombo("##entity_dropdown", currentItemName.c_str())) - { - // Optional: Add a "None" option for clearing the target - if (ImGui::Selectable("None", targetEntity == ecs::MAX_ENTITIES)) { - targetEntity = ecs::MAX_ENTITIES; - changed = true; - } - - 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; - } - - if (isSelected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - - ImGui::PopID(); - return changed; - } -} diff --git a/editor/src/Components/Components.hpp b/editor/src/Components/Components.hpp deleted file mode 100644 index 082714990..000000000 --- a/editor/src/Components/Components.hpp +++ /dev/null @@ -1,177 +0,0 @@ -//// Components.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: Mehdy MORVAN -// Date: 17/02/2025 -// Description: Header file for the utilitary ImGui functions -// -/////////////////////////////////////////////////////////////////////////////// -#pragma once - -#include -#include -#include -#include - -#include "ecs/Coordinator.hpp" - -namespace nexo::editor { - - /** - * @brief A collection of utility functions for custom ImGui components. - * - * This class provides helper functions to draw custom buttons, drag floats, - * color buttons, and separators with text. - */ - class Components { - public: - /** - * @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. - */ - static bool drawButton(const std::string &label, const ImVec2& size = ImVec2(0, 0), ImU32 bg = 0, ImU32 bgHovered = 0, ImU32 bgActive = 0, ImU32 txtColor = 0); - - static bool drawComponentButton(const std::string &uniqueId, const std::string &icon, const std::string &label, const ImVec2 &itemSize); - - /** - * @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. - */ - static void drawButtonBorder(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. - */ - static void drawButtonInnerBorder(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. - */ - static bool drawDragFloat(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 an icon button with custom style colors. - * - * Similar to drawButton, but intended for icon-only buttons. - * - * @param label The label for the button. - * @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 (icon) color. - * @return true if the button was clicked; false otherwise. - */ - static bool drawIconButton(const std::string &label, ImVec2 size = ImVec2(0, 0), ImU32 bg = 0, ImU32 bgHovered = 0, ImU32 bgActive = 0, ImU32 txtColor = 0); - - /** - * @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. - */ - static void drawColorButton(const std::string &label, ImVec2 size, ImVec4 color, bool *clicked = nullptr, ImGuiColorEditFlags flags = ImGuiColorEditFlags_None); - - /** - * @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. - */ - static void drawCustomSeparatorText(const std::string &text, float textPadding, float leftSpacing, float thickness, ImU32 lineColor, ImU32 textColor); - - 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. - * @param pMin Upper left corner position of the rectangle - * @param pMax Lower right corner position of the rectangle - * @param angle Angle of the gradient in degrees (0.0f = down, 90.0f = right, 180.0f = up, 270.0f = left) - * @param stops Vector of gradient stops, each defined by a position (0.0f to 1.0f) and a color - */ - static void drawRectFilledLinearGradient(const ImVec2& pMin, const ImVec2& pMax, float angle, std::vector stops, ImDrawList* drawList = nullptr); - - static bool drawToolbarButton(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, - const ImU32 borderColor = IM_COL32(100, 100, 120, 255), - const ImU32 borderColorHovered = IM_COL32(150, 150, 200, 255), - const ImU32 borderColorActive = IM_COL32(200, 200, 255, 255), - const ImU32 iconColor = IM_COL32(255, 255, 255, 255) - ); - - - - static bool drawRowEntityDropdown(const std::string &label, ecs::Entity &targetEntity, - const std::vector& entities, - const std::function& getNameFunc); - }; -} diff --git a/editor/src/Components/EntityPropertiesComponents.cpp b/editor/src/Components/EntityPropertiesComponents.cpp deleted file mode 100644 index 0275b78a7..000000000 --- a/editor/src/Components/EntityPropertiesComponents.cpp +++ /dev/null @@ -1,305 +0,0 @@ -//// EntityPropertiesComponents.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: 22/02/2025 -// Description: Source file for the entity properties components -// -/////////////////////////////////////////////////////////////////////////////// - -#include "EntityPropertiesComponents.hpp" -#include "Components.hpp" - -namespace nexo::editor { - - bool EntityPropertiesComponents::drawHeader(const std::string &label, std::string_view headerText) - { - ImGuiStyle& style = ImGui::GetStyle(); - ImGui::PushStyleVar( - ImGuiStyleVar_FramePadding, - ImVec2(style.FramePadding.x, 3.0f) - ); - - bool open = ImGui::TreeNodeEx( - label.c_str(), - ImGuiTreeNodeFlags_DefaultOpen | - ImGuiTreeNodeFlags_Framed | - ImGuiTreeNodeFlags_AllowItemOverlap | - ImGuiTreeNodeFlags_SpanAvailWidth - ); - - ImGui::PopStyleVar(); - - // We retrieve the bounding box of the tree node - ImVec2 bbMin = ImGui::GetItemRectMin(); - ImVec2 bbMax = ImGui::GetItemRectMax(); - ImVec2 bbSize = ImVec2(bbMax.x - bbMin.x, bbMax.y - bbMin.y); - - ImVec2 textSize = ImGui::CalcTextSize(headerText.data()); - - // We manually compute the absolute screen position - ImVec2 textPos; - textPos.x = bbMin.x + (bbSize.x - textSize.x) * 0.5f; - textPos.y = bbMin.y + (bbSize.y - textSize.y) * 0.5f; - - // Draw directly on top of it - ImDrawList* dl = ImGui::GetWindowDrawList(); - dl->AddText( - ImGui::GetFont(), - ImGui::GetFontSize(), - textPos, - ImGui::GetColorU32(ImGuiCol_Text), - headerText.data() - ); - - return open; - } - - - - void EntityPropertiesComponents::drawRowLabel(const ChannelLabel &rowLabel) - { - ImGui::TableNextColumn(); - if (rowLabel.fixedWidth != -1.0f) - { - //TODO: Implement now fixed width row label - //float fixedCellWidth = rowLabel.fixedWidth; - //ImVec2 textSize = ImGui::CalcTextSize(rowLabel.label.c_str()); - //float offsetX = (fixedCellWidth - textSize.x) * 0.5f; - //float rowHeight = ImGui::GetTextLineHeightWithSpacing(); - //float offsetY = (rowHeight - textSize.y) * 0.5f; - //ImVec2 cellPos = ImGui::GetCursorPos(); - } - //ImGui::SetWindowFontScale(1.11f); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(rowLabel.label.c_str()); - //ImGui::SetWindowFontScale(1.0f); - } - - bool EntityPropertiesComponents::drawRowDragFloat(const Channels &channels) - { - bool clicked = false; - for (unsigned int i = 0; i < channels.count; ++i) - { - ImGui::TableNextColumn(); - if (!channels.badges[i].label.empty()) - { - const auto &[label, size, bg, bgHovered, bgActive, txtColor] = channels.badges[i]; - Components::drawButton(label, size, bg, bgHovered, bgActive, txtColor); - } - ImGui::SameLine(0, 2); - const auto &[label, value, speed, min, max, bg, bgHovered, bgActive, textColor, format] = channels.sliders[i]; - clicked = Components::drawDragFloat( - label, - value, - speed, - min, - max, - format, - bg, - bgHovered, - bgActive) || clicked; - } - return clicked; - } - - bool EntityPropertiesComponents::drawRowDragFloat1(const char *uniqueLabel, const std::string &badgeLabel, float *value, float minValue, float maxValue, float speed) - { - const std::string labelStr = uniqueLabel; - std::string labelX = std::string("##X") + labelStr; - - std::string badgeLabelX = (badgeLabel.empty()) ? "" : badgeLabel + std::string("##") + labelStr; - - ImGui::TableNextRow(); - - ChannelLabel chanLabel; - chanLabel.label = std::string(uniqueLabel); - chanLabel.fixedWidth = -1.0f; - - std::vector badges; - badges.reserve(1); - badges.emplace_back(Badge{badgeLabelX, {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)}); - - std::vector sliders; - sliders.reserve(1); - sliders.emplace_back(labelX, value, speed, minValue, maxValue, 0, 0, 0, 0, "%.2f"); - - Channels channels; - channels.count = 1; - channels.badges = badges; - channels.sliders = sliders; - - EntityPropertiesComponents::drawRowLabel(chanLabel); - return EntityPropertiesComponents::drawRowDragFloat(channels); - } - - bool EntityPropertiesComponents::drawRowDragFloat2( - 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 - ) - { - const std::string labelStr = uniqueLabel; - std::string labelX = std::string("##X") + labelStr; - std::string labelY = std::string("##Y") + labelStr; - - const std::string badgeLabelX = badLabelX + std::string("##") + labelStr; - const std::string badgeLabelY = badLabelY + std::string("##") + labelStr; - - ImGui::TableNextRow(); - - ChannelLabel chanLabel; - chanLabel.label = std::string(uniqueLabel); - chanLabel.fixedWidth = -1.0f; - - if (badgeColor.empty()) - badgeColor = {IM_COL32(102, 28, 28, 255), IM_COL32(0, 80, 0, 255)}; - if (textBadgeColor.empty()) - textBadgeColor = {IM_COL32(255, 180, 180, 255), IM_COL32(180, 255, 180, 255)}; - std::vector badges; - badges.reserve(2); - badges.emplace_back(Badge{badgeLabelX, {0, 0}, badgeColor[0], badgeColor[0], badgeColor[0], textBadgeColor[0]}); - badges.emplace_back(Badge{badgeLabelY, {0, 0}, badgeColor[1], badgeColor[1], badgeColor[1], textBadgeColor[1]}); - - std::vector sliders; - sliders.reserve(2); - std::vector sliderColors = {ImGui::GetColorU32(ImGuiCol_FrameBg), ImGui::GetColorU32(ImGuiCol_FrameBgHovered), ImGui::GetColorU32(ImGuiCol_FrameBgActive), ImGui::GetColorU32(ImGuiCol_Text)}; - if (disabled) - sliderColors = {ImGui::GetColorU32(ImGuiCol_FrameBg), ImGui::GetColorU32(ImGuiCol_FrameBgHovered), ImGui::GetColorU32(ImGuiCol_FrameBgActive), ImGui::GetColorU32(ImGuiCol_TextDisabled)}; - sliders.emplace_back(labelX, &values[0], speed, minValue, maxValue, sliderColors[0], sliderColors[1], - sliderColors[2], sliderColors[3], "%.2f"); - sliders.emplace_back(labelY, &values[1], speed, minValue, maxValue, sliderColors[0], sliderColors[1], - sliderColors[2], sliderColors[3], "%.2f"); - - Channels channels; - channels.count = 2; - channels.badges = badges; - channels.sliders = sliders; - - EntityPropertiesComponents::drawRowLabel(chanLabel); - return EntityPropertiesComponents::drawRowDragFloat(channels); - } - - bool EntityPropertiesComponents::drawRowDragFloat3( - 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 textBadgeColor - ) - { - std::string labelStr = uniqueLabel; - std::string labelX = std::string("##X") + labelStr; - std::string labelY = std::string("##Y") + labelStr; - std::string labelZ = std::string("##Z") + labelStr; - - std::string badgeLabelX = badLabelX + std::string("##") + labelStr; - std::string badgeLabelY = badLabelY + std::string("##") + labelStr; - std::string badgeLabelZ = badLabelZ + std::string("##") + labelStr; - - ImGui::TableNextRow(); - - ChannelLabel chanLabel; - chanLabel.label = std::string(uniqueLabel); - chanLabel.fixedWidth = -1.0f; - - float badgeSize = ImGui::GetFrameHeight(); - if (badgeColors.empty()) - badgeColors = {IM_COL32(102, 28, 28, 255), IM_COL32(0, 80, 0, 255), IM_COL32(38, 49, 121, 255)}; - if (textBadgeColor.empty()) - textBadgeColor = {IM_COL32(255, 180, 180, 255), IM_COL32(180, 255, 180, 255), IM_COL32(180, 180, 255, 255)}; - std::vector badges; - badges.reserve(3); - badges.emplace_back(Badge{badgeLabelX, {badgeSize, badgeSize}, badgeColors[0], badgeColors[0], badgeColors[0], textBadgeColor[0]}); - badges.emplace_back(Badge{badgeLabelY, {badgeSize, badgeSize}, badgeColors[1], badgeColors[1], badgeColors[1], textBadgeColor[1]}); - badges.emplace_back(Badge{badgeLabelZ, {badgeSize, badgeSize}, badgeColors[2], badgeColors[2], badgeColors[2], textBadgeColor[2]}); - - std::vector sliders; - sliders.reserve(3); - sliders.emplace_back(labelX, &values[0], speed, minValue, maxValue, 0, - 0, 0, ImGui::GetColorU32(ImGuiCol_Text), - "%.2f"); - sliders.emplace_back(labelY, &values[1], speed, minValue, maxValue, 0, - 0, 0, ImGui::GetColorU32(ImGuiCol_Text), - "%.2f"); - sliders.emplace_back(labelZ, &values[2], speed, minValue, maxValue, 0, - 0, 0, ImGui::GetColorU32(ImGuiCol_Text), - "%.2f"); - - Channels channels; - channels.count = 3; - channels.badges = badges; - channels.sliders = sliders; - - if (!chanLabel.label.empty()) - EntityPropertiesComponents::drawRowLabel(chanLabel); - return EntityPropertiesComponents::drawRowDragFloat(channels); - } - - bool EntityPropertiesComponents::drawToggleButtonWithSeparator(const std::string &label, bool* toggled) - { - bool clicked = false; - ImGui::PushID(label.c_str()); - - constexpr ImVec2 buttonSize(24, 24); - if (const std::string arrowLabel = "##arrow" + label; ImGui::InvisibleButton(arrowLabel.c_str(), buttonSize)) - clicked = true; - if (clicked) - *toggled = !(*toggled); - - const ImVec2 btnPos = ImGui::GetItemRectMin(); - const ImVec2 btnSize = ImGui::GetItemRectSize(); - const ImVec2 center(btnPos.x + btnSize.x * 0.5f, btnPos.y + btnSize.y * 0.5f); - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - constexpr float arrowSize = 5.0f; - const ImU32 arrowColor = ImGui::GetColorU32(ImGuiCol_TextTab); - if (*toggled) - { - // Draw a downward pointing arrow - draw_list->AddTriangleFilled( - ImVec2(center.x - arrowSize, center.y - arrowSize), - ImVec2(center.x + arrowSize, center.y - arrowSize), - ImVec2(center.x, center.y + arrowSize), - arrowColor); - } - else - { - // Draw a rightward pointing arrow - draw_list->AddTriangleFilled( - ImVec2(center.x - arrowSize, center.y - arrowSize), - ImVec2(center.x - arrowSize, center.y + arrowSize), - ImVec2(center.x + arrowSize, center.y), - arrowColor); - } - - ImGui::SameLine(); - const ImVec2 separatorPos = ImGui::GetCursorScreenPos(); - constexpr float separatorHeight = buttonSize.y; // match button height - draw_list->AddLine(separatorPos, ImVec2(separatorPos.x, separatorPos.y + separatorHeight), - ImGui::GetColorU32(ImGuiCol_Separator), 1.0f); - ImGui::Dummy(ImVec2(4, buttonSize.y)); - - ImGui::SameLine(); - Components::drawCustomSeparatorText(label, 10.0f, 0.1f, 0.5f, IM_COL32(255, 255, 255, 255), IM_COL32(255, 255, 255, 255)); - ImGui::PopID(); - return clicked; - } -} diff --git a/editor/src/Components/EntityPropertiesComponents.hpp b/editor/src/Components/EntityPropertiesComponents.hpp deleted file mode 100644 index cf42cbe86..000000000 --- a/editor/src/Components/EntityPropertiesComponents.hpp +++ /dev/null @@ -1,202 +0,0 @@ -//// EntityPropertiesComponents.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: Mehdy MORVAN -// Date: 22/05/2025 -// Description: Header file for the entity properties components -// -/////////////////////////////////////////////////////////////////////////////// -#pragma once - -#include -#include -#include - -namespace nexo::editor { - - /** - * @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 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 - }; - - /** - * @class EntityPropertiesComponents - * @brief Static class providing UI components for entity property editing - * - * This class offers methods to draw various ImGui-based UI components - * specifically designed for editing entity properties in a consistent - * and visually appealing way. - */ - class EntityPropertiesComponents { - public: - /** - * @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 - */ - static bool drawHeader(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 - */ - static void drawRowLabel(const ChannelLabel &rowLabel); - - /** - * @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 - */ - static bool drawRowDragFloat1( - 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 - */ - static bool drawRowDragFloat2( - 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 - */ - static bool drawRowDragFloat3( - 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 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 - */ - static bool drawRowDragFloat(const Channels &channels); - - /** - * @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 - */ - static bool drawToggleButtonWithSeparator(const std::string &label, bool* toggled); - }; -} diff --git a/editor/src/Components/Widgets.cpp b/editor/src/Components/Widgets.cpp deleted file mode 100644 index f94ca9be0..000000000 --- a/editor/src/Components/Widgets.cpp +++ /dev/null @@ -1,672 +0,0 @@ -//// Widgets.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: 22/02/2025 -// Description: Source file for the widgets components -// -/////////////////////////////////////////////////////////////////////////////// - -#include "Widgets.hpp" - -#include -#include -#include -#include "Path.hpp" - -#include "Components.hpp" -#include "Definitions.hpp" -#include "IconsFontAwesome.h" -#include "Nexo.hpp" -#include "Texture.hpp" -#include "components/Camera.hpp" -#include "EntityPropertiesComponents.hpp" -#include "CameraFactory.hpp" -#include "components/Render3D.hpp" -#include "components/Transform.hpp" -#include "components/Uuid.hpp" -#include "tinyfiledialogs.h" -#include "context/Selector.hpp" - -namespace nexo::editor { - bool Widgets::drawColorEditor( - const std::string &label, - glm::vec4 *selectedEntityColor, - ImGuiColorEditFlags *colorPickerMode, - bool *showPicker, - const ImGuiColorEditFlags colorButtonFlags - ) { - const ImGuiStyle &style = ImGui::GetStyle(); - const ImVec2 contentAvailable = ImGui::GetContentRegionAvail(); - bool colorModified = false; - - const std::string colorButton = std::string("##ColorButton") + label; - - const ImVec2 cogIconSize = ImGui::CalcTextSize(ICON_FA_COG); - const ImVec2 cogIconPadding = style.FramePadding; - const ImVec2 itemSpacing = style.ItemSpacing; - - // Color button - Components::drawColorButton( - 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 - ); - - ImGui::SameLine(); - - const std::string pickerSettings = std::string("##PickerSettings") + label; - const std::string colorPickerPopup = std::string("##ColorPickerPopup") + label; - - // Cog button - if (Components::drawButton(std::string(ICON_FA_COG) + pickerSettings)) { - ImGui::OpenPopup(colorPickerPopup.c_str()); - } - - if (ImGui::BeginPopup(colorPickerPopup.c_str())) - { - ImGui::Text("Picker Mode:"); - if (ImGui::RadioButton("Hue Wheel", *colorPickerMode == ImGuiColorEditFlags_PickerHueWheel)) - *colorPickerMode = ImGuiColorEditFlags_PickerHueWheel; - if (ImGui::RadioButton("Hue bar", *colorPickerMode == ImGuiColorEditFlags_PickerHueBar)) - *colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - ImGui::EndPopup(); - } - - const std::string colorPickerInline = std::string("##ColorPickerInline") + label; - if (*showPicker) - { - ImGui::Spacing(); - colorModified = ImGui::ColorPicker4(colorPickerInline.c_str(), - reinterpret_cast(selectedEntityColor), *colorPickerMode); - } - return colorModified; - } - - bool Widgets::drawTextureButton(const std::string &label, std::shared_ptr &texture) - { - bool textureModified = 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 (ImGui::ImageButton(textureButton.c_str(), textureId, previewSize)) - { - const char* filePath = tinyfd_openFileDialog( - "Open Texture", - "", - 0, - nullptr, - nullptr, - 0 - ); - - if (filePath) - { - const std::string path(filePath); - std::shared_ptr newTexture = renderer::Texture2D::create(path); - if (newTexture) - { - texture = newTexture; - textureModified = true; - } - } - } - Components::drawButtonBorder(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 textureModified; - } - - bool Widgets::drawMaterialInspector(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" }; - float availableWidth = ImGui::GetContentRegionAvail().x; - - ImGui::SetNextItemWidth(availableWidth); - if (ImGui::Combo("##RenderingModeCombo", ¤tRenderingModeIndex, renderingModeOptions, IM_ARRAYSIZE(renderingModeOptions))) - { - //TODO: implement rendering mode - } - - // --- Albedo texture --- - static ImGuiColorEditFlags colorPickerModeAlbedo = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPickerAlbedo = false; - modified = Widgets::drawTextureButton("Albedo texture", material->albedoTexture) || modified; - ImGui::SameLine(); - modified = Widgets::drawColorEditor("##ColorEditor Albedo texture", &material->albedoColor, &colorPickerModeAlbedo, &showColorPickerAlbedo) || modified; - - // --- Specular texture --- - static ImGuiColorEditFlags colorPickerModeSpecular = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPickerSpecular = false; - modified = Widgets::drawTextureButton("Specular texture", material->metallicMap) || modified; - ImGui::SameLine(); - modified = Widgets::drawColorEditor("##ColorEditor Specular texture", &material->specularColor, &colorPickerModeSpecular, &showColorPickerSpecular) || modified; - return modified; - } - - static ecs::Entity createDefaultPerspectiveCamera(const scene::SceneId sceneId, ImVec2 sceneViewportSize) - { - auto &app = getApp(); - renderer::FramebufferSpecs framebufferSpecs; - framebufferSpecs.attachments = { - renderer::FrameBufferTextureFormats::RGBA8, renderer::FrameBufferTextureFormats::RED_INTEGER, renderer::FrameBufferTextureFormats::Depth - }; - const ImVec2 availSize = ImGui::GetContentRegionAvail(); - const float totalWidth = availSize.x; - 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 panel - framebufferSpecs.width = static_cast(sceneViewportSize.x); - framebufferSpecs.height = static_cast(sceneViewportSize.y); - const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs); - ecs::Entity defaultCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, -5.0f}, static_cast(sceneViewportSize.x), static_cast(sceneViewportSize.y), renderTarget); - app.getSceneManager().getScene(sceneId).addEntity(static_cast(defaultCamera)); - - components::Material billboardMat{}; - std::shared_ptr cameraIconTexture = renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png").string()); - billboardMat.albedoTexture = cameraIconTexture; - auto billboard = std::make_shared(); - auto renderable = std::make_shared(billboardMat, billboard); - components::RenderComponent renderComponent(renderable, components::RenderType::RENDER_3D); - app.m_coordinator->addComponent(defaultCamera, renderComponent); - return defaultCamera; - } - - void Widgets::drawTransformProperties(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, size, quat] = transformComponent; - - 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); - - EntityPropertiesComponents::drawRowDragFloat3("Position", "X", "Y", "Z", &pos.x); - - const glm::vec3 computedEuler = math::customQuatToEuler(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 (EntityPropertiesComponents::drawRowDragFloat3("Rotation", "X", "Y", "Z", &rotation.x)) { - const glm::vec3 deltaEuler = rotation - lastDisplayedEuler; - const glm::quat deltaQuat = glm::radians(deltaEuler); - quat = glm::normalize(deltaQuat * quat); - lastDisplayedEuler = math::customQuatToEuler(quat); - rotation = lastDisplayedEuler; - } - EntityPropertiesComponents::drawRowDragFloat3("Scale", "X", "Y", "Z", &size.x); - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - } - - void Widgets::drawCameraProperties(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); - glm::vec2 viewPort = {cameraComponent.width, cameraComponent.height}; - std::vector badgeColors; - std::vector textBadgeColors; - - const bool disabled = cameraComponent.viewportLocked; - if (disabled) - ImGui::BeginDisabled(); - if (EntityPropertiesComponents::drawRowDragFloat2("Viewport size", "W", "H", &viewPort.x, -FLT_MAX, FLT_MAX, 1.0f, badgeColors, textBadgeColors, disabled)) - { - if (!cameraComponent.viewportLocked) - cameraComponent.resize(static_cast(viewPort.x), static_cast(viewPort.y)); - } - if (disabled) - ImGui::EndDisabled(); - - ImGui::TableSetColumnIndex(3); - - // Lock button - const std::string lockBtnLabel = cameraComponent.viewportLocked ? ICON_FA_LOCK "##ViewPortSettings" : ICON_FA_UNLOCK "##ViewPortSettings"; - if (Components::drawButton(lockBtnLabel)) { - cameraComponent.viewportLocked = !cameraComponent.viewportLocked; - } - - - ImGui::EndTable(); - } - - if (ImGui::BeginTable("InspectorCameraVariables", 2, ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - EntityPropertiesComponents::drawRowDragFloat1("FOV", "", &cameraComponent.fov, 30.0f, 120.0f, 0.3f); - EntityPropertiesComponents::drawRowDragFloat1("Near plane", "", &cameraComponent.nearPlane, 0.01f, 1.0f, 0.001f); - EntityPropertiesComponents::drawRowDragFloat1("Far plane", "", &cameraComponent.farPlane, 100.0f, 10000.0f, 1.0f); - - ImGui::EndTable(); - } - - - ImGui::PopStyleVar(); - - ImGui::Spacing(); - static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; - ImGui::AlignTextToFramePadding(); - ImGui::Text("Clear Color"); - ImGui::SameLine(); - Widgets::drawColorEditor("##ColorEditor Spot light", &cameraComponent.clearColor, &colorPickerMode, &showColorPicker); - } - - void Widgets::drawCameraTargetProperties(components::PerspectiveCameraTarget &cameraTargetComponent) - { - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("InspectorControllerTable", 2, - ImGuiTableFlags_SizingStretchProp)) - { - auto &app = getApp(); - auto &selector = Selector::get(); - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - const std::vector &entities = app.m_coordinator->getAllEntitiesWith< - components::TransformComponent, - ecs::Exclude, - ecs::Exclude, - ecs::Exclude, - ecs::Exclude, - ecs::Exclude>(); - - EntityPropertiesComponents::drawRowDragFloat1("Mouse sensitivity", "", &cameraTargetComponent.mouseSensitivity, 0.1f); - EntityPropertiesComponents::drawRowDragFloat1("Distance", "", &cameraTargetComponent.distance, 0.1f); - Components::drawRowEntityDropdown( - "Target Entity", - cameraTargetComponent.targetEntity, entities, - [&app, &selector](ecs::Entity e) { - return selector.getUiHandle( - app.m_coordinator->getComponent(e).uuid, - std::to_string(e) - ); - }); - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - } - - void Widgets::drawCameraControllerProperties(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); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - EntityPropertiesComponents::drawRowDragFloat1("Mouse sensitivity", "", &cameraControllerComponent.mouseSensitivity); - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - } - - bool Widgets::drawCameraCreator(const scene::SceneId sceneId, ImVec2 sceneViewportSize) - { - auto &app = getApp(); - - const ImVec2 availSize = ImGui::GetContentRegionAvail(); - const float totalWidth = availSize.x; - 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 - static ecs::Entity camera = ecs::MAX_ENTITIES; - if (camera == ecs::MAX_ENTITIES) - { - camera = createDefaultPerspectiveCamera(sceneId, ImVec2(previewWidth, totalHeight)); - } - - static char cameraName[128] = ""; - static bool nameIsEmpty = false; - ImGui::Columns(2, "CameraCreatorColumns", false); - - ImGui::SetColumnWidth(0, inspectorWidth); - // --- Left Side: Camera Inspector --- - { - ImGui::BeginChild("CameraInspector", ImVec2(inspectorWidth - 4, totalHeight), true); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Name"); - ImGui::SameLine(); - if (nameIsEmpty) { - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.9f, 0.2f, 0.2f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - } - ImGui::InputText("##CameraName", cameraName, IM_ARRAYSIZE(cameraName)); - if (nameIsEmpty) { - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); - - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.2f, 0.2f, 1.0f)); - ImGui::TextWrapped("Name is empty"); - ImGui::PopStyleColor(); - ImGui::Spacing(); - } else { - ImGui::Spacing(); - } - if (nameIsEmpty && cameraName[0] != '\0') - nameIsEmpty = false; - ImGui::Spacing(); - - if (EntityPropertiesComponents::drawHeader("##CameraNode", "Camera")) - { - auto &cameraComponent = app.m_coordinator->getComponent(camera); - cameraComponent.render = true; - Widgets::drawCameraProperties(cameraComponent); - ImGui::TreePop(); - } - - ImGui::Spacing(); - ImGui::Spacing(); - ImGui::Spacing(); - - if (EntityPropertiesComponents::drawHeader("##TransformNode", "Transform Component")) - { - static glm::vec3 lastDisplayedEuler(0.0f); - auto &transformComponent = app.m_coordinator->getComponent(camera); - Widgets::drawTransformProperties(transformComponent, lastDisplayedEuler); - ImGui::TreePop(); - } - - if (app.m_coordinator->entityHasComponent(camera) && - EntityPropertiesComponents::drawHeader("##PerspectiveCameraTarget", "Camera Target Component")) - { - auto &cameraTargetComponent = app.m_coordinator->getComponent(camera); - Widgets::drawCameraTargetProperties(cameraTargetComponent); - ImGui::TreePop(); - } - - if (app.m_coordinator->entityHasComponent(camera) && - EntityPropertiesComponents::drawHeader("##PerspectiveCameraController", "Camera Controller Component")) - { - auto &cameraControllerComponent = app.m_coordinator->getComponent(camera); - Widgets::drawCameraControllerProperties(cameraControllerComponent); - ImGui::TreePop(); - } - - ImGui::Spacing(); - ImGui::Spacing(); - ImGui::Spacing(); - ImGui::Spacing(); - // Add Component button - const float buttonWidth = inspectorWidth - 16; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 4)); - float centeredX = (inspectorWidth - buttonWidth) * 0.5f; - ImGui::SetCursorPosX(centeredX); - - // Static variables for state tracking - static bool showComponentSelector = false; - static float animProgress = 0.0f; - static float lastClickTime = 0.0f; - - // Button with arrow indicating state - std::string buttonText = "Add Component " + std::string(showComponentSelector ? ICON_FA_CHEVRON_UP : ICON_FA_CHEVRON_DOWN); - - if (Components::drawButton(buttonText.c_str(), ImVec2(buttonWidth, 0))) - { - showComponentSelector = !showComponentSelector; - if (showComponentSelector) { - lastClickTime = ImGui::GetTime(); - animProgress = 0.0f; - } - } - ImGui::PopStyleVar(); - - // Component selector with just two options - if (showComponentSelector) - { - // Animation calculation - const float animDuration = 0.25f; - float timeSinceClick = ImGui::GetTime() - lastClickTime; - animProgress = std::min(timeSinceClick / animDuration, 1.0f); - - // Simplified component grid with compact layout - const float maxGridHeight = 90.0f; - 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_FramePadding, ImVec2(8, 8)); // Better padding inside items - - ImGui::BeginChild("ComponentSelector", ImVec2(buttonWidth, currentHeight), 0, ImGuiWindowFlags_NoScrollbar); - - if (animProgress > 0.5f) - { - // Center elements horizontally with proper spacing - const float itemSize = 75.0f; - - // Draw component buttons side-by-side with controlled spacing - ImGui::BeginGroup(); - - if (!app.m_coordinator->entityHasComponent(camera) && - !app.m_coordinator->entityHasComponent(camera) && - Components::drawComponentButton("camera_target", ICON_FA_CAMERA, "Camera target", ImVec2(75.0f, 75.0f))) - { - components::PerspectiveCameraTarget cameraTarget{}; - app.m_coordinator->addComponent(camera, cameraTarget); - showComponentSelector = false; - } - ImGui::SameLine(); - if (!app.m_coordinator->entityHasComponent(camera) && - !app.m_coordinator->entityHasComponent(camera) && - Components::drawComponentButton("camera_controller", ICON_FA_GAMEPAD, "Camera Controller", ImVec2(75.0f, 75.0f))) - { - components::PerspectiveCameraController cameraController{}; - app.m_coordinator->addComponent(camera, cameraController); - showComponentSelector = false; - } - ImGui::EndGroup(); - } - - ImGui::EndChild(); - ImGui::PopStyleVar(3); - - // Reset animation if needed - if (!showComponentSelector && animProgress >= 1.0f) { - animProgress = 0.0f; - } - } - - ImGui::EndChild(); // End CameraInspector - } - ImGui::NextColumn(); - // --- Right Side: Camera Preview --- - { - ImGui::BeginChild("CameraPreview", ImVec2(previewWidth - 4, totalHeight), true); - - auto &app = getApp(); - app.run(sceneId, RenderingType::FRAMEBUFFER); - auto const &cameraComponent = Application::m_coordinator->getComponent(camera); - const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); - - const float displayHeight = totalHeight - 20; - const float displayWidth = displayHeight; - - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 4, ImGui::GetCursorPosY() + 4)); - ImGui::Image(static_cast(static_cast(textureId)), - ImVec2(displayWidth, displayHeight), ImVec2(0, 1), ImVec2(1, 0)); - - ImGui::EndChild(); - } - - ImGui::Columns(1); - ImGui::Spacing(); - - // Bottom buttons - centered - constexpr float buttonWidth = 120.0f; - - if (ImGui::Button("OK", ImVec2(buttonWidth, 0))) - { - if (cameraName[0] == '\0') { - nameIsEmpty = true; - return false; - } - nameIsEmpty = false; - auto &selector = Selector::get(); - auto &uuid = app.m_coordinator->getComponent(camera); - auto &cameraComponent = app.m_coordinator->getComponent(camera); - cameraComponent.active = false; - selector.setUiHandle(uuid.uuid, std::string(ICON_FA_CAMERA " ") + cameraName); - camera = ecs::MAX_ENTITIES; - cameraName[0] = '\0'; - ImGui::CloseCurrentPopup(); - return true; - } - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) - { - nameIsEmpty = false; - app.deleteEntity(camera); - camera = ecs::MAX_ENTITIES; - cameraName[0] = '\0'; - ImGui::CloseCurrentPopup(); - return true; - } - return false; - } - - void Widgets::drawVerticalButtonDropDown(const ImVec2& buttonPos, const ImVec2 buttonSize, const std::vector &buttonProps, bool &closure, DropdownOrientation orientation) - { - constexpr float buttonSpacing = 5.0f; - constexpr float padding = 10.0f; - const auto &app = getApp(); - - // Calculate menu dimensions - const float menuWidth = buttonSize.x + padding; // Add padding - const float menuHeight = buttonProps.size() * buttonSize.y + - (buttonProps.size() - 1) * 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; - } - - // Adjust layout for horizontal orientations - bool isHorizontal = (orientation == DropdownOrientation::LEFT || - orientation == DropdownOrientation::RIGHT); - - // For horizontal layouts, swap width and height - ImVec2 menuSize = isHorizontal ? - ImVec2(menuHeight, buttonSize.y + 10.0f) : - ImVec2(menuWidth, menuHeight); - - // Create a separate overlay child window for the primitive buttons - ImGui::SetNextWindowPos(menuPos); - ImGui::SetNextWindowSize(menuSize); - ImGui::SetNextWindowBgAlpha(0.2f); - - // Style adjustments - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5.0f, buttonSpacing)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, - isHorizontal ? ImVec2(buttonSpacing, 0) : ImVec2(0, buttonSpacing)); - 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) - { - // Strangely here the clicked inside here does not seem to work - Components::drawToolbarButton(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)) - { - button.onClick(); - closure = false; - } - if (button.onRightClick && ImGui::IsItemClicked(ImGuiMouseButton_Right)) - { - button.onRightClick(); - } - if (!button.tooltip.empty() && ImGui::IsItemHovered()) - ImGui::SetTooltip(button.tooltip.c_str()); - } - } - // Check for clicks outside to close menu - if (ImGui::IsMouseClicked(0) && !ImGui::IsWindowHovered()) - { - closure = false; - } - ImGui::End(); - - ImGui::PopStyleVar(3); - } -} diff --git a/editor/src/Components/Widgets.hpp b/editor/src/Components/Widgets.hpp deleted file mode 100644 index 6791c0a85..000000000 --- a/editor/src/Components/Widgets.hpp +++ /dev/null @@ -1,109 +0,0 @@ -//// Widgets.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: Mehdy MORVAN -// Date: 22/02/2025 -// Description: Header file for the widgets components -// -/////////////////////////////////////////////////////////////////////////////// -#pragma once - -#include -#include -#include - -#include "Components/Components.hpp" -#include "components/Camera.hpp" -#include "components/Render3D.hpp" -#include "components/Transform.hpp" -#include "renderer/Texture.hpp" -#include "core/scene/SceneManager.hpp" - -namespace nexo::editor { - - /** - * @brief A collection of custom ImGui widget drawing functions. - * - * Provides utility functions for drawing color editors, texture buttons, and a material inspector, - * which can be used to simplify UI code for rendering material properties. - */ - class Widgets { - public: - - /** - * @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. - */ - static bool drawColorEditor( - const std::string &label, - glm::vec4 *selectedEntityColor, - ImGuiColorEditFlags *colorPickerMode, - bool *showPicker, - ImGuiColorEditFlags colorButtonFlags = ImGuiColorEditFlags_None); - - /** - * @brief Draws a texture button that displays a texture preview. - * - * When clicked, opens a file dialog to select a new texture. If a new texture is loaded, - * the passed texture pointer is updated and the function returns true. - * - * @param label A unique label identifier for the button. - * @param texture A shared pointer to the renderer::Texture2D that holds the texture. - * @return true if the texture was modified; false otherwise. - */ - static bool drawTextureButton(const std::string &label, std::shared_ptr &texture); - - /** - * @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 Pointer to the components::Material to be inspected and modified. - * @return true if any material property was modified; false otherwise. - */ - static bool drawMaterialInspector(components::Material *material); - - static void drawTransformProperties(components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler); - static void drawCameraProperties(components::CameraComponent &cameraComponent); - static void drawCameraTargetProperties(components::PerspectiveCameraTarget &cameraTargetComponent); - static void drawCameraControllerProperties(components::PerspectiveCameraController &cameraControllerComponent); - - static bool drawCameraCreator(const scene::SceneId sceneId, ImVec2 sceneViewportSize); - - - struct ButtonProps { - std::string uniqueId; - std::string icon; - std::function onClick = nullptr; - std::function onRightClick = nullptr; - std::string tooltip = ""; - - 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 - }; - static void drawVerticalButtonDropDown(const ImVec2& buttonPos, const ImVec2 buttonSize, const std::vector &buttonProps, bool &closure, const DropdownOrientation orientation = DropdownOrientation::DOWN); - }; -} diff --git a/editor/src/DockingRegistry.hpp b/editor/src/DockingRegistry.hpp index 9893c3864..094081d14 100644 --- a/editor/src/DockingRegistry.hpp +++ b/editor/src/DockingRegistry.hpp @@ -22,6 +22,17 @@ namespace nexo::editor { + /** + * @brief Manages associations between window names and their docking identifiers. + * + * The DockingRegistry maintains a mapping between window names and ImGui dock IDs, + * allowing the editor to track and restore docking configurations across sessions. + * This class is central to the editor's window layout management system, enabling + * persistent window arrangements and proper docking behavior. + * + * It uses a TransparentStringHash for efficient string lookups and provides methods + * for setting, retrieving, and removing dock ID associations. + */ class DockingRegistry { public: @@ -47,6 +58,18 @@ namespace nexo::editor { */ 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); private: diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp index 7f91052ec..f7d1a3b49 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow.cpp @@ -25,6 +25,7 @@ #include #include #include "ConsoleWindow.hpp" +#include "ImNexo/Elements.hpp" #include "Editor.hpp" #include "Logger.hpp" #include "Path.hpp" @@ -315,7 +316,7 @@ namespace nexo::editor { ImGui::Separator(); ImGui::Checkbox("File logging", &m_exportLog); - if (ImGui::Button("Open log folder")) + if (ImNexo::Button("Open log folder")) utils::openFolder(Path::resolvePathRelativeToExe("../logs").string()); ImGui::EndPopup(); @@ -358,7 +359,7 @@ namespace nexo::editor { } ImGui::SameLine(); - if (ImGui::Button("...")) + if (ImNexo::Button("...")) ImGui::OpenPopup("VerbositySettings"); if (ImGui::BeginPopup("VerbositySettings")) diff --git a/editor/src/DocumentWindows/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow.hpp index 189e0fe27..d23f33a39 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.hpp +++ b/editor/src/DocumentWindows/ConsoleWindow.hpp @@ -21,24 +21,38 @@ namespace nexo::editor { 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; - std::string message; - std::string prefix; + 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. + * + * This class provides a visual interface for viewing log messages with different + * verbosity levels, executing commands, and managing log settings. It integrates + * 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 registry The window registry used to register this console window. - */ + /** + * @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 registry The window registry used to register this console window. + */ explicit ConsoleWindow(const std::string &windowName, WindowRegistry ®istry); /** @@ -51,6 +65,7 @@ namespace nexo::editor { loguru::remove_callback(LOGURU_CALLBACK_NAME); }; + // No-op method in this class void setup() override; /** @@ -73,8 +88,19 @@ namespace nexo::editor { * 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: @@ -119,10 +145,42 @@ namespace nexo::editor { */ 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(const char* fmt, Args&&... args); + + /** + * @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(); + + /** + * @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(); /** diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 3c5a3cfed..0ea21e0db 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -26,13 +26,12 @@ #include "Nexo.hpp" #include "Texture.hpp" #include "WindowRegistry.hpp" -#include "Components/Widgets.hpp" -#include "Components/EntityPropertiesComponents.hpp" #include "components/Camera.hpp" #include "components/Uuid.hpp" #include "math/Matrix.hpp" #include "context/Selector.hpp" #include "utils/String.hpp" +#include "ImNexo/Widgets.hpp" #include #include @@ -44,7 +43,6 @@ namespace nexo::editor { void EditorScene::setup() { - setupImguizmo(); setupWindow(); setupScene(); } @@ -53,7 +51,6 @@ namespace nexo::editor { { auto &app = getApp(); - // New handling m_sceneId = static_cast(app.getSceneManager().createScene(m_windowName)); renderer::FramebufferSpecs framebufferSpecs; framebufferSpecs.attachments = { @@ -77,11 +74,6 @@ namespace nexo::editor { loadDefaultEntities(); } - void EditorScene::setupImguizmo() const - { - ImGuizmo::SetOrthographic(true); - } - void EditorScene::loadDefaultEntities() const { auto &app = getApp(); @@ -144,20 +136,20 @@ namespace nexo::editor { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 0)); } - bool EditorScene::renderToolbarButton(const std::string &uniqueId, const std::string &icon, const std::string &tooltip, const std::vector & gradientStop) + bool EditorScene::renderToolbarButton(const std::string &uniqueId, const std::string &icon, const std::string &tooltip, const std::vector & gradientStop) { constexpr float buttonWidth = 35.0f; constexpr float buttonHeight = 35.0f; - bool clicked = Components::drawToolbarButton(uniqueId, icon, ImVec2(buttonWidth, buttonHeight), gradientStop); + bool clicked = ImNexo::IconGradientButton(uniqueId, icon, ImVec2(buttonWidth, buttonHeight), gradientStop); if (!tooltip.empty() && ImGui::IsItemHovered()) - ImGui::SetTooltip(tooltip.c_str()); + ImGui::SetTooltip("%s", tooltip.c_str()); return clicked; } void EditorScene::renderPrimitiveSubMenu(const ImVec2 &primitiveButtonPos, const ImVec2 &buttonSize, bool &showPrimitiveMenu) { auto &app = getApp(); - static const std::vector buttonProps = + static const std::vector buttonProps = { { .uniqueId = "cube_primitive", @@ -171,12 +163,12 @@ namespace nexo::editor { .tooltip = "Create Cube" } }; - Widgets::drawVerticalButtonDropDown(primitiveButtonPos, buttonSize, buttonProps, showPrimitiveMenu); + ImNexo::ButtonDropDown(primitiveButtonPos, buttonSize, buttonProps, showPrimitiveMenu); } void EditorScene::renderSnapSubMenu(const ImVec2 &snapButtonPos, const ImVec2 &buttonSize, bool &showSnapMenu) { - const std::vector buttonProps = + const std::vector buttonProps = { { .uniqueId = "toggle_translate_snap", @@ -222,7 +214,7 @@ namespace nexo::editor { // .buttonGradient = (m_snapScaleOn) ? m_selectedGradient : buttonGradient // } }; - Widgets::drawVerticalButtonDropDown(snapButtonPos, buttonSize, buttonProps, showSnapMenu); + ImNexo::ButtonDropDown(snapButtonPos, buttonSize, buttonProps, showSnapMenu); } void EditorScene::snapSettingsPopup() @@ -239,7 +231,7 @@ namespace nexo::editor { ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - EntityPropertiesComponents::drawRowDragFloat3("Translate Snap", "X", "Y", "Z", &this->m_snapTranslate.x); + ImNexo::RowDragFloat3("Translate Snap", "X", "Y", "Z", &this->m_snapTranslate.x); ImGui::EndTable(); } @@ -252,7 +244,7 @@ namespace nexo::editor { ImGui::TableSetupColumn("##Empty1", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Empty2", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - EntityPropertiesComponents::drawRowDragFloat1("Rotate Snap", "", &this->m_angleSnap); + ImNexo::RowDragFloat1("Rotate Snap", "", &this->m_angleSnap); ImGui::EndTable(); } ImGui::Spacing(); @@ -262,7 +254,7 @@ namespace nexo::editor { float windowWidth = ImGui::GetWindowSize().x; ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); - if (ImGui::Button("OK", ImVec2(buttonWidth, 0.0f))) + if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) { m_popupManager.closePopupInContext(); } @@ -295,10 +287,10 @@ namespace nexo::editor { } } - bool EditorScene::renderGizmoModeToolbarButton(const bool showGizmoModeMenu, Widgets::ButtonProps &activeGizmoMode, Widgets::ButtonProps &inactiveGizmoMode) + bool EditorScene::renderGizmoModeToolbarButton(const bool showGizmoModeMenu, ImNexo::ButtonProps &activeGizmoMode, ImNexo::ButtonProps &inactiveGizmoMode) { - static const Widgets::ButtonProps gizmoLocalModeButtonProps = {"local_coords", ICON_FA_CROSSHAIRS, [this]() {this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL;}, nullptr, "Local coordinates"}; - static const Widgets::ButtonProps gizmoWorldModeButtonProps = {"world_coords", ICON_FA_GLOBE, [this]() {this->m_currentGizmoMode = ImGuizmo::MODE::WORLD;}, nullptr, "World coordinates"}; + static const ImNexo::ButtonProps gizmoLocalModeButtonProps = {"local_coords", ICON_FA_CROSSHAIRS, [this]() {this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL;}, nullptr, "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; inactiveGizmoMode = gizmoWorldModeButtonProps; @@ -338,18 +330,18 @@ namespace nexo::editor { ImGui::SameLine(); // -------- Gizmo operation button -------- - static const Widgets::ButtonProps gizmoTranslateButtonProps = Widgets::ButtonProps{"translate", ICON_FA_ARROWS, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE;}, nullptr, "Translate"}; - static const Widgets::ButtonProps gizmoRotateButtonProps = Widgets::ButtonProps{"rotate", ICON_FA_REFRESH, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE;}, nullptr, "Rotate"}; - static const Widgets::ButtonProps gizmoScaleButtonProps = Widgets::ButtonProps{"scale", ICON_FA_EXPAND, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE;}, nullptr, "Scale"}; - static const Widgets::ButtonProps gizmoUniversalButtonProps = Widgets::ButtonProps{"universal", ICON_FA_ARROWS_ALT, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL;}, nullptr, "Universal"}; - std::vector gizmoButtons = { + static const ImNexo::ButtonProps gizmoTranslateButtonProps = ImNexo::ButtonProps{"translate", ICON_FA_ARROWS, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE;}, nullptr, "Translate"}; + static const ImNexo::ButtonProps gizmoRotateButtonProps = ImNexo::ButtonProps{"rotate", ICON_FA_REFRESH, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE;}, nullptr, "Rotate"}; + static const ImNexo::ButtonProps gizmoScaleButtonProps = ImNexo::ButtonProps{"scale", ICON_FA_EXPAND, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE;}, nullptr, "Scale"}; + static const ImNexo::ButtonProps gizmoUniversalButtonProps = ImNexo::ButtonProps{"universal", ICON_FA_ARROWS_ALT, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL;}, nullptr, "Universal"}; + std::vector gizmoButtons = { gizmoTranslateButtonProps, gizmoRotateButtonProps, gizmoScaleButtonProps, gizmoUniversalButtonProps }; - Widgets::ButtonProps activeOp; + ImNexo::ButtonProps activeOp; switch (m_currentGizmoOperation) { case ImGuizmo::OPERATION::TRANSLATE: activeOp = gizmoTranslateButtonProps; @@ -382,8 +374,8 @@ namespace nexo::editor { ImGui::SameLine(); // -------- Gizmo operation button -------- - Widgets::ButtonProps activeGizmoMode; - Widgets::ButtonProps inactiveGizmoMode; + ImNexo::ButtonProps activeGizmoMode; + ImNexo::ButtonProps inactiveGizmoMode; ImVec2 changeGizmoModePos = ImGui::GetCursorScreenPos(); static bool showGizmoModeMenu = false; bool changeGizmoModeClicked = renderGizmoModeToolbarButton(showGizmoModeMenu, activeGizmoMode, inactiveGizmoMode); @@ -444,13 +436,13 @@ namespace nexo::editor { // -------- Gizmo operation sub-menu -------- if (showGizmoOpMenu) { - Widgets::drawVerticalButtonDropDown(changeGizmoOpPos, buttonSize, gizmoButtons, showGizmoOpMenu); + ImNexo::ButtonDropDown(changeGizmoOpPos, buttonSize, gizmoButtons, showGizmoOpMenu); } // -------- Gizmo mode sub-menu -------- if (showGizmoModeMenu) { - Widgets::drawVerticalButtonDropDown(changeGizmoModePos, buttonSize, {inactiveGizmoMode}, showGizmoModeMenu); + ImNexo::ButtonDropDown(changeGizmoModePos, buttonSize, {inactiveGizmoMode}, showGizmoModeMenu); } // -------- Snap sub-menu -------- diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp index 59b0ebc27..3c6e6811c 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene.hpp @@ -16,11 +16,10 @@ #include "ADocumentWindow.hpp" #include "IDocumentWindow.hpp" #include "core/scene/SceneManager.hpp" -#include "Components/Components.hpp" -#include "Components/Widgets.hpp" #include "PopupManager.hpp" #include #include +#include "ImNexo/Widgets.hpp" namespace nexo::editor { @@ -37,6 +36,8 @@ namespace nexo::editor { * - Creating and configuring the scene. */ void setup() override; + + // No-op method in this class void shutdown() override; /** @@ -68,10 +69,30 @@ namespace nexo::editor { * @return scene::SceneId The identifier of this scene. */ [[nodiscard]] scene::SceneId getSceneId() const {return m_sceneId;}; + + /** + * @brief Gets the current viewport size. + * + * @return ImVec2 The width and height of the viewport in pixels. + */ [[nodiscard]] ImVec2 getViewportSize() const {return m_viewSize;}; + /** + * @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); + /** + * @brief Marks this scene as the default scene. + * + * 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; }; private: @@ -96,13 +117,13 @@ namespace nexo::editor { PopupManager m_popupManager; - const std::vector m_buttonGradient = { + const std::vector m_buttonGradient = { {0.0f, IM_COL32(50, 50, 70, 230)}, {1.0f, IM_COL32(30, 30, 45, 230)} }; // Selected button gradient - lighter blue gradient - const std::vector m_selectedGradient = { + const std::vector m_selectedGradient = { {0.0f, IM_COL32(70, 70, 120, 230)}, {1.0f, IM_COL32(50, 50, 100, 230)} }; @@ -113,10 +134,30 @@ namespace nexo::editor { * Configures the view to a default size of 1280x720 pixels. */ void setupWindow(); - void setupImguizmo() const; + + /** + * @brief Creates and initializes a scene with basic components. + * + * Sets up the scene with a framebuffer, editor camera, and loads default + * entities if this is the default scene. + */ void setupScene(); + + /** + * @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 Handles keyboard input events for the scene. + * + * Processes key presses for navigation, selection, and other scene-specific + * operations when the scene window is focused. + * @note not implemented yet + */ void handleKeyEvents() const; /** @@ -127,13 +168,92 @@ namespace nexo::editor { * The toolbar is positioned relative to the current view to align with the scene layout. */ void renderToolbar(); + + /** + * @brief Sets up the initial layout and style for the toolbar. + * + * Creates a child window with specific size and style settings for the toolbar, + * positions it at the top of the viewport, and configures spacing. + * + * @param buttonWidth Standard width for toolbar buttons + * @param buttonHeight Standard height for toolbar buttons + */ void initialToolbarSetup(const float buttonWidth, const float buttonHeight); + + /** + * @brief Renders the editor camera button in the toolbar. + * + * Shows either a camera settings button (when editor camera is active) or a + * "switch back to editor camera" button (when a different camera is active). + */ void renderEditorCameraToolbarButton(); - bool renderGizmoModeToolbarButton(const bool showGizmoModeMenu, Widgets::ButtonProps &activeGizmoMode, Widgets::ButtonProps &inactiveGizmoMode); + + /** + * @brief Renders the button to toggle between world/local coordinate modes. + * + * Updates the button props based on the current mode and renders the appropriate + * button with correct styling. + * + * @param showGizmoModeMenu Flag indicating if the mode dropdown menu is visible + * @param activeGizmoMode Reference to store the active mode button properties + * @param inactiveGizmoMode Reference to store the inactive mode button properties + * @return true if the button was clicked + */ + bool renderGizmoModeToolbarButton( + const bool showGizmoModeMenu, + ImNexo::ButtonProps &activeGizmoMode, + ImNexo::ButtonProps &inactiveGizmoMode + ); + + /** + * @brief Renders the primitive creation dropdown menu. + * + * Creates a dropdown with buttons for adding primitive shapes like cubes, + * spheres, etc. to the scene. + * + * @param primitiveButtonPos Position of the parent button that opened this menu + * @param buttonSize Size of buttons in the dropdown + * @param showPrimitiveMenu Reference to the flag controlling menu visibility + */ void renderPrimitiveSubMenu(const ImVec2 &primitiveButtonPos, const ImVec2 &buttonSize, bool &showPrimitiveMenu); + + /** + * @brief Renders the snap settings dropdown menu. + * + * Creates a dropdown with toggles for different snapping modes (translate, rotate) + * and their settings. + * + * @param snapButtonPos Position of the parent button that opened this menu + * @param buttonSize Size of buttons in the dropdown + * @param showSnapMenu Reference to the flag controlling menu visibility + */ void renderSnapSubMenu(const ImVec2 &snapButtonPos, const ImVec2 &buttonSize, bool &showSnapMenu); + + /** + * @brief Handles the snap settings popup dialog. + * + * Creates a modal popup allowing users to configure fine-grained snap settings + * like translate values and rotation angles. + */ void snapSettingsPopup(); - bool renderToolbarButton(const std::string &uniqueId, const std::string &icon, const std::string &tooltip, const std::vector & gradientStop); + + /** + * @brief Renders a standard toolbar button with optional tooltip and styling. + * + * Creates a gradient button with the specified icon and shows a tooltip on hover. + * + * @param uniqueId Unique identifier for the ImGui control + * @param icon Font icon to display on the button + * @param tooltip Text to show when hovering over the button + * @param gradientStop Color gradient for the button background + * @return true if the button was clicked + */ + bool renderToolbarButton( + const std::string &uniqueId, + const std::string &icon, + const std::string &tooltip, + const std::vector & gradientStop + ); /** * @brief Renders the transformation gizmo for the selected entity. @@ -147,6 +267,13 @@ namespace nexo::editor { * If the gizmo is actively manipulated, the entity's transform component is updated with the new values. */ void renderGizmo(); + + /** + * @brief Renders the main viewport showing the 3D scene. + * + * Handles resizing of the viewport, draws the framebuffer texture containing the + * rendered scene, and updates viewport bounds for input handling. + */ void renderView(); }; } diff --git a/editor/src/DocumentWindows/EntityProperties/AEntityProperty.hpp b/editor/src/DocumentWindows/EntityProperties/AEntityProperty.hpp index 570af6509..762c37189 100644 --- a/editor/src/DocumentWindows/EntityProperties/AEntityProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/AEntityProperty.hpp @@ -27,17 +27,17 @@ namespace nexo::editor { }; 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; }; }; diff --git a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp index 5ce11724c..83a11ecd9 100644 --- a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp @@ -14,8 +14,7 @@ #include "AmbientLightProperty.hpp" #include "components/Light.hpp" -#include "Components/EntityPropertiesComponents.hpp" -#include "Components/Widgets.hpp" +#include "ImNexo/Widgets.hpp" namespace nexo::editor { @@ -23,7 +22,7 @@ namespace nexo::editor { { auto& ambientComponent = Application::getEntityComponent(entity); - if (EntityPropertiesComponents::drawHeader("##AmbientNode", "Ambient light")) + if (ImNexo::Header("##AmbientNode", "Ambient light")) { ImGui::Spacing(); static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; @@ -31,7 +30,7 @@ namespace nexo::editor { ImGui::Text("Color"); ImGui::SameLine(); glm::vec4 color = {ambientComponent.color, 1.0f}; - Widgets::drawColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker); + ImNexo::ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker); ambientComponent.color = color; ImGui::TreePop(); } diff --git a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp index 6f1045aa4..0c37d4528 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp @@ -14,7 +14,8 @@ #include "CameraController.hpp" #include "components/Camera.hpp" -#include "Components/EntityPropertiesComponents.hpp" +#include "ImNexo/Elements.hpp" +#include "ImNexo/EntityProperties.hpp" namespace nexo::editor { @@ -22,23 +23,12 @@ namespace nexo::editor { { auto& controllerComponent = Application::getEntityComponent(entity); - if (EntityPropertiesComponents::drawHeader("##ControllerNode", "Camera Controller")) + if (ImNexo::Header("##ControllerNode", "Camera Controller")) { - ImGui::Spacing(); + 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); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - EntityPropertiesComponents::drawRowDragFloat1("Mouse sensitivity", "", &controllerComponent.mouseSensitivity); - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - ImGui::TreePop(); + ImNexo::CameraController(controllerComponent); + ImGui::TreePop(); } } } diff --git a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp index 3fe1abbd8..0f58fbcbb 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp @@ -13,11 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #include "CameraProperty.hpp" -#include "Components/EntityPropertiesComponents.hpp" -#include "Components/Widgets.hpp" #include "components/Camera.hpp" -#include "IconsFontAwesome.h" -#include "Components/Components.hpp" +#include "ImNexo/Elements.hpp" +#include "ImNexo/EntityProperties.hpp" namespace nexo::editor { @@ -25,66 +23,10 @@ namespace nexo::editor { { auto& cameraComponent = Application::getEntityComponent(entity); - if (EntityPropertiesComponents::drawHeader("##CameraNode", "Camera")) - { - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("InspectorViewPortParams", 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); - glm::vec2 viewPort = {cameraComponent.width, cameraComponent.height}; - std::vector badgeColors; - std::vector textBadgeColors; - - const bool disabled = cameraComponent.viewportLocked; - if (disabled) - ImGui::BeginDisabled(); - if (EntityPropertiesComponents::drawRowDragFloat2("Viewport size", "W", "H", &viewPort.x, -FLT_MAX, FLT_MAX, 1.0f, badgeColors, textBadgeColors, disabled)) - { - if (!cameraComponent.viewportLocked) - cameraComponent.resize(static_cast(viewPort.x), static_cast(viewPort.y)); - } - if (disabled) - ImGui::EndDisabled(); - - ImGui::TableSetColumnIndex(3); - - // Lock button - const std::string lockBtnLabel = cameraComponent.viewportLocked ? ICON_FA_LOCK "##ViewPortSettings" : ICON_FA_UNLOCK "##ViewPortSettings"; - if (Components::drawButton(lockBtnLabel)) { - cameraComponent.viewportLocked = !cameraComponent.viewportLocked; - } - - - ImGui::EndTable(); - } - - if (ImGui::BeginTable("InspectorCameraVariables", 2, ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - EntityPropertiesComponents::drawRowDragFloat1("FOV", "", &cameraComponent.fov, 30.0f, 120.0f, 0.3f); - EntityPropertiesComponents::drawRowDragFloat1("Near plane", "", &cameraComponent.nearPlane, 0.01f, 1.0f, 0.001f); - EntityPropertiesComponents::drawRowDragFloat1("Far plane", "", &cameraComponent.farPlane, 100.0f, 10000.0f, 1.0f); - - ImGui::EndTable(); - } - - - ImGui::PopStyleVar(); - - ImGui::Spacing(); - static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; - ImGui::Text("Clear Color"); - ImGui::SameLine(); - Widgets::drawColorEditor("##ColorEditor Spot light", &cameraComponent.clearColor, &colorPickerMode, &showColorPicker); - - ImGui::TreePop(); + if (ImNexo::Header("##CameraNode", "Camera")) + { + ImNexo::Camera(cameraComponent); + ImGui::TreePop(); } } } diff --git a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp index 2afe3aaac..e25fc732f 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp @@ -13,152 +13,22 @@ /////////////////////////////////////////////////////////////////////////////// #include "CameraTarget.hpp" -#include "Coordinator.hpp" #include "Definitions.hpp" #include "components/Camera.hpp" -#include "Components/EntityPropertiesComponents.hpp" -#include "components/Light.hpp" -#include "components/Transform.hpp" -#include "components/Uuid.hpp" -#include "context/Selector.hpp" +#include "ImNexo/Elements.hpp" +#include "ImNexo/EntityProperties.hpp" namespace nexo::editor { - /** - * @brief Draw a dropdown to select an entity with proper UI name display - * - * @param label The label to display - * @param targetEntity Reference to the entity ID that will be updated on selection - * @param entities Vector of available entities to choose from - * @param getNameFunc Function to get UI name from entity ID - * @return true if the selection changed, false otherwise - */ - static bool drawRowEntityDropdown(const std::string &label, ecs::Entity &targetEntity, - const std::vector& entities, - const std::function& getNameFunc) - { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(label.c_str()); - - ImGui::TableNextColumn(); - - // Create a unique ID for this widget - ImGui::PushID(label.c_str()); - - ImGui::SetNextItemWidth(-FLT_MIN); // Use all available width - - bool changed = false; - - // Build entity-name mapping - static std::vector> entityNamePairs; - static ecs::Entity lastTargetEntity = 0; - static std::vector lastEntities; - - // Only rebuild the mapping if entities list changed or target entity changed - bool needRebuild = lastTargetEntity != targetEntity || - lastEntities.size() != entities.size(); - - if (!needRebuild) { - for (size_t i = 0; i < entities.size() && !needRebuild; i++) { - needRebuild = lastEntities[i] != entities[i]; - } - } - - if (needRebuild) { - entityNamePairs.clear(); - entityNamePairs.reserve(entities.size()); - lastEntities = entities; - lastTargetEntity = targetEntity; - - for (ecs::Entity entity : entities) { - std::string name = getNameFunc(entity); - entityNamePairs.emplace_back(entity, name); - } - } - - // 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; - } - } - - // Add a "None" option if we want to allow null selection - std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; - - // Draw the combo box - if (ImGui::BeginCombo("##entity_dropdown", currentItemName.c_str())) - { - // Optional: Add a "None" option for clearing the target - if (ImGui::Selectable("None", targetEntity == ecs::MAX_ENTITIES)) { - targetEntity = ecs::MAX_ENTITIES; - changed = true; - } - - 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; - } - - if (isSelected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - - ImGui::PopID(); - return changed; - } - void CameraTarget::show(const ecs::Entity entity) { auto& targetComponent = Application::getEntityComponent(entity); - if (EntityPropertiesComponents::drawHeader("##ControllerNode", "Camera Controller")) + if (ImNexo::Header("##ControllerNode", "Camera Controller")) { ImGui::Spacing(); - - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("InspectorControllerTable", 2, - ImGuiTableFlags_SizingStretchProp)) - { - auto &app = getApp(); - auto &selector = Selector::get(); - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - const std::vector &entities = app.m_coordinator->getAllEntitiesWith< - components::TransformComponent, - ecs::Exclude, - ecs::Exclude, - ecs::Exclude, - ecs::Exclude, - ecs::Exclude>(); - - EntityPropertiesComponents::drawRowDragFloat1("Mouse sensitivity", "", &targetComponent.mouseSensitivity, 0.1f); - EntityPropertiesComponents::drawRowDragFloat1("Distance", "", &targetComponent.distance, 0.1f); - drawRowEntityDropdown( - "Target Entity", - targetComponent.targetEntity, entities, - [&app, &selector](ecs::Entity e) { - return selector.getUiHandle( - app.m_coordinator->getComponent(e).uuid, - std::to_string(e) - ); - }); - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); - ImGui::TreePop(); + ImNexo::CameraTarget(targetComponent), + ImGui::TreePop(); } - } + } } diff --git a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp index c1a3315e5..3d35e8307 100644 --- a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp @@ -13,30 +13,28 @@ /////////////////////////////////////////////////////////////////////////////// #include "DirectionalLightProperty.hpp" -#include "Components/EntityPropertiesComponents.hpp" -#include "Components/Widgets.hpp" #include "components/Light.hpp" +#include "ImNexo/Widgets.hpp" namespace nexo::editor { - void DirectionalLightProperty::show(const ecs::Entity entity) - { + void DirectionalLightProperty::show(const ecs::Entity entity) + { auto& directionalComponent = Application::getEntityComponent(entity); - - if (EntityPropertiesComponents::drawHeader("##DirectionalNode", "Directional light")) + if (ImNexo::Header("##DirectionalNode", "Directional light")) { - ImGui::Spacing(); - static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; - ImGui::Text("Color"); - ImGui::SameLine(); - glm::vec4 color = {directionalComponent.color, 1.0f}; - Widgets::drawColorEditor("##ColorEditor Directional light", &color, &colorPickerMode, &showColorPicker); - directionalComponent.color = color; - - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - if (ImGui::BeginTable("InspectorDirectionTable", 4, + ImGui::Spacing(); + static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPicker = false; + ImGui::Text("Color"); + ImGui::SameLine(); + glm::vec4 color = {directionalComponent.color, 1.0f}; + ImNexo::ColorEditor("##ColorEditor Directional light", &color, &colorPickerMode, &showColorPicker); + 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); @@ -44,12 +42,12 @@ namespace nexo::editor { ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - EntityPropertiesComponents::drawRowDragFloat3("Direction", "X", "Y", "Z", &directionalComponent.direction.x); + ImNexo::RowDragFloat3("Direction", "X", "Y", "Z", &directionalComponent.direction.x); ImGui::EndTable(); } ImGui::PopStyleVar(); - ImGui::TreePop(); + ImGui::TreePop(); } - } + } } diff --git a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp index 8bc02f816..3d6c05787 100644 --- a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp @@ -13,10 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #include "PointLightProperty.hpp" -#include "Components/EntityPropertiesComponents.hpp" -#include "Components/Widgets.hpp" #include "components/Light.hpp" #include "math/Light.hpp" +#include "ImNexo/Widgets.hpp" namespace nexo::editor { @@ -24,7 +23,7 @@ namespace nexo::editor { { auto& pointComponent = nexo::Application::getEntityComponent(entity); - if (EntityPropertiesComponents::drawHeader("##PointNode", "Point light")) + if (ImNexo::Header("##PointNode", "Point light")) { ImGui::Spacing(); static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; @@ -32,7 +31,7 @@ namespace nexo::editor { ImGui::Text("Color"); ImGui::SameLine(); glm::vec4 color = {pointComponent.color, 1.0f}; - Widgets::drawColorEditor("##ColorEditor Point light", &color, &colorPickerMode, &showColorPicker); + ImNexo::ColorEditor("##ColorEditor Point light", &color, &colorPickerMode, &showColorPicker); pointComponent.color = color; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); @@ -44,7 +43,7 @@ namespace nexo::editor { ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - EntityPropertiesComponents::drawRowDragFloat3("Position", "X", "Y", "Z", &pointComponent.pos.x); + ImNexo::RowDragFloat3("Position", "X", "Y", "Z", &pointComponent.pos.x); ImGui::EndTable(); } diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index 05588e2b2..a119a372a 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -17,14 +17,15 @@ #include "RenderProperty.hpp" #include "AEntityProperty.hpp" #include "Application.hpp" -#include "Components/EntityPropertiesComponents.hpp" -#include "Components/Widgets.hpp" #include "Framebuffer.hpp" #include "utils/ScenePreview.hpp" #include "components/Camera.hpp" #include "components/Render.hpp" #include "DocumentWindows/InspectorWindow.hpp" #include "DocumentWindows/MaterialInspector.hpp" +#include "ImNexo/Panels.hpp" +#include "ImNexo/Elements.hpp" +#include "ImNexo/Components.hpp" namespace nexo::editor { @@ -37,7 +38,7 @@ namespace nexo::editor { const float totalWidth = availSize.x; float totalHeight = availSize.y - 40; // Reserve space for bottom buttons - // Define layout: 60% for inspector, 40% for preview + // Define layout: 40% for inspector, 60% for preview const float inspectorWidth = totalWidth * 0.4f; const float previewWidth = totalWidth - inspectorWidth - 8; // Subtract spacing between panels @@ -61,7 +62,7 @@ namespace nexo::editor { ImGui::InputText("Name", materialName, IM_ARRAYSIZE(materialName)); ImGui::Spacing(); - Widgets::drawMaterialInspector(&renderable3D->material); + ImNexo::MaterialInspector(&renderable3D->material); ImGui::EndChild(); } ImGui::NextColumn(); @@ -93,7 +94,7 @@ namespace nexo::editor { // Bottom buttons - centered constexpr float buttonWidth = 120.0f; - if (ImGui::Button("OK", ImVec2(buttonWidth, 0))) + if (ImNexo::Button("OK", ImVec2(buttonWidth, 0))) { // TODO: Insert logic to create the new material @@ -110,7 +111,7 @@ namespace nexo::editor { ImGui::CloseCurrentPopup(); } ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) + if (ImNexo::Button("Cancel", ImVec2(buttonWidth, 0))) { if (scenePreviewInfo.sceneGenerated) { @@ -143,16 +144,15 @@ namespace nexo::editor { } static bool sectionOpen = true; - if (EntityPropertiesComponents::drawHeader("##RenderNode", "Render Component")) + if (ImNexo::Header("##RenderNode", "Render Component")) { - //ImGui::SetWindowFontScale(1.15f); ImGui::Text("Hide"); ImGui::SameLine(0, 12); bool hidden = !renderComponent.isRendered; ImGui::Checkbox("##HideCheckBox", &hidden); renderComponent.isRendered = !hidden; - EntityPropertiesComponents::drawToggleButtonWithSeparator("Material", §ionOpen); + ImNexo::ToggleButtonWithSeparator("Material", §ionOpen); static std::shared_ptr framebuffer = nullptr; static int entityBase = -1; if (sectionOpen) @@ -184,12 +184,12 @@ namespace nexo::editor { ImGui::Combo("##MaterialType", &selectedMaterialIndex, materialTypes, IM_ARRAYSIZE(materialTypes)); // --- Material Action Buttons --- - if (ImGui::Button("Create new material")) + if (ImNexo::Button("Create new material")) { m_popupManager.openPopup("Create new material", ImVec2(1440,900)); } ImGui::SameLine(); - if (ImGui::Button("Modify Material")) + if (ImNexo::Button("Modify Material")) { m_inspector.setSubInspectorVisibility(true); } diff --git a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp index 0375d54d6..8ac38c288 100644 --- a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp @@ -13,10 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #include "SpotLightProperty.hpp" -#include "Components/EntityPropertiesComponents.hpp" -#include "Components/Widgets.hpp" #include "components/Light.hpp" #include "math/Light.hpp" +#include "ImNexo/Widgets.hpp" namespace nexo::editor { @@ -24,15 +23,15 @@ namespace nexo::editor { { auto& spotComponent = Application::getEntityComponent(entity); - if (EntityPropertiesComponents::drawHeader("##SpotNode", "Spot light")) + if (ImNexo::Header("##SpotNode", "Spot light")) { - ImGui::Spacing(); + ImGui::Spacing(); static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; static bool showColorPicker = false; ImGui::Text("Color"); ImGui::SameLine(); glm::vec4 color = {spotComponent.color, 1.0f}; - Widgets::drawColorEditor("##ColorEditor Spot light", &color, &colorPickerMode, &showColorPicker); + ImNexo::ColorEditor("##ColorEditor Spot light", &color, &colorPickerMode, &showColorPicker); spotComponent.color = color; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); @@ -44,8 +43,8 @@ namespace nexo::editor { ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - EntityPropertiesComponents::drawRowDragFloat3("Direction", "X", "Y", "Z", &spotComponent.direction.x, -FLT_MAX, FLT_MAX, 0.1f); - EntityPropertiesComponents::drawRowDragFloat3("Position", "X", "Y", "Z", &spotComponent.pos.x, -FLT_MAX, FLT_MAX, 0.1f); + ImNexo::RowDragFloat3("Direction", "X", "Y", "Z", &spotComponent.direction.x, -FLT_MAX, FLT_MAX, 0.1f); + ImNexo::RowDragFloat3("Position", "X", "Y", "Z", &spotComponent.pos.x, -FLT_MAX, FLT_MAX, 0.1f); ImGui::EndTable(); @@ -56,7 +55,7 @@ namespace nexo::editor { ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - if (EntityPropertiesComponents::drawRowDragFloat1("Distance", "", &spotComponent.maxDistance, 1.0f, 3250.0f, 1.0f)) + if (ImNexo::RowDragFloat1("Distance", "", &spotComponent.maxDistance, 1.0f, 3250.0f, 1.0f)) { auto [lin, quad] = math::computeAttenuationFromDistance(spotComponent.maxDistance); spotComponent.linear = lin; @@ -64,9 +63,9 @@ namespace nexo::editor { } float innerCutOffDegrees = glm::degrees(glm::acos(spotComponent.cutOff)); float outerCutOffDegrees = glm::degrees(glm::acos(spotComponent.outerCutoff)); - if (EntityPropertiesComponents::drawRowDragFloat1("Inner cut off", "", &innerCutOffDegrees, 0.0f, outerCutOffDegrees, 0.5f)) + if (ImNexo::RowDragFloat1("Inner cut off", "", &innerCutOffDegrees, 0.0f, outerCutOffDegrees, 0.5f)) spotComponent.cutOff = glm::cos(glm::radians(innerCutOffDegrees)); - if (EntityPropertiesComponents::drawRowDragFloat1("Outer cut off", "", &outerCutOffDegrees, innerCutOffDegrees, 90.0f, 0.5f)) + if (ImNexo::RowDragFloat1("Outer cut off", "", &outerCutOffDegrees, innerCutOffDegrees, 90.0f, 0.5f)) spotComponent.outerCutoff = glm::cos(glm::radians(outerCutOffDegrees)); ImGui::EndTable(); diff --git a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp index 1ed39ab49..f8d642500 100644 --- a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp @@ -15,53 +15,19 @@ #include #include "TransformProperty.hpp" -#include "Components/EntityPropertiesComponents.hpp" -#include "math/Vector.hpp" +#include "ImNexo/Elements.hpp" +#include "ImNexo/EntityProperties.hpp" namespace nexo::editor { void TransformProperty::show(ecs::Entity entity) { - auto& [pos, size, quat] = Application::getEntityComponent(entity); - + auto& transformComponent = Application::getEntityComponent(entity); static glm::vec3 lastDisplayedEuler(0.0f); - if (EntityPropertiesComponents::drawHeader("##TransformNode", "Transform Component")) + if (ImNexo::Header("##TransformNode", "Transform Component")) { - // Increase cell padding so rows have more space: - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); - - 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); - - EntityPropertiesComponents::drawRowDragFloat3("Position", "X", "Y", "Z", &pos.x); - - const glm::vec3 computedEuler = math::customQuatToEuler(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 (EntityPropertiesComponents::drawRowDragFloat3("Rotation", "X", "Y", "Z", &rotation.x)) { - const glm::vec3 deltaEuler = rotation - lastDisplayedEuler; - const glm::quat deltaQuat = glm::radians(deltaEuler); - quat = glm::normalize(deltaQuat * quat); - lastDisplayedEuler = math::customQuatToEuler(quat); - rotation = lastDisplayedEuler; - } - EntityPropertiesComponents::drawRowDragFloat3("Scale", "X", "Y", "Z", &size.x); - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); + ImNexo::Transform(transformComponent, lastDisplayedEuler); ImGui::TreePop(); } } diff --git a/editor/src/DocumentWindows/InspectorWindow.cpp b/editor/src/DocumentWindows/InspectorWindow.cpp index fa3831fc4..a19139f21 100644 --- a/editor/src/DocumentWindows/InspectorWindow.cpp +++ b/editor/src/DocumentWindows/InspectorWindow.cpp @@ -20,6 +20,7 @@ #include #include "Application.hpp" +#include "ImNexo/Elements.hpp" #include "EntityProperties/RenderProperty.hpp" #include "EntityProperties/TransformProperty.hpp" #include "EntityProperties/AmbientLightProperty.hpp" @@ -30,14 +31,11 @@ #include "EntityProperties/CameraController.hpp" #include "components/Transform.hpp" #include "utils/ScenePreview.hpp" -#include "Components/EntityPropertiesComponents.hpp" #include "components/Camera.hpp" #include "components/Light.hpp" #include "context/Selector.hpp" #include "core/scene/SceneManager.hpp" -#include "Components/Widgets.hpp" - extern ImGuiID g_materialInspectorDockID; namespace nexo::editor @@ -96,10 +94,9 @@ namespace nexo::editor if (size_t spacePos = uiHandle.find(' '); spacePos != std::string::npos) uiHandle = uiHandle.substr(spacePos + 1); - if (EntityPropertiesComponents::drawHeader("##SceneNode", uiHandle)) + if (ImNexo::Header("##SceneNode", uiHandle)) { ImGui::Spacing(); - //ImGui::SetWindowFontScale(1.15f); ImGui::Columns(2, "sceneProps"); ImGui::SetColumnWidth(0, 80); diff --git a/editor/src/DocumentWindows/InspectorWindow.hpp b/editor/src/DocumentWindows/InspectorWindow.hpp index 1def4680f..b1cad6469 100644 --- a/editor/src/DocumentWindows/InspectorWindow.hpp +++ b/editor/src/DocumentWindows/InspectorWindow.hpp @@ -37,6 +37,8 @@ namespace nexo::editor { * properties in the inspector UI. */ void setup() override; + + // No-op method in this class void shutdown() override; /** @@ -47,6 +49,8 @@ namespace nexo::editor { * 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; /** diff --git a/editor/src/DocumentWindows/MaterialInspector.cpp b/editor/src/DocumentWindows/MaterialInspector.cpp index feb2c62aa..ca8861371 100644 --- a/editor/src/DocumentWindows/MaterialInspector.cpp +++ b/editor/src/DocumentWindows/MaterialInspector.cpp @@ -19,9 +19,9 @@ #include "components/Render3D.hpp" #include "utils/ScenePreview.hpp" #include "components/Camera.hpp" -#include "Components/Widgets.hpp" #include "context/Selector.hpp" #include "exceptions/Exceptions.hpp" +#include "ImNexo/Panels.hpp" namespace nexo::editor { @@ -80,7 +80,7 @@ namespace nexo::editor { return; auto materialVariant = inspectorWindow->getSubInspectorData(); if (materialVariant) - materialModified = Widgets::drawMaterialInspector(materialVariant); + materialModified = ImNexo::MaterialInspector(materialVariant); } void MaterialInspector::show() diff --git a/editor/src/DocumentWindows/MaterialInspector.hpp b/editor/src/DocumentWindows/MaterialInspector.hpp index 91f1f055e..7d63c900d 100644 --- a/editor/src/DocumentWindows/MaterialInspector.hpp +++ b/editor/src/DocumentWindows/MaterialInspector.hpp @@ -30,7 +30,11 @@ namespace nexo::editor { * 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: diff --git a/editor/src/DocumentWindows/PopupManager.cpp b/editor/src/DocumentWindows/PopupManager.cpp index b6fe80c30..73f911d1a 100644 --- a/editor/src/DocumentWindows/PopupManager.cpp +++ b/editor/src/DocumentWindows/PopupManager.cpp @@ -13,7 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "PopupManager.hpp" -#include "Components/Components.hpp" +#include "ImNexo/Elements.hpp" #include "Logger.hpp" #include @@ -92,7 +92,7 @@ namespace nexo::editor { ImVec2 pMax = ImVec2(pMin.x + size.x, pMin.y + size.y); ImDrawList* drawList = ImGui::GetWindowDrawList(); - const std::vector stops = { + 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) }, @@ -100,7 +100,7 @@ namespace nexo::editor { }; float angle = 148.0f; - Components::drawRectFilledLinearGradient(pMin, pMax, angle, stops, drawList); + ImNexo::RectFilledLinearGradient(pMin, pMax, angle, stops, drawList); return true; } diff --git a/editor/src/DocumentWindows/PopupManager.hpp b/editor/src/DocumentWindows/PopupManager.hpp index d163f9847..495746d12 100644 --- a/editor/src/DocumentWindows/PopupManager.hpp +++ b/editor/src/DocumentWindows/PopupManager.hpp @@ -30,21 +30,38 @@ namespace nexo::editor { 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; - PopupCallback callback = nullptr; - ImVec2 size = ImVec2(0, 0); + 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 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)); /** @@ -83,6 +100,14 @@ namespace nexo::editor { */ void closePopupInContext() 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); private: diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index c1a308d3c..a17d0b1ef 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -32,17 +32,21 @@ #include "components/Uuid.hpp" #include "context/Selector.hpp" #include "LightFactory.hpp" -#include "Components/Widgets.hpp" +#include "ImNexo/Panels.hpp" namespace nexo::editor { SceneTreeWindow::~SceneTreeWindow() = default; void SceneTreeWindow::setup() - {} + { + // Nothing to setup + } void SceneTreeWindow::shutdown() - {} + { + // Nothing to shutdown + } void SceneTreeWindow::handleRename(SceneObject &obj) { @@ -164,14 +168,14 @@ namespace nexo::editor { const auto &editorScenes = m_windowRegistry.getWindows(); for (const auto &scene : editorScenes) { if (scene->getSceneId() == obj.data.sceneProperties.sceneId) { - Widgets::drawCameraCreator(obj.data.sceneProperties.sceneId, scene->getViewportSize()); + ImNexo::CameraInspector(obj.data.sceneProperties.sceneId, scene->getViewportSize()); break; } } }, ImVec2(1440,900)); } - ImGui::EndMenu(); // <-- matches the BeginMenu("Add Entity") + ImGui::EndMenu(); } } @@ -393,7 +397,7 @@ namespace nexo::editor { ImGui::Text("Enter Scene Name:"); ImGui::InputText("##SceneName", sceneNameBuffer, sizeof(sceneNameBuffer)); - if (ImGui::Button("Create")) { + if (ImNexo::Button("Create")) { if (handleSceneCreation(sceneNameBuffer)) { memset(sceneNameBuffer, 0, sizeof(sceneNameBuffer)); m_popupManager.closePopupInContext(); @@ -401,7 +405,7 @@ namespace nexo::editor { } ImGui::SameLine(); - if (ImGui::Button("Cancel")) + if (ImNexo::Button("Cancel")) m_popupManager.closePopupInContext(); m_popupManager.closePopup(); diff --git a/editor/src/DocumentWindows/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow.hpp index 48e1680fc..523df00fd 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.hpp @@ -29,14 +29,26 @@ 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. + */ struct SceneProperties { - scene::SceneId sceneId; - WindowId windowId; + scene::SceneId sceneId; ///< The unique identifier for the scene + WindowId windowId; ///< The associated window identifier in the UI }; + /** + * @brief Links an entity with its parent scene information. + * + * Combines entity ID with scene properties to maintain the hierarchical + * relationship between entities and their containing scenes. + */ struct EntityProperties { - SceneProperties sceneProperties; - ecs::Entity entity; + SceneProperties sceneProperties; ///< Properties of the scene containing this entity + ecs::Entity entity; ///< The entity identifier }; /** @@ -80,7 +92,10 @@ namespace nexo::editor { using ADocumentWindow::ADocumentWindow; ~SceneTreeWindow() override; + // No-op method in this class void setup() override; + + // No-op method in this class void shutdown() override; /** @@ -248,6 +263,14 @@ namespace nexo::editor { */ bool handleSelection(const SceneObject &obj, const std::string &uniqueLabel, ImGuiTreeNodeFlags baseFlags) const; + /** + * @brief Handles camera preview tooltips when hovering over camera nodes. + * + * When hovering over a camera node, renders a preview tooltip showing the camera's + * view and enables rendering for that camera temporarily. + * + * @param obj The scene object (camera) being hovered over. + */ void handleHovering(const SceneObject &obj) const; /** @@ -280,9 +303,43 @@ namespace nexo::editor { * @param obj The scene object representing the camera to be deleted. */ void cameraSelected(const SceneObject &obj) const; + + /** + * @brief Handles camera preview when hovering over a camera node. + * + * Renders a small preview of what the camera sees when the cursor + * hovers over the camera node in the scene tree. + * + * @param obj The scene object representing the camera being hovered. + */ void cameraHovered(const SceneObject &obj) const; + + /** + * @brief Displays context menu options for an entity. + * + * Shows options like "Delete Entity" when right-clicking on an entity + * in the scene tree. Handles entity deletion when selected. + * + * @param obj The scene object representing the entity. + */ void entitySelected(const SceneObject &obj) const; + + /** + * @brief Renders a node and its children in the scene tree. + * + * Handles the recursive display of scene tree nodes, including selection, + * renaming, and context menus for each node type. + * + * @param object The scene object to render. + */ void showNode(SceneObject &object); + + /** + * @brief Shows the context menu for the scene tree background. + * + * Displays options like "Create Scene" when right-clicking on an empty + * area of the scene tree. + */ void sceneContextMenu(); /** @@ -294,7 +351,28 @@ namespace nexo::editor { * The popup is closed either upon successful scene creation or when the "Cancel" button is clicked. */ void sceneCreationMenu(); + + /** + * @brief Creates a new scene with the provided name. + * + * Validates the scene name, creates a new EditorScene object, and configures + * appropriate docking settings for the new scene window. + * + * @param newSceneName The name for the new scene. + * @return true if the scene was created successfully, false otherwise. + */ bool handleSceneCreation(const std::string &newSceneName); + + /** + * @brief Sets up docking for a new scene window. + * + * Creates a new docking node and configures it to contain the specified windows, + * positioning it at the location of the existing floating window. + * + * @param floatingWindowName The name of the existing floating window to use as a reference. + * @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); }; } diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 861ffc85d..1825a2b85 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -21,12 +21,12 @@ #include "Path.hpp" #include "backends/ImGuiBackend.hpp" #include "IconsFontAwesome.h" +#include "ImNexo/Elements.hpp" #include "imgui.h" #include #include #include -#include ImGuiID g_materialInspectorDockID = 0; @@ -348,7 +348,7 @@ namespace nexo::editor { ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBackground); - const std::vector stops = { + 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) }, @@ -357,7 +357,7 @@ namespace nexo::editor { float angle = 148; - Components::drawRectFilledLinearGradient(viewport->Pos, + ImNexo::RectFilledLinearGradient(viewport->Pos, ImVec2(viewport->Pos.x + viewport->Size.x, viewport->Pos.y + viewport->Size.y), angle, stops); ImGui::End(); diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp new file mode 100644 index 000000000..c1c227426 --- /dev/null +++ b/editor/src/ImNexo/Components.cpp @@ -0,0 +1,520 @@ +//// Components.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: 17/02/2025 +// Description: Source file for the utilitary ImGui functions +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Components.hpp" +#include "Elements.hpp" +#include "Guard.hpp" +#include "Utils.hpp" +#include "tinyfiledialogs.h" + +#include +#include +#include +#include + +namespace ImNexo { + + bool ButtonWithIconAndText( + const std::string &uniqueId, + const std::string &icon, + const std::string &label, + const ImVec2 &itemSize + ) + { + IdGuard idGuard(uniqueId); + std::string invisButtonLabel = "##" + uniqueId; + + if (ImGui::InvisibleButton(invisButtonLabel.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 + ); + + // Draw the icon at 25% from top + 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 + ); + + // Use drawButtonBorder instead of direct drawing + 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) + { + 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 + ); + + 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 + ); + } + + + bool TextureButton(const std::string &label, std::shared_ptr &texture) + { + bool textureModified = 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 (ImGui::ImageButton(textureButton.c_str(), textureId, previewSize)) + { + const char* filePath = tinyfd_openFileDialog( + "Open Texture", + "", + 0, + nullptr, + nullptr, + 0 + ); + + if (filePath) + { + const std::string path(filePath); + std::shared_ptr newTexture = nexo::renderer::Texture2D::create(path); + if (newTexture) + { + texture = newTexture; + textureModified = 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 textureModified; + } + + bool IconGradientButton( + const std::string& uniqueId, + const std::string& icon, + const ImVec2& size, + const std::vector& gradientStops, + float gradientAngle, + const ImU32 borderColor, + const ImU32 borderColorHovered, + const ImU32 borderColorActive, + const ImU32 iconColor + ) + { + IdGuard idGuard(uniqueId); + + // Create invisible button for interaction + bool clicked = ImGui::InvisibleButton(("##" + uniqueId).c_str(), size); + + // Get button rectangle coordinates + auto [p_min, p_max] = utils::getItemRect(); + + // Draw the gradient background + 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 + ); + + return clicked; + } + + bool RowEntityDropdown( + const std::string &label, + nexo::ecs::Entity &targetEntity, + const std::vector& entities, + const std::function& getNameFunc + ) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(label.c_str()); + + ImGui::TableNextColumn(); + IdGuard idGuard(label); + + bool changed = false; + + // Build entity-name mapping + static std::vector> entityNamePairs; + static nexo::ecs::Entity lastTargetEntity = 0; + static std::vector lastEntities; + + // Only rebuild the mapping if entities list changed or target entity changed + bool needRebuild = lastTargetEntity != targetEntity || lastEntities.size() != entities.size(); + + if (!needRebuild) { + for (size_t i = 0; i < entities.size() && !needRebuild; i++) { + needRebuild = lastEntities[i] != entities[i]; + } + } + + if (needRebuild) { + entityNamePairs.clear(); + entityNamePairs.reserve(entities.size()); + lastEntities = entities; + lastTargetEntity = targetEntity; + + for (nexo::ecs::Entity entity : entities) { + std::string name = getNameFunc(entity); + entityNamePairs.emplace_back(entity, name); + } + } + + // 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; + } + } + + // Add a "None" option if we want to allow null selection + std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; + + // 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; + } + + 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; + } + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + return changed; + } + + bool RowDragFloat(const Channels &channels) + { + bool modified = false; + + for (unsigned int i = 0; i < channels.count; ++i) + { + ImGui::TableNextColumn(); + + // Draw the badge (if provided) + 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); + ImGui::SameLine(0, 2); + } + + // Draw the drag float control + const auto &slider = channels.sliders[i]; + 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; + } + + return modified; + } + + bool RowDragFloat1( + const char *uniqueLabel, + const std::string &badgeLabel, + float *value, + float minValue, + float maxValue, + float speed + ) + { + ImGui::TableNextRow(); + + ChannelLabel chanLabel; + chanLabel.label = uniqueLabel; + RowLabel(chanLabel); + + // Create channels structure for a single value + std::string labelId = "##X" + std::string(uniqueLabel); + std::string badgeId = badgeLabel.empty() ? "" : badgeLabel + "##" + uniqueLabel; + + // 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.push_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 + ) + { + ImGui::TableNextRow(); + + ChannelLabel chanLabel; + chanLabel.label = uniqueLabel; + RowLabel(chanLabel); + + // Setup badge colors with defaults if not provided + if (badgeColor.size() < 2) { + badgeColor = {IM_COL32(102, 28, 28, 255), IM_COL32(0, 80, 0, 255)}; + } + + if (textBadgeColor.size() < 2) { + textBadgeColor = {IM_COL32(255, 180, 180, 255), IM_COL32(180, 255, 180, 255)}; + } + + // Base ID for controls + std::string baseId = uniqueLabel; + + // Set up channels structure + Channels channels; + channels.count = 2; + + // 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]} + }; + + // 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); + + // 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"} + }; + + return RowDragFloat(channels); + } + + // Creates standard badge colors for X/Y/Z axes if not provided + 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 + }; + } + + if (textBadgeColors.empty()) { + textBadgeColors = { + IM_COL32(255, 180, 180, 255), // X - Light Red + IM_COL32(180, 255, 180, 255), // Y - Light Green + IM_COL32(180, 180, 255, 255) // Z - Light Blue + }; + } + } + + 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 + ) + { + ImGui::TableNextRow(); + + // Setup the label for the row + ChannelLabel chanLabel; + chanLabel.label = uniqueLabel; + + // Setup standard axis colors if not provided + setupAxisBadgeColors(badgeColors, textBadgeColors); + + // Base ID for controls + 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); + + // 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"} + }; + + if (!chanLabel.label.empty()) + RowLabel(chanLabel); + + return RowDragFloat(channels); + } + + 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; + *toggled = !(*toggled); + } + + // Get button bounds and draw the arrow + auto [p_min, p_max] = utils::getItemRect(); + 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); + Arrow(center, *toggled, arrowColor, arrowSize); + + ImGui::SameLine(); + + // Draw separator line + 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::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) + ); + + return clicked; + } +} diff --git a/editor/src/ImNexo/Components.hpp b/editor/src/ImNexo/Components.hpp new file mode 100644 index 000000000..544cbb8b0 --- /dev/null +++ b/editor/src/ImNexo/Components.hpp @@ -0,0 +1,210 @@ +//// Components.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: Mehdy MORVAN +// Date: 17/02/2025 +// Description: Header file for the utilitary ImGui functions +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include +#include +#include + +#include "ecs/Coordinator.hpp" +#include "renderer/Texture.hpp" +#include "Elements.hpp" + +namespace ImNexo { + + /** + * @brief Creates a button with both an icon and text label. + * + * Renders a custom button with an icon positioned at the top portion of the button + * and a text label below it. The text automatically wraps if it's too wide for the button. + * + * @param uniqueId A unique identifier string for ImGui to track this widget + * @param icon The icon string to display (typically a FontAwesome character) + * @param label The text label to display below the icon + * @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); + + /** + * @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 clicked, opens a file dialog to select a new texture. If a new texture is loaded, + * the passed texture pointer is updated and the function returns true. + * + * @param label A unique label identifier for the button. + * @param texture A shared pointer to the renderer::Texture2D that holds the texture. + * @return true if the texture was modified; false otherwise. + */ + bool TextureButton(const std::string &label, std::shared_ptr &texture); + + /** + * @brief Creates a customizable gradient button with a centered icon. + * + * Renders a button with a linear gradient background, customizable border colors + * for different states (normal, hovered, active), and a centered icon. + * + * @param uniqueId A unique identifier string for ImGui to track this widget + * @param icon The icon string to display (typically a FontAwesome character) + * @param size Button dimensions (width, height) + * @param gradientStops Array of gradient color stops defining the background gradient + * @param gradientAngle Angle of the linear gradient in degrees + * @param borderColor Color of the button border in normal state + * @param borderColorHovered Color of the button border when hovered + * @param borderColorActive Color of the button border when active/pressed + * @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, + const ImU32 borderColor = IM_COL32(100, 100, 120, 255), + const ImU32 borderColorHovered = IM_COL32(150, 150, 200, 255), + const ImU32 borderColorActive = IM_COL32(200, 200, 255, 255), + const ImU32 iconColor = IM_COL32(255, 255, 255, 255) + ); + + /** + * @brief Displays a dropdown to select an entity from a list. + * + * Creates a row in a table with a label and dropdown menu showing + * available entities. Updates the target entity when a selection is made. + * + * @param label Text label displayed next to the dropdown + * @param targetEntity Reference to the entity variable that will be updated with the selection + * @param entities Vector of available entities to choose from + * @param getNameFunc Function that converts an entity ID to a displayable name string + * @return true if an entity was selected (value changed), false otherwise + */ + bool RowEntityDropdown(const std::string &label, nexo::ecs::Entity &targetEntity, + const std::vector& entities, + const std::function& getNameFunc); + + /** + * @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 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); +} diff --git a/editor/src/ImNexo/Elements.cpp b/editor/src/ImNexo/Elements.cpp new file mode 100644 index 000000000..c5cfff903 --- /dev/null +++ b/editor/src/ImNexo/Elements.cpp @@ -0,0 +1,400 @@ +//// Elements.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: 18/04/2025 +// Description: Source file for the ui elements +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Elements.hpp" +#include "Guard.hpp" +#include "Utils.hpp" + +#include + +namespace ImNexo { + + /** + * @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, + float verticalPosition, + float horizontalPosition, + ImFont* font + ) { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + // Use specified font or current font + if (font) { + ImGui::PushFont(font); + } + + // Calculate icon size with scale + ImGui::SetWindowFontScale(scale); + ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str()); + ImGui::SetWindowFontScale(1.0f); + + // Calculate position + ImVec2 iconPos = ImVec2( + p_min.x + (p_max.x - p_min.x - iconSize.x) * horizontalPosition, + p_min.y + (p_max.y - p_min.y) * verticalPosition - iconSize.y * 0.5f + ); + + // Draw the icon + drawList->AddText( + font ? font : ImGui::GetFont(), + ImGui::GetFontSize() * scale, + iconPos, color, + icon.c_str() + ); + + if (font) { + ImGui::PopFont(); + } + } + + /** + * @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 + ) { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + float textHeight = ImGui::GetFontSize(); + float wrapWidth = p_max.x - p_min.x - 10.0f; // 5px padding on each side + float textY = p_min.y + (p_max.y - p_min.y) * verticalPosition; + + // Calculate text size to determine if wrapping is needed + ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); + + if (textSize.x > wrapWidth) { + // Try to find a space to split the text + size_t splitPos = text.find(" "); + + if (splitPos != std::string::npos) { + // Split text into two lines + std::string line1 = text.substr(0, splitPos); + std::string line2 = text.substr(splitPos + 1); + + // Calculate positions for both lines + ImVec2 line1Pos = ImVec2( + p_min.x + (p_max.x - p_min.x - ImGui::CalcTextSize(line1.c_str()).x) * 0.5f, + textY - textHeight * 0.5f + ); + + ImVec2 line2Pos = ImVec2( + p_min.x + (p_max.x - p_min.x - ImGui::CalcTextSize(line2.c_str()).x) * 0.5f, + textY + textHeight * 0.5f + ); + + // Draw both lines + drawList->AddText(line1Pos, color, line1.c_str()); + drawList->AddText(line2Pos, color, line2.c_str()); + } else { + // No space to split, draw single line (might be clipped) + ImVec2 textPos = ImVec2( + p_min.x + (p_max.x - p_min.x - textSize.x) * 0.5f, + textY - textHeight * 0.5f + ); + drawList->AddText(textPos, color, text.c_str()); + } + } else { + // No wrapping needed, draw centered + ImVec2 textPos = ImVec2( + p_min.x + (p_max.x - p_min.x - textSize.x) * 0.5f, + textY - textHeight * 0.5f + ); + drawList->AddText(textPos, color, text.c_str()); + } + } + + bool Button( + const std::string &label, + const ImVec2 &size, + const ImU32 bg, + const ImU32 bgHovered, + const ImU32 bgActive, + const ImU32 txtColor + ) + { + StyleGuard colorGuard(ImGuiCol_Button, bg); + colorGuard + .push(ImGuiCol_ButtonHovered, bgHovered) + .push(ImGuiCol_ButtonActive, bgActive) + .push(ImGuiCol_Text, txtColor); + + return ImGui::Button(label.c_str(), size); + } + + void ButtonBorder( + const ImU32 borderColor, + const ImU32 borderColorHovered, + const ImU32 borderColorActive, + const float rounding, + const ImDrawFlags flags, + const float thickness + ) + { + auto [p_min, p_max] = utils::getItemRect(); + ImU32 color = borderColor ? borderColor : ImGui::GetColorU32(ImGuiCol_Button); + + if (ImGui::IsItemHovered()) + color = borderColorHovered ? borderColorHovered : ImGui::GetColorU32(ImGuiCol_ButtonHovered); + if (ImGui::IsItemActive()) + color = borderColorActive ? borderColorActive : ImGui::GetColorU32(ImGuiCol_ButtonActive); + + ImGui::GetWindowDrawList()->AddRect(p_min, p_max, color, rounding, flags, thickness); + } + + 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 + ) + { + StyleGuard colorGuard(ImGuiCol_FrameBg, bg); + colorGuard + .push(ImGuiCol_FrameBgHovered, bgHovered) + .push(ImGuiCol_FrameBgActive, bgActive) + .push(ImGuiCol_Text, textColor); + + return ImGui::DragFloat(label.c_str(), values, speed, min, max, format.c_str()); + } + + /** + * @brief Sanitizes gradient stops to ensure proper ordering and range + */ + static void sanitizeGradientStops(std::vector& stops) + { + if (stops.size() < 2) + return; + + // Sort stops by position if needed + std::sort(stops.begin(), stops.end(), + [](const GradientStop& a, const GradientStop& b) { + return a.pos < b.pos; + }); + + // Clamp positions to valid range + float stop_max = 0.0f; + for (auto& [pos, color] : stops) { + // Clamp stop position to [0.0f, 1.0f] + pos = std::clamp(pos, 0.0f, 1.0f); + + // Ensure stops are monotonically increasing + pos = std::max(pos, stop_max); + stop_max = pos; + } + + // if first stop does not start at 0.0f, we need to add a stop at 0.0f + if (stops[0].pos > 0.0f) { + stops.insert(stops.begin(), { 0.0f, stops[0].color }); + } + // if last stop does not end at 1.0f, we need to add a stop at 1.0f + if (stops.back().pos < 1.0f) { + stops.push_back({ 1.0f, stops.back().color }); + } + } + + void RectFilledLinearGradient( + const ImVec2& pMin, + const ImVec2& pMax, + float angle, + std::vector stops, + ImDrawList* drawList + ) + { + if (!drawList) + drawList = ImGui::GetWindowDrawList(); + + // Check if we have at least two stops. + if (stops.size() < 2) + return; + + angle -= 90.0f; // rotate 90 degrees to match the CSS gradients rotations + + // Convert angle from degrees to radians and normalize + angle = fmodf(angle, 360.0f); + if (angle < 0.0f) + angle += 360.0f; + angle = angle * std::numbers::pi_v / 180.0f; + + const auto gradDir = ImVec2(cosf(angle), sinf(angle)); + + // Define rectangle polygon (clockwise order). + const std::vector rectPoly = { pMin, ImVec2(pMax.x, pMin.y), pMax, ImVec2(pMin.x, pMax.y) }; + + // Compute projection range (d_min, d_max) for the rectangle. + float d_min = std::numeric_limits::max(); + float d_max = -std::numeric_limits::max(); + for (auto const& v : rectPoly) { + const float d = ImDot(v, gradDir); + d_min = std::min(d_min, d); + d_max = std::max(d_max, d); + } + + // Sanitize gradient stops + sanitizeGradientStops(stops); + + // For each segment defined by consecutive stops: + for (long i = static_cast(stops.size()) - 1; i > 0; i--) { + const long posStart = i - 1; + const long posEnd = i; + // Compute threshold projections for the current segment. + const float segStart = d_min + stops[posStart].pos * (d_max - d_min); + const float segEnd = d_min + stops[posEnd].pos * (d_max - d_min); + + // Start with the whole rectangle. + std::vector segPoly = rectPoly; + std::vector tempPoly; + // Clip against lower boundary: d >= seg_start + utils::clipPolygonWithLine(segPoly, gradDir, segStart, tempPoly); + segPoly = tempPoly; // copy result + // Clip against upper boundary: d <= seg_end + // To clip with an upper-bound, invert the normal. + utils::clipPolygonWithLine(segPoly, ImVec2(-gradDir.x, -gradDir.y), -segEnd, tempPoly); + segPoly = tempPoly; + + if (segPoly.empty()) + continue; + + // Now, compute per-vertex colors for the segment polygon. + std::vector polyColors; + polyColors.reserve(segPoly.size()); + for (const ImVec2& v : segPoly) { + // Compute projection for the vertex. + const float d = ImDot(v, gradDir); + // Map projection to [0,1] relative to current segment boundaries. + const float t = (d - segStart) / (segEnd - segStart); + // Interpolate the color between the two stops. + polyColors.push_back(utils::imLerpColor(stops[posStart].color, stops[posEnd].color, t)); + } + + // Draw the filled and colored polygon. + utils::fillConvexPolygon(drawList, segPoly, polyColors); + } + } + + bool Header(const std::string &label, std::string_view headerText) + { + StyleVarGuard styleGuard(ImGuiStyleVar_FramePadding, + ImVec2(ImGui::GetStyle().FramePadding.x, 3.0f)); + + bool open = ImGui::TreeNodeEx( + label.c_str(), + ImGuiTreeNodeFlags_DefaultOpen | + ImGuiTreeNodeFlags_Framed | + ImGuiTreeNodeFlags_AllowItemOverlap | + ImGuiTreeNodeFlags_SpanAvailWidth + ); + + // Get the bounding box and draw centered text + auto [p_min, p_max] = utils::getItemRect(); + ImVec2 textPos = utils::calculateCenteredTextPosition(headerText.data(), p_min, p_max); + + ImGui::GetWindowDrawList()->AddText( + ImGui::GetFont(), + ImGui::GetFontSize(), + textPos, + ImGui::GetColorU32(ImGuiCol_Text), + headerText.data() + ); + + return open; + } + + void RowLabel(const ChannelLabel &rowLabel) + { + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(rowLabel.label.c_str()); + } + + // Helper method to draw arrow indicators + void Arrow(const ImVec2& center, bool isExpanded, ImU32 color, float size) + { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + if (isExpanded) { + // Downward arrow (expanded) + draw_list->AddTriangleFilled( + ImVec2(center.x - size, center.y - size), + ImVec2(center.x + size, center.y - size), + ImVec2(center.x, center.y + size), + color); + } else { + // Rightward arrow (collapsed) + draw_list->AddTriangleFilled( + ImVec2(center.x - size, center.y - size), + ImVec2(center.x - size, center.y + size), + ImVec2(center.x + size, center.y), + color); + } + } + + void CustomSeparatorText( + const std::string &text, + const float textPadding, + const float leftSpacing, + const float thickness, + ImU32 lineColor, + ImU32 textColor + ) + { + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const float availWidth = ImGui::GetContentRegionAvail().x; + const float textWidth = ImGui::CalcTextSize(text.c_str()).x; + + // Compute the length of each line. Clamp to zero if the region is too small. + float lineWidth = (availWidth - textWidth - 2 * textPadding) * leftSpacing; + lineWidth = std::max(lineWidth, 0.0f); + + // Compute Y coordinate to draw lines so they align with the text center. + const float lineY = pos.y + ImGui::GetTextLineHeight() * 0.5f; + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + const ImVec2 lineStart(pos.x, lineY); + const ImVec2 lineEnd(pos.x + lineWidth, lineY); + draw_list->AddLine(lineStart, lineEnd, lineColor, thickness); + + const ImVec2 textPos(pos.x + lineWidth + textPadding, pos.y); + draw_list->AddText(textPos, textColor, text.c_str()); + + const ImVec2 rightLineStart(pos.x + lineWidth + textPadding + textWidth + textPadding, lineY); + const ImVec2 rightLineEnd(pos.x + availWidth, lineY); + draw_list->AddLine(rightLineStart, rightLineEnd, lineColor, thickness); + + ImGui::Dummy(ImVec2(0, ImGui::GetTextLineHeight())); + } +} diff --git a/editor/src/ImNexo/Elements.hpp b/editor/src/ImNexo/Elements.hpp new file mode 100644 index 000000000..342fad593 --- /dev/null +++ b/editor/src/ImNexo/Elements.hpp @@ -0,0 +1,287 @@ +//// Elements.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: Mehdy MORVAN +// Date: 18/04/2025 +// Description: Header file for the ui elements +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include +#include +#include + +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 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 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 + }; + + /** + * @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 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 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 + ); + + /** + * @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. + * @param pMin Upper left corner position of the rectangle + * @param pMax Lower right corner position of the rectangle + * @param angle Angle of the gradient in degrees (0.0f = down, 90.0f = right, 180.0f = up, 270.0f = left) + * @param stops Vector of gradient stops, each defined by a position (0.0f to 1.0f) and a color + */ + 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 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 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); +} diff --git a/editor/src/ImNexo/EntityProperties.cpp b/editor/src/ImNexo/EntityProperties.cpp new file mode 100644 index 000000000..c1b53a02e --- /dev/null +++ b/editor/src/ImNexo/EntityProperties.cpp @@ -0,0 +1,178 @@ +//// EntityProperties.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: 22/02/2025 +// Description: Source file for the entity properties components +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Widgets.hpp" +#include "Guard.hpp" +#include "Utils.hpp" +#include "EntityProperties.hpp" +#include "IconsFontAwesome.h" +#include "Nexo.hpp" +#include "context/Selector.hpp" +#include "components/Uuid.hpp" +#include "components/Light.hpp" +#include "components/Transform.hpp" +#include "math/Vector.hpp" + +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, size, quat] = transformComponent; + + 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 = nexo::math::customQuatToEuler(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::radians(deltaEuler); + quat = glm::normalize(deltaQuat * quat); + lastDisplayedEuler = nexo::math::customQuatToEuler(quat); + rotation = lastDisplayedEuler; + } + 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); + glm::vec2 viewPort = {cameraComponent.width, cameraComponent.height}; + std::vector badgeColors; + std::vector textBadgeColors; + + const bool disabled = cameraComponent.viewportLocked; + if (disabled) + ImGui::BeginDisabled(); + if (RowDragFloat2("Viewport size", "W", "H", &viewPort.x, -FLT_MAX, FLT_MAX, 1.0f, badgeColors, textBadgeColors, disabled)) + { + if (!cameraComponent.viewportLocked) + cameraComponent.resize(static_cast(viewPort.x), static_cast(viewPort.y)); + } + if (disabled) + ImGui::EndDisabled(); + + ImGui::TableSetColumnIndex(3); + + // Lock button + 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); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + RowDragFloat1("FOV", "", &cameraComponent.fov, 30.0f, 120.0f, 0.3f); + RowDragFloat1("Near plane", "", &cameraComponent.nearPlane, 0.01f, 1.0f, 0.001f); + RowDragFloat1("Far plane", "", &cameraComponent.farPlane, 100.0f, 10000.0f, 1.0f); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + + ImGui::Spacing(); + static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; + 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) + { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); + if (ImGui::BeginTable("InspectorControllerTable", 2, + ImGuiTableFlags_SizingStretchProp)) + { + auto &app = nexo::getApp(); + auto &selector = nexo::editor::Selector::get(); + ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + const std::vector &entities = app.m_coordinator->getAllEntitiesWith< + 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, + [&app, &selector](nexo::ecs::Entity e) { + return selector.getUiHandle( + app.m_coordinator->getComponent(e).uuid, + std::to_string(e) + ); + } + ); + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + + 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); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + RowDragFloat1("Mouse sensitivity", "", &cameraControllerComponent.mouseSensitivity); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + +} diff --git a/editor/src/ImNexo/EntityProperties.hpp b/editor/src/ImNexo/EntityProperties.hpp new file mode 100644 index 000000000..b484ddd5d --- /dev/null +++ b/editor/src/ImNexo/EntityProperties.hpp @@ -0,0 +1,80 @@ +//// EntityPropertiesComponents.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: Mehdy MORVAN +// Date: 22/05/2025 +// Description: Header file for the entity properties components +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include +#include + +#include "components/Transform.hpp" +#include "components/Camera.hpp" + +namespace ImNexo { + + /** + * @brief Renders and handles the transform component editor UI. + * + * Creates a table-based editor for position, rotation, and scale values of a transform component. + * Rotation is handled specially to convert between quaternion (internal) and euler angles (UI display). + * When the user modifies euler angles, the function calculates the delta from the last displayed euler + * angles and applies a corresponding rotation to the master quaternion. + * + * @param transformComponent Reference to the transform component being edited + * @param lastDisplayedEuler Reference to vector storing the last displayed euler angles for computing deltas + */ + void Transform(nexo::components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler); + + /** + * @brief Renders and handles the camera component editor UI. + * + * Creates a table-based editor for camera parameters, including: + * - Viewport size (width/height) with optional locking + * - Field of view (FOV) adjustment + * - Near and far clipping planes + * - Camera clear color with color picker + * + * The viewport size can be locked to prevent accidental changes, which is useful + * when the camera is being used in a specific context that requires fixed dimensions. + * + * @param cameraComponent Reference to the camera component being edited + */ + void Camera(nexo::components::CameraComponent &cameraComponent); + + /** + * @brief Renders and handles the camera target component editor UI. + * + * Creates a table-based editor for a camera target component, which controls + * a camera that orbits around a target entity. The editor includes: + * - Mouse sensitivity for orbit control + * - Distance from camera to target + * - Target entity selection dropdown showing available entities + * + * The entity dropdown filters out cameras and lights to show only valid targets. + * + * @param cameraTargetComponent Reference to the camera target component being edited + */ + void CameraTarget(nexo::components::PerspectiveCameraTarget &cameraTargetComponent); + + /** + * @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 + * 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); + +} diff --git a/editor/src/ImNexo/Guard.hpp b/editor/src/ImNexo/Guard.hpp new file mode 100644 index 000000000..cbd7aaee2 --- /dev/null +++ b/editor/src/ImNexo/Guard.hpp @@ -0,0 +1,192 @@ +//// Guard.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: Mehdy MORVAN +// Date: 18/04/2025 +// Description: Header for the utils guard class +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include +#include +#include + +namespace ImNexo { + /** + * @brief Guard class for managing ImGui style colors. + * + * Automatically pushes ImGui style colors on construction and pops them on destruction, + * 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(ImGuiCol_ col, ImU32 color) { + if (color != 0) { + m_colorIndices.push_back(col); + ImGui::PushStyleColor(col, color); + } + } + + /** + * @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(ImGuiCol_ col, 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. + * + * Automatically pushes ImGui style variables on construction and pops them on destruction, + * 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(ImGuiStyleVar_ var, 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(ImGuiStyleVar_ var, float value) { + m_varCount++; + ImGui::PushStyleVar(var, value); + } + + /** + * @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(ImGuiStyleVar_ var, 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(ImGuiStyleVar_ var, 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()); + } + + /** + * @brief Destructor that automatically pops the pushed ID. + */ + ~IdGuard() { + ImGui::PopID(); + } + }; + + /** + * @brief Guard class for managing ImGui font scaling. + * + * 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(float scale) : m_scale(scale) { + ImGui::SetWindowFontScale(scale); + } + + /** + * @brief Destructor that automatically resets the font scale to default. + */ + ~FontScaleGuard() { + ImGui::SetWindowFontScale(1.0f); + } + private: + float m_scale; ///< Stores the applied scale factor + }; +} diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp new file mode 100644 index 000000000..e2485e68d --- /dev/null +++ b/editor/src/ImNexo/Panels.cpp @@ -0,0 +1,343 @@ +//// Panels.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: 18/04/2025 +// Description: Source file for the ui panels +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Nexo.hpp" +#include "Panels.hpp" +#include "Elements.hpp" +#include "Widgets.hpp" +#include "EntityProperties.hpp" +#include "CameraFactory.hpp" +#include "Path.hpp" +#include "IconsFontAwesome.h" +#include "components/Uuid.hpp" +#include "context/Selector.hpp" + +namespace ImNexo { + 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" }; + float availableWidth = ImGui::GetContentRegionAvail().x; + + ImGui::SetNextItemWidth(availableWidth); + if (ImGui::Combo("##RenderingModeCombo", ¤tRenderingModeIndex, renderingModeOptions, IM_ARRAYSIZE(renderingModeOptions))) + { + //TODO: implement rendering mode + } + + // --- Albedo texture --- + static ImGuiColorEditFlags colorPickerModeAlbedo = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPickerAlbedo = false; + modified = TextureButton("Albedo texture", material->albedoTexture) || modified; + ImGui::SameLine(); + modified = ColorEditor("##ColorEditor Albedo texture", &material->albedoColor, &colorPickerModeAlbedo, &showColorPickerAlbedo) || modified; + + // --- Specular texture --- + static ImGuiColorEditFlags colorPickerModeSpecular = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPickerSpecular = false; + modified = TextureButton("Specular texture", material->metallicMap) || modified; + 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, ImVec2 sceneViewportSize) + { + auto &app = nexo::getApp(); + nexo::renderer::FramebufferSpecs framebufferSpecs; + framebufferSpecs.attachments = { + nexo::renderer::FrameBufferTextureFormats::RGBA8, nexo::renderer::FrameBufferTextureFormats::RED_INTEGER, nexo::renderer::FrameBufferTextureFormats::Depth + }; + const ImVec2 availSize = ImGui::GetContentRegionAvail(); + const float totalWidth = availSize.x; + 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 panel + framebufferSpecs.width = static_cast(sceneViewportSize.x); + framebufferSpecs.height = static_cast(sceneViewportSize.y); + const auto renderTarget = nexo::renderer::Framebuffer::create(framebufferSpecs); + 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(static_cast(defaultCamera)); + + nexo::components::Material billboardMat{}; + std::shared_ptr cameraIconTexture = nexo::renderer::Texture2D::create(nexo::Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png").string()); + billboardMat.albedoTexture = cameraIconTexture; + auto billboard = std::make_shared(); + auto renderable = std::make_shared(billboardMat, billboard); + nexo::components::RenderComponent renderComponent(renderable, nexo::components::RenderType::RENDER_3D); + app.m_coordinator->addComponent(defaultCamera, renderComponent); + return defaultCamera; + } + + bool CameraInspector(const nexo::scene::SceneId sceneId, ImVec2 sceneViewportSize) + { + auto &app = nexo::getApp(); + + const ImVec2 availSize = ImGui::GetContentRegionAvail(); + const float totalWidth = availSize.x; + 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 + static nexo::ecs::Entity 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; + ImGui::Columns(2, "CameraCreatorColumns", false); + + ImGui::SetColumnWidth(0, inspectorWidth); + // --- Left Side: Camera Inspector --- + { + ImGui::BeginChild("CameraInspector", ImVec2(inspectorWidth - 4, totalHeight), true); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Name"); + ImGui::SameLine(); + if (nameIsEmpty) { + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.9f, 0.2f, 0.2f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + } + ImGui::InputText("##CameraName", cameraName, IM_ARRAYSIZE(cameraName)); + if (nameIsEmpty) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.2f, 0.2f, 1.0f)); + ImGui::TextWrapped("Name is empty"); + ImGui::PopStyleColor(); + ImGui::Spacing(); + } else { + ImGui::Spacing(); + } + if (nameIsEmpty && cameraName[0] != '\0') + nameIsEmpty = false; + ImGui::Spacing(); + + if (Header("##CameraNode", "Camera")) + { + auto &cameraComponent = app.m_coordinator->getComponent(camera); + cameraComponent.render = true; + Camera(cameraComponent); + ImGui::TreePop(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + + if (Header("##TransformNode", "Transform Component")) + { + static glm::vec3 lastDisplayedEuler(0.0f); + auto &transformComponent = app.m_coordinator->getComponent(camera); + Transform(transformComponent, lastDisplayedEuler); + ImGui::TreePop(); + } + + if (app.m_coordinator->entityHasComponent(camera) && + Header("##PerspectiveCameraTarget", "Camera Target Component")) + { + auto &cameraTargetComponent = app.m_coordinator->getComponent(camera); + CameraTarget(cameraTargetComponent); + ImGui::TreePop(); + } + + if (app.m_coordinator->entityHasComponent(camera) && + Header("##PerspectiveCameraController", "Camera Controller Component")) + { + auto &cameraControllerComponent = app.m_coordinator->getComponent(camera); + CameraController(cameraControllerComponent); + ImGui::TreePop(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Spacing(); + // Add Component button + const float buttonWidth = inspectorWidth - 16; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 4)); + float centeredX = (inspectorWidth - buttonWidth) * 0.5f; + ImGui::SetCursorPosX(centeredX); + + // Static variables for state tracking + static bool showComponentSelector = false; + static float animProgress = 0.0f; + static float lastClickTime = 0.0f; + + // Button with arrow indicating state + std::string buttonText = "Add Component " + std::string(showComponentSelector ? ICON_FA_CHEVRON_UP : ICON_FA_CHEVRON_DOWN); + + if (Button(buttonText.c_str(), ImVec2(buttonWidth, 0))) + { + showComponentSelector = !showComponentSelector; + if (showComponentSelector) { + lastClickTime = ImGui::GetTime(); + animProgress = 0.0f; + } + } + ImGui::PopStyleVar(); + + // Component selector with just two options + if (showComponentSelector) + { + // Animation calculation + const float animDuration = 0.25f; + float timeSinceClick = ImGui::GetTime() - lastClickTime; + animProgress = std::min(timeSinceClick / animDuration, 1.0f); + + // Simplified component grid with compact layout + const float maxGridHeight = 90.0f; + 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_FramePadding, ImVec2(8, 8)); // Better padding inside items + + ImGui::BeginChild("ComponentSelector", ImVec2(buttonWidth, currentHeight), 0, ImGuiWindowFlags_NoScrollbar); + + if (animProgress > 0.5f) + { + // Center elements horizontally with proper spacing + const float itemSize = 75.0f; + + // Draw component buttons side-by-side with controlled spacing + ImGui::BeginGroup(); + + if (!app.m_coordinator->entityHasComponent(camera) && + !app.m_coordinator->entityHasComponent(camera) && + ButtonWithIconAndText("camera_target", ICON_FA_CAMERA, "Camera target", ImVec2(75.0f, 75.0f))) + { + nexo::components::PerspectiveCameraTarget cameraTarget{}; + app.m_coordinator->addComponent(camera, cameraTarget); + showComponentSelector = false; + } + ImGui::SameLine(); + if (!app.m_coordinator->entityHasComponent(camera) && + !app.m_coordinator->entityHasComponent(camera) && + ButtonWithIconAndText("camera_controller", ICON_FA_GAMEPAD, "Camera Controller", ImVec2(75.0f, 75.0f))) + { + nexo::components::PerspectiveCameraController cameraController{}; + app.m_coordinator->addComponent(camera, cameraController); + showComponentSelector = false; + } + ImGui::EndGroup(); + } + + ImGui::EndChild(); + ImGui::PopStyleVar(3); + + // Reset animation if needed + if (!showComponentSelector && animProgress >= 1.0f) { + animProgress = 0.0f; + } + } + + ImGui::EndChild(); // End CameraInspector + } + ImGui::NextColumn(); + // --- Right Side: Camera Preview --- + { + ImGui::BeginChild("CameraPreview", ImVec2(previewWidth - 4, totalHeight), true); + + auto &app = nexo::getApp(); + app.run(sceneId, nexo::RenderingType::FRAMEBUFFER); + 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; + + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 4, ImGui::GetCursorPosY() + 4)); + ImGui::Image(static_cast(static_cast(textureId)), + ImVec2(displayWidth, displayHeight), ImVec2(0, 1), ImVec2(1, 0)); + + ImGui::EndChild(); + } + + ImGui::Columns(1); + ImGui::Spacing(); + + // Bottom buttons - centered + constexpr float buttonWidth = 120.0f; + + if (ImGui::Button("OK", ImVec2(buttonWidth, 0))) + { + if (cameraName[0] == '\0') { + nameIsEmpty = true; + return false; + } + nameIsEmpty = false; + auto &selector = nexo::editor::Selector::get(); + auto &uuid = app.m_coordinator->getComponent(camera); + auto &cameraComponent = app.m_coordinator->getComponent(camera); + cameraComponent.active = false; + selector.setUiHandle(uuid.uuid, std::string(ICON_FA_CAMERA " ") + cameraName); + camera = nexo::ecs::MAX_ENTITIES; + cameraName[0] = '\0'; + ImGui::CloseCurrentPopup(); + return true; + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) + { + nameIsEmpty = false; + app.deleteEntity(camera); + camera = nexo::ecs::MAX_ENTITIES; + cameraName[0] = '\0'; + ImGui::CloseCurrentPopup(); + return true; + } + return false; + } +} diff --git a/editor/src/ImNexo/Panels.hpp b/editor/src/ImNexo/Panels.hpp new file mode 100644 index 000000000..f01a81a41 --- /dev/null +++ b/editor/src/ImNexo/Panels.hpp @@ -0,0 +1,54 @@ +//// NxUiPanels.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: Mehdy MORVAN +// Date: 18/04/2025 +// Description: Header file for ui panels +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "components/Render.hpp" +#include "core/scene/SceneManager.hpp" + +#include + +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 Pointer 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); + + /** + * @brief Displays a camera creation and configuration dialog. + * + * Creates a modal window with a split layout: + * - Left panel: Camera property inspector with fields for name, camera parameters, + * transform values, and optional components + * - Right panel: Real-time preview of the camera's view + * + * The dialog includes an animated "Add Component" dropdown that allows adding + * optional camera components (Camera Target or Camera Controller). At the bottom, + * OK and Cancel buttons allow confirming or aborting camera creation. + * + * When OK is clicked, the camera name is validated. If valid, the camera is added + * to the specified scene with the configured parameters. If Cancel is clicked or + * 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(const nexo::scene::SceneId sceneId, ImVec2 sceneViewportSize); +} diff --git a/editor/src/ImNexo/Utils.cpp b/editor/src/ImNexo/Utils.cpp new file mode 100644 index 000000000..6cf36e499 --- /dev/null +++ b/editor/src/ImNexo/Utils.cpp @@ -0,0 +1,98 @@ +//// Utils.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: 18/04/2025 +// Description: Source file for the ui utils functions +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Utils.hpp" + +namespace ImNexo::utils { + + ImU32 imLerpColor(const ImU32 colA, const ImU32 colB, const float t) + { + const unsigned char a0 = (colA >> 24) & 0xFF, r0 = (colA >> 16) & 0xFF, g0 = (colA >> 8) & 0xFF, b0 = colA & 0xFF; + const unsigned char a1 = (colB >> 24) & 0xFF, r1 = (colB >> 16) & 0xFF, g1 = (colB >> 8) & 0xFF, 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)); + return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); + } + + void clipPolygonWithLine(const std::vector& poly, const ImVec2& normal, float offset, std::vector& outPoly) + { + outPoly.clear(); + const auto count = poly.size(); + outPoly.reserve(count * 2); // Preallocate space for the output polygon (prepare worst case) + 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); + // if the edge spans the boundary, compute intersection + if ((da >= 0 && db < 0) || (da < 0 && db >= 0)) { + const float t = da / (da - db); + ImVec2 inter; + inter.x = a.x + t * (b.x - a.x); + inter.y = a.y + t * (b.y - a.y); + outPoly.push_back(inter); + } + } + } + + void fillConvexPolygon(ImDrawList* drawList, const std::vector& poly, const std::vector& polyColors) + { + if (poly.size() < 3) + return; + const auto count = static_cast(poly.size()); + drawList->PrimReserve((count - 2) * 3, count); + // Use the first vertex as pivot. + for (int i = 1; i < count - 1; i++) { + const auto currentIdx = drawList->_VtxCurrentIdx; + drawList->PrimWriteIdx(static_cast(currentIdx)); + drawList->PrimWriteIdx(static_cast(currentIdx + i)); + drawList->PrimWriteIdx(static_cast(currentIdx + i + 1)); + } + // Write vertices with their computed colors. + for (int i = 0; i < count; i++) { + // For a vertex, we determine its position t between the segment boundaries later. + // Here we assume the provided poly_colors already correspond vertex-by-vertex. + drawList->PrimWriteVtx(poly[i], drawList->_Data->TexUvWhitePixel, polyColors[i]); + } + } + + std::pair getItemRect(const ImVec2& padding) + { + ImVec2 p_min = ImGui::GetItemRectMin(); + ImVec2 p_max = ImGui::GetItemRectMax(); + + p_min.x += padding.x; + p_min.y += padding.y; + p_max.x -= padding.x; + p_max.y -= padding.y; + + return {p_min, p_max}; + } + + /** + * @brief Positions text centered within a rectangle + */ + ImVec2 calculateCenteredTextPosition(const std::string& text, const ImVec2& p_min, const ImVec2& p_max) + { + ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); + return ImVec2( + 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 + ); + } +} diff --git a/editor/src/ImNexo/Utils.hpp b/editor/src/ImNexo/Utils.hpp new file mode 100644 index 000000000..d927ac052 --- /dev/null +++ b/editor/src/ImNexo/Utils.hpp @@ -0,0 +1,60 @@ +//// Utils.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: Mehdy MORVAN +// Date: 18/04/2025 +// Description: Header file for the ui utils functions +// +/////////////////////////////////////////////////////////////////////////////// +#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). + */ + ImU32 imLerpColor(const ImU32 colA, const ImU32 colB, const 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 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. + */ + 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 + */ + std::pair getItemRect(const ImVec2& padding = ImVec2(0, 0)); + + /** + * @brief Positions text centered within a rectangle + */ + ImVec2 calculateCenteredTextPosition(const std::string& text, const ImVec2& p_min, const ImVec2& p_max); +} diff --git a/editor/src/ImNexo/Widgets.cpp b/editor/src/ImNexo/Widgets.cpp new file mode 100644 index 000000000..fe2402881 --- /dev/null +++ b/editor/src/ImNexo/Widgets.cpp @@ -0,0 +1,169 @@ +//// Widgets.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: 22/02/2025 +// Description: Source file for the widgets components +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Widgets.hpp" + +#include +#include +#include +#include "Path.hpp" + +#include "Definitions.hpp" +#include "IconsFontAwesome.h" +#include "Nexo.hpp" +#include "Texture.hpp" +#include "components/Camera.hpp" +#include "CameraFactory.hpp" +#include "components/Render3D.hpp" +#include "components/Transform.hpp" +#include "components/Uuid.hpp" +#include "context/Selector.hpp" + +namespace ImNexo { + + bool ColorEditor( + const std::string &label, + glm::vec4 *selectedEntityColor, + ImGuiColorEditFlags *colorPickerMode, + bool *showPicker, + const ImGuiColorEditFlags colorButtonFlags + ) { + const ImGuiStyle &style = ImGui::GetStyle(); + const ImVec2 contentAvailable = ImGui::GetContentRegionAvail(); + bool colorModified = false; + + const std::string colorButton = std::string("##ColorButton") + label; + + const ImVec2 cogIconSize = ImGui::CalcTextSize(ICON_FA_COG); + const ImVec2 cogIconPadding = style.FramePadding; + const ImVec2 itemSpacing = style.ItemSpacing; + + // Color button + ColorButton( + 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 + ); + + ImGui::SameLine(); + + 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)) { + ImGui::OpenPopup(colorPickerPopup.c_str()); + } + + if (ImGui::BeginPopup(colorPickerPopup.c_str())) + { + ImGui::Text("Picker Mode:"); + if (ImGui::RadioButton("Hue Wheel", *colorPickerMode == ImGuiColorEditFlags_PickerHueWheel)) + *colorPickerMode = ImGuiColorEditFlags_PickerHueWheel; + if (ImGui::RadioButton("Hue bar", *colorPickerMode == ImGuiColorEditFlags_PickerHueBar)) + *colorPickerMode = ImGuiColorEditFlags_PickerHueBar; + ImGui::EndPopup(); + } + + const std::string colorPickerInline = std::string("##ColorPickerInline") + label; + if (*showPicker) + { + ImGui::Spacing(); + colorModified = ImGui::ColorPicker4(colorPickerInline.c_str(), + reinterpret_cast(selectedEntityColor), *colorPickerMode); + } + return colorModified; + } + + void ButtonDropDown(const ImVec2& buttonPos, const ImVec2 buttonSize, const std::vector &buttonProps, bool &closure, DropdownOrientation orientation) + { + constexpr float buttonSpacing = 5.0f; + constexpr float padding = 10.0f; + const auto &app = nexo::getApp(); + + // Calculate menu dimensions + const float menuWidth = buttonSize.x + padding; // Add padding + const float menuHeight = buttonProps.size() * buttonSize.y + + (buttonProps.size() - 1) * 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; + } + + // Adjust layout for horizontal orientations + bool isHorizontal = (orientation == DropdownOrientation::LEFT || + orientation == DropdownOrientation::RIGHT); + + // For horizontal layouts, swap width and height + ImVec2 menuSize = isHorizontal ? + ImVec2(menuHeight, buttonSize.y + 10.0f) : + ImVec2(menuWidth, menuHeight); + + ImGui::SetNextWindowPos(menuPos); + ImGui::SetNextWindowSize(menuSize); + ImGui::SetNextWindowBgAlpha(0.2f); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5.0f, buttonSpacing)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, + isHorizontal ? ImVec2(buttonSpacing, 0) : ImVec2(0, buttonSpacing)); + 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) + { + // 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)) + { + button.onClick(); + closure = false; + } + if (button.onRightClick && ImGui::IsItemClicked(ImGuiMouseButton_Right)) + { + button.onRightClick(); + } + if (!button.tooltip.empty() && ImGui::IsItemHovered()) + ImGui::SetTooltip(button.tooltip.c_str()); + } + } + // Check for clicks outside to close menu + if (ImGui::IsMouseClicked(0) && !ImGui::IsWindowHovered()) + { + closure = false; + } + ImGui::End(); + + ImGui::PopStyleVar(3); + } +} diff --git a/editor/src/ImNexo/Widgets.hpp b/editor/src/ImNexo/Widgets.hpp new file mode 100644 index 000000000..2021b545a --- /dev/null +++ b/editor/src/ImNexo/Widgets.hpp @@ -0,0 +1,103 @@ +//// Widgets.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: Mehdy MORVAN +// Date: 22/02/2025 +// Description: Header file for the widgets components +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include +#include + +#include "Components.hpp" +#include "components/Camera.hpp" +#include "components/Render3D.hpp" +#include "components/Transform.hpp" +#include "renderer/Texture.hpp" +#include "core/scene/SceneManager.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 Configuration properties for a button in a dropdown menu. + * + * This structure defines the appearance and behavior of buttons in a + * dropdown menu created with ButtonDropDown. It allows for specifying + * 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::function onRightClick = nullptr; ///< Callback executed when button is right-clicked + 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)} + }; + }; + + 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 + }; + + /** + * @brief Creates a dropdown menu of buttons at a specified position. + * + * Displays a configurable dropdown menu containing multiple buttons defined by ButtonProps. + * The dropdown automatically closes when a button is clicked or when clicking outside + * the dropdown area. Button layout adapts based on the specified orientation. + * + * @param buttonPos Position where the dropdown should appear, typically the position of the trigger button + * @param buttonSize Size dimensions for each button in the dropdown + * @param buttonProps Vector of button configurations (icons, callbacks, tooltips, etc.) + * @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, + const ImVec2 buttonSize, + const std::vector &buttonProps, + bool &closure, + const DropdownOrientation orientation = DropdownOrientation::DOWN + ); +} diff --git a/editor/src/WindowRegistry.hpp b/editor/src/WindowRegistry.hpp index 2c1d775f3..376657e8e 100644 --- a/editor/src/WindowRegistry.hpp +++ b/editor/src/WindowRegistry.hpp @@ -27,11 +27,31 @@ namespace nexo::editor { + /** + * @brief Helper function to cast a window from IDocumentWindow to a specific type. + * + * Used by the transform_view in getWindows() to perform the static cast from + * the base IDocumentWindow type to the requested derived type. + * + * @tparam T The derived window type to cast to + * @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); } + /** + * @brief Non-const version of the window casting helper function. + * + * Used by the non-const getWindows() method to cast windows from the base + * IDocumentWindow type to the requested derived type. + * + * @tparam T The derived window type to cast to + * @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); @@ -65,6 +85,16 @@ namespace nexo::editor { 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) @@ -155,6 +185,18 @@ namespace nexo::editor { 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< @@ -195,6 +237,15 @@ namespace nexo::editor { */ std::optional getDockId(const std::string& name) 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); /** diff --git a/editor/src/utils/Config.hpp b/editor/src/utils/Config.hpp index 306f7a79e..7c183dccb 100644 --- a/editor/src/utils/Config.hpp +++ b/editor/src/utils/Config.hpp @@ -32,7 +32,30 @@ namespace nexo::editor { */ 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. + */ const 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); } diff --git a/editor/src/utils/FileSystem.hpp b/editor/src/utils/FileSystem.hpp index fac9a39b3..c7498edc7 100644 --- a/editor/src/utils/FileSystem.hpp +++ b/editor/src/utils/FileSystem.hpp @@ -21,5 +21,26 @@ #endif namespace nexo::editor::utils { + /** + * @brief Opens a file explorer window showing a specified folder. + * + * Uses platform-specific mechanisms to open the operating system's file explorer + * at the specified folder location: + * - On Windows: Uses ShellExecuteA to open Windows Explorer + * - On Linux/Unix: Uses xdg-open via system command + * + * This function is intended for user interaction purposes, such as revealing + * exported files, log directories, or other locations that the user may need + * to access directly. + * + * @param folderPath The absolute path to the folder to open + * + * @note This function does not check if the path exists or is accessible. + * It simply passes the request to the operating system which will handle + * any error conditions according to its standard behavior. + * + * @note On non-Windows platforms, this executes a system command, which may have + * security implications if folderPath contains untrusted input. + */ void openFolder(const std::string &folderPath); } diff --git a/editor/src/utils/ScenePreview.cpp b/editor/src/utils/ScenePreview.cpp index 50cf0e08d..4f0ba28d6 100644 --- a/editor/src/utils/ScenePreview.cpp +++ b/editor/src/utils/ScenePreview.cpp @@ -65,58 +65,42 @@ namespace nexo::editor::utils { const auto &transformComponentBase = nexo::Application::m_coordinator->getComponent(entity); const auto &transformComponent = nexo::Application::m_coordinator->getComponent(entityCopy); - // Create the render target for the preview scene. auto framebuffer = renderer::Framebuffer::create(framebufferSpecs); float distance = transformComponentBase.size.z * 2.0f; - // Define default angular offsets (in degrees) for yaw and pitch float defaultYawDeg = 30.0f; // horizontal offset float defaultPitchDeg = -20.0f; // vertical offset - // Convert the angles to radians float defaultYaw = glm::radians(defaultYawDeg); float defaultPitch = glm::radians(defaultPitchDeg); - // Set the target position for the camera. - // In this preview, the target is the copied entity, whose transform we set above. glm::vec3 targetPos = transformComponent.pos; - // Start with an initial offset vector. - // Here we assume the camera initially lies along the positive Z axis (relative to the target). glm::vec3 initialOffset = {0.0f, 0.0f, distance}; - // Create an incremental quaternion for horizontal rotation (yaw) about the world up. glm::quat qYaw = glm::angleAxis(defaultYaw, glm::vec3(0, 1, 0)); - // For the pitch (vertical rotation), compute the right axis. glm::vec3 rightAxis = glm::normalize(glm::cross(glm::vec3(0, 1, 0), initialOffset)); if (glm::length(rightAxis) < 0.001f) // Fallback if the vector is degenerate. rightAxis = glm::vec3(1, 0, 0); glm::quat qPitch = glm::angleAxis(defaultPitch, rightAxis); - // Combine the yaw and pitch rotations, similar to the event handler logic. glm::quat incrementalRotation = qYaw * qPitch; - // Apply the incremental rotation to the initial offset to get the final offset. glm::vec3 newOffset = incrementalRotation * initialOffset; - // Normalize and apply the desired distance (optional if you need to clamp or adjust) newOffset = glm::normalize(newOffset) * distance; - // Compute the camera's starting position. glm::vec3 cameraPos = targetPos + newOffset; - // Create the perspective camera using the computed position. ecs::Entity cameraId = CameraFactory::createPerspectiveCamera(cameraPos, framebufferSpecs.width, framebufferSpecs.height, framebuffer, clearColor); - // Update the camera's transform. auto &cameraTransform = nexo::Application::m_coordinator->getComponent(cameraId); cameraTransform.pos = cameraPos; auto &cameraComponent = nexo::Application::m_coordinator->getComponent(cameraId); cameraComponent.render = true; - // Compute the camera's orientation so that it looks at the target. glm::vec3 newFront = glm::normalize(targetPos - cameraPos); cameraTransform.quat = glm::normalize(glm::quatLookAt(newFront, glm::vec3(0.0f, 1.0f, 0.0f))); From 312b17186900da984e4367639162b3604f1db690 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:53:07 +0900 Subject: [PATCH 143/450] feat(document-windows): rename old shader to phong + add shader for albedo unshaded + outline flat and transparent shaders --- .../shaders/albedo_unshaded_transparent.glsl | 47 +++++++++++++ resources/shaders/outline_pulse_flat.glsl | 48 +++++++++++++ .../outline_pulse_transparent_flat.glsl | 60 ++++++++++++++++ .../shaders/{texture.glsl => phong.glsl} | 70 +++++++++---------- 4 files changed, 188 insertions(+), 37 deletions(-) create mode 100644 resources/shaders/albedo_unshaded_transparent.glsl create mode 100644 resources/shaders/outline_pulse_flat.glsl create mode 100644 resources/shaders/outline_pulse_transparent_flat.glsl rename resources/shaders/{texture.glsl => phong.glsl} (61%) diff --git a/resources/shaders/albedo_unshaded_transparent.glsl b/resources/shaders/albedo_unshaded_transparent.glsl new file mode 100644 index 000000000..f17e8148b --- /dev/null +++ b/resources/shaders/albedo_unshaded_transparent.glsl @@ -0,0 +1,47 @@ +#type vertex +#version 430 core +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec2 aTexCoord; +layout(location = 2) in vec3 aNormal; +layout(location = 3) in vec3 aTangent; +layout(location = 4) in vec3 aBiTangent; +layout(location = 5) in int aEntityID; + +uniform mat4 uViewProjection; +uniform mat4 uMatModel; + +out vec2 vTexCoord; +flat out int vEntityID; + +void main() +{ + vec4 worldPos = uMatModel * vec4(aPos, 1.0); + vTexCoord = aTexCoord; + vEntityID = aEntityID; + gl_Position = uViewProjection * worldPos; +} + +#type fragment +#version 430 core +layout(location = 0) out vec4 FragColor; +layout(location = 1) out int EntityID; + +in vec2 vTexCoord; +flat in int vEntityID; + +struct Material { + vec4 albedoColor; + int albedoTexIndex; // Default: 0 (white texture) +}; +uniform Material uMaterial; + +uniform sampler2D uTexture[32]; + +void main() +{ + if (texture(uTexture[uMaterial.albedoTexIndex], vTexCoord).a < 0.1) + discard; + vec3 color = uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); + FragColor = vec4(color, 1.0); + EntityID = vEntityID; +} diff --git a/resources/shaders/outline_pulse_flat.glsl b/resources/shaders/outline_pulse_flat.glsl new file mode 100644 index 000000000..5212b1725 --- /dev/null +++ b/resources/shaders/outline_pulse_flat.glsl @@ -0,0 +1,48 @@ +#type vertex +#version 430 core +layout(location = 0) in vec3 aPos; + +uniform mat4 uViewProjection; +uniform mat4 uMatModel; + +void main() +{ + // Calculate position in world space + vec4 worldPos = uMatModel * vec4(aPos, 1.0); + + // Transform to clip space + gl_Position = uViewProjection * worldPos; +} + +#type fragment +#version 430 core +layout(location = 0) out vec4 FragColor; + +uniform float uTime; // For subtle animation + +void main() +{ + // Base colors + vec4 purpleColor = vec4(0.5, 0.0, 1.0, 1.0); // Purple + vec4 blueColor = vec4(0.0, 0.4, 0.9, 1.0); // Blue + + // Color shifting effect + float colorShift = (sin(uTime * 3.0) * 0.5 + 0.5); + vec4 baseColor = mix(purpleColor, blueColor, colorShift); + + // Contrast pulsation (different frequency than color shift) + float contrastPulse = (sin(uTime * 2.3) * 0.5 + 0.5) * 0.5 + 0.5; // Range from 0.5 to 1.0 + + // Apply contrast variation + vec4 highContrastColor = vec4( + pow(baseColor.r, contrastPulse), + pow(baseColor.g, contrastPulse), + pow(baseColor.b, contrastPulse), + 1.0 + ); + + // Add subtle brightness pulsation for a glowing effect + float brightnessPulse = sin(uTime * 4.0) * 0.15 + 0.85; // Range from 0.7 to 1.0 + + FragColor = highContrastColor * brightnessPulse; +} diff --git a/resources/shaders/outline_pulse_transparent_flat.glsl b/resources/shaders/outline_pulse_transparent_flat.glsl new file mode 100644 index 000000000..4fd0fff66 --- /dev/null +++ b/resources/shaders/outline_pulse_transparent_flat.glsl @@ -0,0 +1,60 @@ +#type vertex +#version 430 core +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec2 aTexCoord; + +uniform mat4 uViewProjection; +uniform mat4 uMatModel; + +out vec2 vTexCoord; + +void main() +{ + vec4 worldPos = uMatModel * vec4(aPos, 1.0); + vTexCoord = aTexCoord; + gl_Position = uViewProjection * worldPos; +} + +#type fragment +#version 430 core +layout(location = 0) out vec4 FragColor; + +in vec2 vTexCoord; + +struct Material { + vec4 albedoColor; + int albedoTexIndex; // Default: 0 (white texture) +}; +uniform Material uMaterial; + +uniform sampler2D uTexture[32]; +uniform float uTime; // For subtle animation + +void main() +{ + if (texture(uTexture[uMaterial.albedoTexIndex], vTexCoord).a < 0.1) + discard; + // Base colors + vec4 purpleColor = vec4(0.5, 0.0, 1.0, 1.0); // Purple + vec4 blueColor = vec4(0.0, 0.4, 0.9, 1.0); // Blue + + // Color shifting effect + float colorShift = (sin(uTime * 3.0) * 0.5 + 0.5); + vec4 baseColor = mix(purpleColor, blueColor, colorShift); + + // Contrast pulsation (different frequency than color shift) + float contrastPulse = (sin(uTime * 2.3) * 0.5 + 0.5) * 0.5 + 0.5; // Range from 0.5 to 1.0 + + // Apply contrast variation + vec4 highContrastColor = vec4( + pow(baseColor.r, contrastPulse), + pow(baseColor.g, contrastPulse), + pow(baseColor.b, contrastPulse), + 1.0 + ); + + // Add subtle brightness pulsation for a glowing effect + float brightnessPulse = sin(uTime * 4.0) * 0.15 + 0.85; // Range from 0.7 to 1.0 + + FragColor = highContrastColor * brightnessPulse; +} diff --git a/resources/shaders/texture.glsl b/resources/shaders/phong.glsl similarity index 61% rename from resources/shaders/texture.glsl rename to resources/shaders/phong.glsl index 9aa1575d0..9c4007f8f 100644 --- a/resources/shaders/texture.glsl +++ b/resources/shaders/phong.glsl @@ -7,8 +7,8 @@ layout(location = 3) in vec3 aTangent; layout(location = 4) in vec3 aBiTangent; layout(location = 5) in int aEntityID; -uniform mat4 viewProjection; -uniform mat4 matModel; +uniform mat4 uViewProjection; +uniform mat4 uMatModel; out vec3 vFragPos; out vec2 vTexCoord; @@ -17,16 +17,16 @@ flat out int vEntityID; void main() { - vec4 worldPos = matModel * vec4(aPos, 1.0); + vec4 worldPos = uMatModel * vec4(aPos, 1.0); vFragPos = worldPos.xyz; vTexCoord = aTexCoord; - vNormal = mat3(transpose(inverse(matModel))) * aNormal; + vNormal = mat3(transpose(inverse(uMatModel))) * aNormal; vEntityID = aEntityID; - gl_Position = viewProjection * vec4(vFragPos, 1.0); + gl_Position = uViewProjection * vec4(vFragPos, 1.0); } #type fragment @@ -71,15 +71,14 @@ flat in int vEntityID; uniform sampler2D uTexture[32]; -uniform vec3 camPos; +uniform vec3 uCamPos; -uniform DirectionalLight dirLights[MAX_DIR_LIGHTS]; -uniform int numDirLights; -uniform PointLight pointLights[MAX_POINT_LIGHTS]; -uniform int numPointLights; -uniform SpotLight spotLights[MAX_SPOT_LIGHTS]; -uniform int numSpotLights; -uniform vec3 ambientLight; +uniform DirectionalLight uDirLight; +uniform PointLight uPointLights[MAX_POINT_LIGHTS]; +uniform int uNumPointLights; +uniform SpotLight uSpotLights[MAX_SPOT_LIGHTS]; +uniform int uNumSpotLights; +uniform vec3 uAmbientLight; struct Material { vec4 albedoColor; @@ -95,7 +94,7 @@ struct Material { float opacity; int opacityTexIndex; // Default: 0 (white texture) }; -uniform Material material; +uniform Material uMaterial; vec3 CalcDirLight(DirectionalLight light, vec3 normal, vec3 viewDir) { @@ -104,12 +103,12 @@ vec3 CalcDirLight(DirectionalLight light, vec3 normal, vec3 viewDir) float diff = max(dot(normal, lightDir), 0.0); // specular shading vec3 reflectDir = reflect(-lightDir, normal); - float shininess = mix(128.0, 2.0, material.roughness); + float shininess = mix(128.0, 2.0, uMaterial.roughness); float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess); // combine results - //vec3 ambient = ambientLight * material.albedoColor.rgb * vec3(texture(uTexture[material.albedoTexIndex], vTexCoord)); - vec3 diffuse = light.color.rgb * diff * material.albedoColor.rgb * vec3(texture(uTexture[material.albedoTexIndex], vTexCoord)); - vec3 specular = light.color.rgb * spec * material.specularColor.rgb * vec3(texture(uTexture[material.specularTexIndex], vTexCoord)); + //vec3 ambient = ambientLight * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); + vec3 diffuse = light.color.rgb * diff * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); + vec3 specular = light.color.rgb * spec * uMaterial.specularColor.rgb * vec3(texture(uTexture[uMaterial.specularTexIndex], vTexCoord)); return (diffuse + specular); } @@ -120,15 +119,15 @@ vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) float diff = max(dot(normal, lightDir), 0.0); // specular shading vec3 reflectDir = reflect(-lightDir, normal); - float shininess = mix(128.0, 2.0, material.roughness); + float shininess = mix(128.0, 2.0, uMaterial.roughness); float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess); // attenuation float distance = length(light.position - fragPos); float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); // combine results - //vec3 ambient = ambientLight * material.albedoColor.rgb * vec3(texture(uTexture[material.albedoTexIndex], vTexCoord)); - vec3 diffuse = light.color.rgb * diff * material.albedoColor.rgb * vec3(texture(uTexture[material.albedoTexIndex], vTexCoord)); - vec3 specular = light.color.rgb * spec * material.specularColor.rgb * vec3(texture(uTexture[material.specularTexIndex], vTexCoord)); + //vec3 ambient = ambientLight * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); + vec3 diffuse = light.color.rgb * diff * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); + vec3 specular = light.color.rgb * spec * uMaterial.specularColor.rgb * vec3(texture(uTexture[uMaterial.specularTexIndex], vTexCoord)); //ambient *= attenuation; diffuse *= attenuation; specular *= attenuation; @@ -142,7 +141,7 @@ vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) float diff = max(dot(normal, lightDir), 0.0); // specular shading vec3 reflectDir = reflect(-lightDir, normal); - float shininess = mix(128.0, 2.0, material.roughness); + float shininess = mix(128.0, 2.0, uMaterial.roughness); float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess); // attenuation float distance = length(light.position - fragPos); @@ -152,9 +151,9 @@ vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) float epsilon = light.cutOff - light.outerCutoff; float intensity = clamp((theta - light.outerCutoff) / epsilon, 0.0, 1.0); // combine results - //vec3 ambient = ambientLight * material.albedoColor.rgb * vec3(texture(uTexture[material.albedoTexIndex], vTexCoord)); - vec3 diffuse = light.color.rgb * diff * material.albedoColor.rgb * vec3(texture(uTexture[material.albedoTexIndex], vTexCoord)); - vec3 specular = light.color.rgb * spec * material.specularColor.rgb * vec3(texture(uTexture[material.specularTexIndex], vTexCoord)); + //vec3 ambient = ambientLight * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); + vec3 diffuse = light.color.rgb * diff * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); + vec3 specular = light.color.rgb * spec * uMaterial.specularColor.rgb * vec3(texture(uTexture[uMaterial.specularTexIndex], vTexCoord)); //ambient *= attenuation * intensity; diffuse *= attenuation * intensity; specular *= attenuation * intensity; @@ -164,26 +163,23 @@ vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) void main() { vec3 norm = normalize(vNormal); - vec3 viewDir = normalize(camPos - vFragPos); + vec3 viewDir = normalize(uCamPos - vFragPos); vec3 result = vec3(0.0); - if (texture(uTexture[material.albedoTexIndex], vTexCoord).a < 0.1) + if (texture(uTexture[uMaterial.albedoTexIndex], vTexCoord).a < 0.1) discard; - vec3 ambient = ambientLight * material.albedoColor.rgb * vec3(texture(uTexture[material.albedoTexIndex], vTexCoord)); + vec3 ambient = uAmbientLight * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); result += ambient; - for (int i = 0; i < numDirLights; i++) - { - result += CalcDirLight(dirLights[i], norm, viewDir); - } + result += CalcDirLight(uDirLight, norm, viewDir); - for (int i = 0; i < numPointLights; i++) + for (int i = 0; i < uNumPointLights; i++) { - result += CalcPointLight(pointLights[i], norm, vFragPos, viewDir); + result += CalcPointLight(uPointLights[i], norm, vFragPos, viewDir); } - for (int i = 0; i < numSpotLights; i++) + for (int i = 0; i < uNumSpotLights; i++) { - result += CalcSpotLight(spotLights[i], norm, vFragPos, viewDir); + result += CalcSpotLight(uSpotLights[i], norm, vFragPos, viewDir); } FragColor = vec4(result, 1.0); From a5ac666c9762a81508851123cf682c3b839210bb Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:53:45 +0900 Subject: [PATCH 144/450] fix(document-windows): corrected clear color in default argument --- engine/src/CameraFactory.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/CameraFactory.hpp b/engine/src/CameraFactory.hpp index a1a056eda..2ce2df839 100644 --- a/engine/src/CameraFactory.hpp +++ b/engine/src/CameraFactory.hpp @@ -23,6 +23,6 @@ namespace nexo { public: static ecs::Entity createPerspectiveCamera(glm::vec3 pos, unsigned int width, unsigned int height, std::shared_ptr renderTarget = nullptr, - const glm::vec4 &clearColor = {0.0f, 0.0f, 0.0f, 1.0f}, float fov = 45.0f, float nearPlane = 0.1f, float farPlane = 1000.0f); + 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 = 0.1f, float farPlane = 1000.0f); }; } From be70db33358483c9d2944db421cc92926f59c54f Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:54:12 +0900 Subject: [PATCH 145/450] feat(document-windows): add new selected tag component --- engine/src/Application.cpp | 2 ++ engine/src/components/Editor.hpp | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 engine/src/components/Editor.hpp diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index eb74c869d..429e68611 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -24,6 +24,7 @@ #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" #include "components/Transform.hpp" +#include "components/Editor.hpp" #include "components/Uuid.hpp" #include "core/event/Input.hpp" #include "Timestep.hpp" @@ -71,6 +72,7 @@ namespace nexo { m_coordinator->registerComponent(); m_coordinator->registerComponent(); m_coordinator->registerComponent(); + m_coordinator->registerComponent(); m_coordinator->registerSingletonComponent(); m_coordinator->registerComponent(); diff --git a/engine/src/components/Editor.hpp b/engine/src/components/Editor.hpp new file mode 100644 index 000000000..2414f2649 --- /dev/null +++ b/engine/src/components/Editor.hpp @@ -0,0 +1,18 @@ +//// Editor.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: Mehdy MORVAN +// Date: 19/04/2025 +// Description: Header file for the editor related components +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +namespace nexo::components { + struct SelectedTag {}; +} From 5d11e2dc7f597d3c317a2e81c10fd4973ff7d493 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:54:33 +0900 Subject: [PATCH 146/450] feat(document-windows): add shader library --- engine/src/renderer/ShaderLibrary.cpp | 60 +++++++++++++++++++++++++++ engine/src/renderer/ShaderLibrary.hpp | 31 ++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 engine/src/renderer/ShaderLibrary.cpp create mode 100644 engine/src/renderer/ShaderLibrary.hpp diff --git a/engine/src/renderer/ShaderLibrary.cpp b/engine/src/renderer/ShaderLibrary.cpp new file mode 100644 index 000000000..d40b6abb5 --- /dev/null +++ b/engine/src/renderer/ShaderLibrary.cpp @@ -0,0 +1,60 @@ +//// ShaderLibrary.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: 19/04/2025 +// Description: Source file for the shader library +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ShaderLibrary.hpp" +#include "Logger.hpp" + +namespace nexo::renderer { + void ShaderLibrary::add(const std::shared_ptr &shader) + { + const std::string &name = shader->getName(); + m_shaders[name] = shader; + } + + void ShaderLibrary::add(const std::string &name, const std::shared_ptr &shader) + { + m_shaders[name] = shader; + } + + std::shared_ptr ShaderLibrary::load(const std::string &name, const std::string &path) + { + auto shader = Shader::create(path); + add(name, shader); + return shader; + } + + std::shared_ptr ShaderLibrary::load(const std::string &path) + { + auto shader = Shader::create(path); + add(shader); + return shader; + } + + std::shared_ptr ShaderLibrary::load(const std::string &name, const std::string &vertexSource, const std::string &fragmentSource) + { + auto shader = Shader::create(name, vertexSource, fragmentSource); + add(shader); + return shader; + } + + std::shared_ptr ShaderLibrary::get(const std::string &name) const + { + if (!m_shaders.contains(name)) + { + LOG(NEXO_WARN, "ShaderLibrary::get: shader {} not found", name); + return nullptr; + } + return m_shaders.at(name); + } +} diff --git a/engine/src/renderer/ShaderLibrary.hpp b/engine/src/renderer/ShaderLibrary.hpp new file mode 100644 index 000000000..93dea3b86 --- /dev/null +++ b/engine/src/renderer/ShaderLibrary.hpp @@ -0,0 +1,31 @@ +//// ShaderLibrary.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: Mehdy MORVAN +// Date: 19/04/2025 +// Description: Header file for the shader library +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include "Shader.hpp" + +namespace nexo::renderer { + class ShaderLibrary { + 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; + private: + std::unordered_map> m_shaders; + }; +} From 6365d82e1f8244fecb7fbe427ac20ae0e14733b0 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:55:27 +0900 Subject: [PATCH 147/450] feat(document-windows): add enums for common uniforms + now stores the locs + add new method to set them without asking the driver --- engine/src/renderer/Shader.cpp | 43 ---------------------------- engine/src/renderer/Shader.hpp | 51 ++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/engine/src/renderer/Shader.cpp b/engine/src/renderer/Shader.cpp index 7c13c682c..c0f625f9d 100644 --- a/engine/src/renderer/Shader.cpp +++ b/engine/src/renderer/Shader.cpp @@ -66,47 +66,4 @@ namespace nexo::renderer { THROW_EXCEPTION(OutOfRangeException, index, m_storageBuffers.size()); m_storageBuffers[index]->setData(data, size); } - - void ShaderLibrary::add(const std::shared_ptr &shader) - { - const std::string &name = shader->getName(); - m_shaders[name] = shader; - } - - void ShaderLibrary::add(const std::string &name, const std::shared_ptr &shader) - { - m_shaders[name] = shader; - } - - std::shared_ptr ShaderLibrary::load(const std::string &name, const std::string &path) - { - auto shader = Shader::create(path); - add(name, shader); - return shader; - } - - std::shared_ptr ShaderLibrary::load(const std::string &path) - { - auto shader = Shader::create(path); - add(shader); - return shader; - } - - std::shared_ptr ShaderLibrary::load(const std::string &name, const std::string &vertexSource, const std::string &fragmentSource) - { - auto shader = Shader::create(name, vertexSource, fragmentSource); - add(shader); - return shader; - } - - std::shared_ptr ShaderLibrary::get(const std::string &name) const - { - if (!m_shaders.contains(name)) - { - LOG(NEXO_WARN, "ShaderLibrary::get: shader {} not found", name); - return nullptr; - } - return m_shaders.at(name); - } - } diff --git a/engine/src/renderer/Shader.hpp b/engine/src/renderer/Shader.hpp index f20e52bc3..62812da54 100644 --- a/engine/src/renderer/Shader.hpp +++ b/engine/src/renderer/Shader.hpp @@ -23,6 +23,37 @@ namespace nexo::renderer { + enum class ShaderUniforms { + VIEW_PROJECTION, + MODEL_MATRIX, + CAMERA_POSITION, + + TEXTURE_SAMPLER, + + DIR_LIGHT, + AMBIENT_LIGHT, + POINT_LIGHT_ARRAY, + NB_POINT_LIGHT, + SPOT_LIGHT_ARRAY, + NB_SPOT_LIGHT, + + MATERIAL + }; + + inline const std::unordered_map ShaderUniformsName = { + {ShaderUniforms::VIEW_PROJECTION, "uViewProjection"}, + {ShaderUniforms::MODEL_MATRIX, "uMatModel"}, + {ShaderUniforms::CAMERA_POSITION, "uCamPos"}, + {ShaderUniforms::TEXTURE_SAMPLER, "uTexture"}, + {ShaderUniforms::DIR_LIGHT, "uDirLight"}, + {ShaderUniforms::AMBIENT_LIGHT, "uAmbientLight"}, + {ShaderUniforms::POINT_LIGHT_ARRAY, "uPointLights"}, + {ShaderUniforms::NB_POINT_LIGHT, "uNbPointLights"}, + {ShaderUniforms::SPOT_LIGHT_ARRAY, "uSpotLights"}, + {ShaderUniforms::NB_SPOT_LIGHT, "uNbSpotLights"}, + {ShaderUniforms::MATERIAL, "uMaterial"} + }; + /** * @class Shader * @brief Abstract class representing a shader program in the rendering pipeline. @@ -105,6 +136,13 @@ namespace nexo::renderer { virtual bool setUniformInt(const std::string &name, int value) const = 0; virtual bool setUniformIntArray(const std::string &name, const int *values, unsigned int count) const = 0; + virtual bool setUniformFloat(const ShaderUniforms uniform, const float value) const = 0; + virtual bool setUniformFloat3(const ShaderUniforms uniform, const glm::vec3 &values) const = 0; + virtual bool setUniformFloat4(const ShaderUniforms uniform, const glm::vec4 &values) const = 0; + virtual bool setUniformMatrix(const ShaderUniforms uniform, const glm::mat4 &matrix) const = 0; + virtual bool setUniformInt(const ShaderUniforms uniform, int value) const = 0; + virtual bool setUniformIntArray(const ShaderUniforms uniform, const int *values, unsigned int count) const = 0; + void addStorageBuffer(const std::shared_ptr &buffer); void setStorageBufferData(unsigned int index, void *data, unsigned int size); virtual void bindStorageBufferBase(unsigned int index, unsigned int bindingPoint) const = 0; @@ -116,17 +154,8 @@ namespace nexo::renderer { protected: static std::string readFile(const std::string &filepath); std::vector> m_storageBuffers; + std::unordered_map m_uniformLocations; }; - class ShaderLibrary { - 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; - private: - std::unordered_map> m_shaders; - }; + } From 7892bd9e237b5aaddd336310a702c2d6d764b1d2 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:56:01 +0900 Subject: [PATCH 148/450] feat(document-windows): implement new setUniforms method --- engine/src/renderer/opengl/OpenGlShader.cpp | 74 +++++++++++++++++++++ engine/src/renderer/opengl/OpenGlShader.hpp | 8 +++ 2 files changed, 82 insertions(+) diff --git a/engine/src/renderer/opengl/OpenGlShader.cpp b/engine/src/renderer/opengl/OpenGlShader.cpp index 70681f77e..8cdb92f59 100644 --- a/engine/src/renderer/opengl/OpenGlShader.cpp +++ b/engine/src/renderer/opengl/OpenGlShader.cpp @@ -15,6 +15,7 @@ #include "OpenGlShader.hpp" #include "Exception.hpp" #include "Logger.hpp" +#include "Shader.hpp" #include "renderer/RendererExceptions.hpp" #include @@ -36,6 +37,7 @@ namespace nexo::renderer { OpenGlShader::OpenGlShader(const std::string &path) { + std::cout << path << std::endl; const std::string src = readFile(path); auto shaderSources = preProcess(src, path); compile(shaderSources); @@ -45,6 +47,7 @@ namespace nexo::renderer { const auto lastDot = path.rfind('.'); const auto count = lastDot == std::string::npos ? path.size() - lastSlash : lastDot - lastSlash; m_name = path.substr(lastSlash, count); + setupUniformLocations(); } OpenGlShader::OpenGlShader(std::string name, const std::string_view &vertexSource, @@ -54,6 +57,7 @@ namespace nexo::renderer { preProcessedSource[GL_VERTEX_SHADER] = vertexSource; preProcessedSource[GL_FRAGMENT_SHADER] = fragmentSource; compile(preProcessedSource); + setupUniformLocations(); } OpenGlShader::~OpenGlShader() @@ -177,6 +181,16 @@ namespace nexo::renderer { glDetachShader(program, id); } + void OpenGlShader::setupUniformLocations() + { + glUseProgram(m_id); + for (const auto &[key, name] : ShaderUniformsName) { + const int loc = glGetUniformLocation(m_id, name.c_str()); + m_uniformLocations[key] = loc; + } + glUseProgram(0); + } + void OpenGlShader::bind() const { glUseProgram(m_id); @@ -197,6 +211,16 @@ namespace nexo::renderer { return true; } + bool OpenGlShader::setUniformFloat(const ShaderUniforms uniform, const float value) const + { + const int loc = m_uniformLocations.at(uniform); + if (loc == -1) + return false; + + glUniform1f(loc, value); + return true; + } + bool OpenGlShader::setUniformFloat3(const std::string &name, const glm::vec3 &values) const { const int loc = glGetUniformLocation(m_id, name.c_str()); @@ -207,6 +231,16 @@ namespace nexo::renderer { return true; } + bool OpenGlShader::setUniformFloat3(const ShaderUniforms uniform, const glm::vec3 &values) const + { + const int loc = m_uniformLocations.at(uniform); + if (loc == -1) + return false; + + glUniform3f(loc, values.x, values.y, values.z); + return true; + } + bool OpenGlShader::setUniformFloat4(const std::string &name, const glm::vec4 &values) const { const int loc = glGetUniformLocation(m_id, name.c_str()); @@ -217,6 +251,16 @@ namespace nexo::renderer { return true; } + bool OpenGlShader::setUniformFloat4(const ShaderUniforms uniform, const glm::vec4 &values) const + { + const int loc = m_uniformLocations.at(uniform); + if (loc == -1) + return false; + + glUniform4f(loc, values.x, values.y, values.z, values.w); + return true; + } + bool OpenGlShader::setUniformMatrix(const std::string &name, const glm::mat4 &matrix) const { const int loc = glGetUniformLocation(m_id, name.c_str()); @@ -227,6 +271,16 @@ namespace nexo::renderer { return true; } + bool OpenGlShader::setUniformMatrix(const ShaderUniforms uniform, const glm::mat4 &matrix) const + { + const int loc = m_uniformLocations.at(uniform); + if (loc == -1) + return false; + + glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(matrix)); + return true; + } + bool OpenGlShader::setUniformInt(const std::string &name, const int value) const { const int loc = glGetUniformLocation(m_id, name.c_str()); @@ -237,6 +291,16 @@ namespace nexo::renderer { return true; } + bool OpenGlShader::setUniformInt(const ShaderUniforms uniform, const int value) const + { + const int loc = m_uniformLocations.at(uniform); + if (loc == -1) + return false; + + glUniform1i(loc, value); + return true; + } + bool OpenGlShader::setUniformIntArray(const std::string &name, const int *values, const unsigned int count) const { const int loc = glGetUniformLocation(m_id, name.c_str()); @@ -247,6 +311,16 @@ namespace nexo::renderer { return true; } + bool OpenGlShader::setUniformIntArray(const ShaderUniforms uniform, const int *values, const unsigned int count) const + { + const int loc = m_uniformLocations.at(uniform); + if (loc == -1) + return false; + + glUniform1iv(loc, static_cast(count), values); + return true; + } + void OpenGlShader::bindStorageBuffer(unsigned int index) const { if (index > m_storageBuffers.size()) diff --git a/engine/src/renderer/opengl/OpenGlShader.hpp b/engine/src/renderer/opengl/OpenGlShader.hpp index 514bcc9c3..fc60bb81d 100644 --- a/engine/src/renderer/opengl/OpenGlShader.hpp +++ b/engine/src/renderer/opengl/OpenGlShader.hpp @@ -64,6 +64,13 @@ namespace nexo::renderer { 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(const ShaderUniforms uniform, const float value) const override; + bool setUniformFloat3(const ShaderUniforms uniform, const glm::vec3 &values) const override; + bool setUniformFloat4(const ShaderUniforms uniform, const glm::vec4 &values) const override; + bool setUniformMatrix(const ShaderUniforms uniform, const glm::mat4 &matrix) const override; + bool setUniformInt(const ShaderUniforms uniform, int value) const override; + bool setUniformIntArray(const ShaderUniforms 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; @@ -75,6 +82,7 @@ namespace nexo::renderer { 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(); }; } From 2305614082f2208ca2c47854f785a1061f27e1b7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:56:37 +0900 Subject: [PATCH 149/450] feat(document-windows): add methods for stencil and depth buffer handling --- engine/src/renderer/RendererAPI.hpp | 9 +++++++++ engine/src/renderer/opengl/OpenGlRendererAPI.hpp | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/engine/src/renderer/RendererAPI.hpp b/engine/src/renderer/RendererAPI.hpp index 47493ba6d..4c5692fe8 100644 --- a/engine/src/renderer/RendererAPI.hpp +++ b/engine/src/renderer/RendererAPI.hpp @@ -111,6 +111,9 @@ namespace nexo::renderer { */ virtual void setClearDepth(float depth) = 0; + virtual void setDepthTest(bool enable) = 0; + virtual void setDepthFunc(unsigned int func) = 0; + /** * @brief Issues a draw call for indexed geometry. * @@ -123,5 +126,11 @@ namespace nexo::renderer { * Must be implemented by subclasses. */ virtual void drawIndexed(const std::shared_ptr &vertexArray, unsigned int count = 0) = 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; + }; } diff --git a/engine/src/renderer/opengl/OpenGlRendererAPI.hpp b/engine/src/renderer/opengl/OpenGlRendererAPI.hpp index bcbbfbf57..418e5e199 100644 --- a/engine/src/renderer/opengl/OpenGlRendererAPI.hpp +++ b/engine/src/renderer/opengl/OpenGlRendererAPI.hpp @@ -106,6 +106,9 @@ namespace nexo::renderer { */ void setClearDepth(float depth) override; + void setDepthTest(bool enable) override; + void setDepthFunc(unsigned int func) override; + /** * @brief Renders indexed geometry using OpenGL. * @@ -119,6 +122,11 @@ namespace nexo::renderer { * - InvalidValue if the `vertexArray` is null. */ void drawIndexed(const std::shared_ptr &vertexArray, unsigned int indexCount = 0) 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; private: bool m_initialized = false; unsigned int m_maxWidth = 0; From a21c645b3f4a1c333103055cc4e72f793eda00c7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:57:11 +0900 Subject: [PATCH 150/450] refactor(document-windows): now use shader library + pass the shader as argument to begin scene --- engine/src/renderer/Renderer3D.cpp | 62 +++++++++++++++++++----------- engine/src/renderer/Renderer3D.hpp | 9 +++-- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index 8c17dad28..20580ad73 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -15,6 +15,7 @@ #include "Renderer3D.hpp" #include "RenderCommand.hpp" #include "Logger.hpp" +#include "Shader.hpp" #include "renderer/RendererExceptions.hpp" #include @@ -57,10 +58,23 @@ namespace nexo::renderer { for (int i = 0; i < static_cast(Renderer3DStorage::maxTextureSlots); ++i) samplers[i] = i; - m_storage->textureShader = Shader::create(Path::resolvePathRelativeToExe( - "../resources/shaders/texture.glsl").string()); - m_storage->textureShader->bind(); - m_storage->textureShader->setUniformIntArray("uTexture", samplers.data(), Renderer3DStorage::maxTextureSlots); + auto phong = m_storage->shaderLibrary.load("Phong", Path::resolvePathRelativeToExe( + "../resources/shaders/phong.glsl").string()); + m_storage->shaderLibrary.load("Outline pulse flat", Path::resolvePathRelativeToExe( + "../resources/shaders/outline_pulse_flat.glsl").string()); + auto outlinePulseTransparentFlat = m_storage->shaderLibrary.load("Outline pulse transparent flat", Path::resolvePathRelativeToExe( + "../resources/shaders/outline_pulse_transparent_flat.glsl").string()); + auto albedoUnshadedTransparent = m_storage->shaderLibrary.load("Albedo unshaded transparent", Path::resolvePathRelativeToExe( + "../resources/shaders/albedo_unshaded_transparent.glsl").string()); + phong->bind(); + phong->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), Renderer3DStorage::maxTextureSlots); + phong->unbind(); + outlinePulseTransparentFlat->bind(); + outlinePulseTransparentFlat->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), Renderer3DStorage::maxTextureSlots); + outlinePulseTransparentFlat->unbind(); + albedoUnshadedTransparent->bind(); + albedoUnshadedTransparent->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), Renderer3DStorage::maxTextureSlots); + albedoUnshadedTransparent->unbind(); m_storage->textureSlots[0] = m_storage->whiteTexture; @@ -74,16 +88,20 @@ namespace nexo::renderer { m_storage.reset(); } - void Renderer3D::beginScene(const glm::mat4 &viewProjection, const glm::vec3 &cameraPos) + void Renderer3D::beginScene(const glm::mat4 &viewProjection, const glm::vec3 &cameraPos, const std::string &shader) { if (!m_storage) THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_3D); - m_storage->textureShader->bind(); + if (shader.empty()) + m_storage->currentSceneShader = m_storage->shaderLibrary.get("Phong"); + else + m_storage->currentSceneShader = m_storage->shaderLibrary.get(shader); + m_storage->currentSceneShader->bind(); m_storage->vertexArray->bind(); m_storage->vertexBuffer->bind(); - m_storage->textureShader->setUniformMatrix("viewProjection", viewProjection); + m_storage->currentSceneShader->setUniformMatrix("uViewProjection", viewProjection); m_storage->cameraPosition = cameraPos; - m_storage->textureShader->setUniformFloat3("camPos", 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(); @@ -113,7 +131,7 @@ namespace nexo::renderer { void Renderer3D::flush() const { - m_storage->textureShader->bind(); + m_storage->currentSceneShader->bind(); for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) { m_storage->textureSlots[i]->bind(i); @@ -122,7 +140,7 @@ namespace nexo::renderer { m_storage->stats.drawCalls++; m_storage->vertexArray->unbind(); m_storage->vertexBuffer->unbind(); - m_storage->textureShader->unbind(); + m_storage->currentSceneShader->unbind(); for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) { m_storage->textureSlots[i]->unbind(i); @@ -169,18 +187,18 @@ namespace nexo::renderer { if (!m_storage) THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_3D); - m_storage->textureShader->setUniformFloat4("material.albedoColor", material.albedoColor); - m_storage->textureShader->setUniformInt("material.albedoTexIndex", material.albedoTexIndex); - m_storage->textureShader->setUniformFloat4("material.specularColor", material.specularColor); - m_storage->textureShader->setUniformInt("material.specularTexIndex", material.specularTexIndex); - m_storage->textureShader->setUniformFloat3("material.emissiveColor", material.emissiveColor); - m_storage->textureShader->setUniformInt("material.emissiveTexIndex", material.emissiveTexIndex); - m_storage->textureShader->setUniformFloat("material.roughness", material.roughness); - m_storage->textureShader->setUniformInt("material.roughnessTexIndex", material.roughnessTexIndex); - m_storage->textureShader->setUniformFloat("material.metallic", material.metallic); - m_storage->textureShader->setUniformInt("material.metallicTexIndex", material.metallicTexIndex); - m_storage->textureShader->setUniformFloat("material.opacity", material.opacity); - m_storage->textureShader->setUniformInt("material.opacityTexIndex", material.opacityTexIndex); + m_storage->currentSceneShader->setUniformFloat4("uMaterial.albedoColor", material.albedoColor); + m_storage->currentSceneShader->setUniformInt("uMaterial.albedoTexIndex", material.albedoTexIndex); + m_storage->currentSceneShader->setUniformFloat4("uMaterial.specularColor", material.specularColor); + m_storage->currentSceneShader->setUniformInt("uMaterial.specularTexIndex", material.specularTexIndex); + m_storage->currentSceneShader->setUniformFloat3("uMaterial.emissiveColor", material.emissiveColor); + m_storage->currentSceneShader->setUniformInt("uMaterial.emissiveTexIndex", material.emissiveTexIndex); + m_storage->currentSceneShader->setUniformFloat("uMaterial.roughness", material.roughness); + m_storage->currentSceneShader->setUniformInt("uMaterial.roughnessTexIndex", material.roughnessTexIndex); + m_storage->currentSceneShader->setUniformFloat("uMaterial.metallic", material.metallic); + m_storage->currentSceneShader->setUniformInt("uMaterial.metallicTexIndex", material.metallicTexIndex); + m_storage->currentSceneShader->setUniformFloat("uMaterial.opacity", material.opacity); + m_storage->currentSceneShader->setUniformInt("uMaterial.opacityTexIndex", material.opacityTexIndex); } void Renderer3D::resetStats() const diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index fd87c02f3..aabc3027a 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -16,6 +16,7 @@ #include "Shader.hpp" #include "VertexArray.hpp" #include "Texture.hpp" +#include "ShaderLibrary.hpp" #include "components/Render3D.hpp" #include @@ -80,7 +81,9 @@ namespace nexo::renderer { glm::vec3 cameraPosition; - std::shared_ptr textureShader; + ShaderLibrary shaderLibrary; + + std::shared_ptr currentSceneShader = nullptr; std::shared_ptr vertexArray; std::shared_ptr vertexBuffer; std::shared_ptr indexBuffer; @@ -169,7 +172,7 @@ namespace nexo::renderer { * - RendererNotInitialized if the renderer is not initialized. * - RendererSceneLifeCycleFailure if called without proper initialization. */ - void beginScene(const glm::mat4& viewProjection, const glm::vec3 &cameraPos); + void beginScene(const glm::mat4& viewProjection, const glm::vec3 &cameraPos, const std::string &shader = ""); /** * @brief Ends the current 3D rendering scene. @@ -330,7 +333,7 @@ namespace nexo::renderer { */ [[nodiscard]] Renderer3DStats getStats() const; - std::shared_ptr &getShader() const {return m_storage->textureShader;}; + std::shared_ptr &getShader() const {return m_storage->currentSceneShader;}; std::shared_ptr getInternalStorage() const { return m_storage; }; private: From 9bd6d492c9485c765a4b59aa09a7516c4a3a28b0 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:57:39 +0900 Subject: [PATCH 151/450] feat(document-windows): add depth and stencil buffer handling --- engine/src/renderer/RenderCommand.hpp | 76 +++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/engine/src/renderer/RenderCommand.hpp b/engine/src/renderer/RenderCommand.hpp index 2ff1704c9..ccc6cb58b 100644 --- a/engine/src/renderer/RenderCommand.hpp +++ b/engine/src/renderer/RenderCommand.hpp @@ -122,6 +122,82 @@ namespace nexo::renderer { _rendererApi->drawIndexed(vertexArray, indexCount); } + static void setDepthTest(bool enable) + { + _rendererApi->setDepthTest(enable); + } + + static void setDepthFunc(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(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(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(unsigned int func, int ref, 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(unsigned int sfail, unsigned int dpfail, unsigned int dppass) { + _rendererApi->setStencilOp(sfail, dpfail, dppass); + } + private: /** * @brief Static pointer to the active `RendererApi` implementation. From d3675bd0a0a28b7bf03400dfd63b57609a8d9957 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:58:17 +0900 Subject: [PATCH 152/450] refactor(document-windows): updated the uniforms name to comply with the new naming convention --- engine/src/renderer/primitives/Cube.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/engine/src/renderer/primitives/Cube.cpp b/engine/src/renderer/primitives/Cube.cpp index 11a41e082..1e8d8ac3b 100644 --- a/engine/src/renderer/primitives/Cube.cpp +++ b/engine/src/renderer/primitives/Cube.cpp @@ -128,7 +128,7 @@ namespace nexo::renderer { const glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0f), size); - m_storage->textureShader->setUniformMatrix("matModel", transform); + m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); renderer::Material mat; mat.albedoColor = color; @@ -179,7 +179,7 @@ namespace nexo::renderer { rotationMat * glm::scale(glm::mat4(1.0f), size); - m_storage->textureShader->setUniformMatrix("matModel", transform); + m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); renderer::Material mat; mat.albedoColor = color; @@ -224,7 +224,7 @@ namespace nexo::renderer { } - m_storage->textureShader->setUniformMatrix("matModel", transform); + m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); renderer::Material mat; mat.albedoColor = color; @@ -272,7 +272,7 @@ namespace nexo::renderer { const glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0f), size); - m_storage->textureShader->setUniformMatrix("matModel", transform); + m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); renderer::Material mat; mat.albedoColor = material.albedoColor; @@ -327,7 +327,7 @@ namespace nexo::renderer { rotationMat * glm::scale(glm::mat4(1.0f), size); - m_storage->textureShader->setUniformMatrix("matModel", transform); + m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); renderer::Material mat; mat.albedoColor = material.albedoColor; @@ -382,7 +382,7 @@ namespace nexo::renderer { rotationMat * glm::scale(glm::mat4(1.0f), size); - m_storage->textureShader->setUniformMatrix("matModel", transform); + m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); renderer::Material mat; mat.albedoColor = material.albedoColor; @@ -430,7 +430,7 @@ namespace nexo::renderer { "Renderer not rendering a scene, make sure to call beginScene first"); } - m_storage->textureShader->setUniformMatrix("matModel", transform); + m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); renderer::Material mat; mat.albedoColor = material.albedoColor; From 629540b9b866b7a835336b66cc4c6a3f6539c7cf Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 18:59:27 +0900 Subject: [PATCH 153/450] fix(document-windows): fix incorrect winding order --- engine/src/renderer/primitives/Billboard.cpp | 67 ++++++++------------ 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/engine/src/renderer/primitives/Billboard.cpp b/engine/src/renderer/primitives/Billboard.cpp index d8e4cc344..82e2a861f 100644 --- a/engine/src/renderer/primitives/Billboard.cpp +++ b/engine/src/renderer/primitives/Billboard.cpp @@ -77,48 +77,46 @@ namespace nexo::renderer { } /** - * @brief Calculates a billboard rotation matrix that makes the quad face the camera. - * - * @param billboardPosition The position of the billboard in world space. - * @param cameraPosition The position of the camera in world space. - * @param cameraUp The up vector of the camera (usually {0,1,0}). - * @param constrainToY Whether to only rotate around Y-axis (true) or do full rotation (false). - * @return glm::mat4 The rotation matrix for the billboard. - */ + * @brief Calculates a billboard rotation matrix that makes the quad face the camera. + * + * @param billboardPosition The position of the billboard in world space. + * @param cameraPosition The position of the camera in world space. + * @param cameraUp The up vector of the camera (usually {0,1,0}). + * @param constrainToY Whether to only rotate around Y-axis (true) or do full rotation (false). + * @return glm::mat4 The rotation matrix for the billboard. + */ static glm::mat4 calculateBillboardRotation( const glm::vec3& billboardPosition, const glm::vec3& cameraPosition, const glm::vec3& cameraUp = glm::vec3(0.0f, 1.0f, 0.0f), bool constrainToY = false) { - // Direction from billboard to camera glm::vec3 look = glm::normalize(cameraPosition - billboardPosition); if (constrainToY) { - // For Y-axis constrained billboards (like trees) - look.y = 0.0f; // Zero out the Y component - look = glm::normalize(look); // Re-normalize + look.y = 0.0f; + look = glm::normalize(look); - // Calculate right vector from up and look glm::vec3 right = glm::normalize(glm::cross(cameraUp, look)); + glm::vec3 up = glm::cross(look, right); + + return glm::mat4( + 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) + ); + } else { + glm::vec3 right = glm::normalize(glm::cross(cameraUp, look)); + glm::vec3 up = glm::cross(look, right); - // Create rotation matrix for Y-axis constrained billboard return glm::mat4( glm::vec4(right, 0.0f), - glm::vec4(cameraUp, 0.0f), - glm::vec4(look, 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) ); } - else { - // For full billboards (complete rotation to face camera) - // Create a look-at matrix but use it as a rotation matrix - return glm::transpose(glm::lookAt( - glm::vec3(0.0f), // Eye at origin - look, // Look in the direction of camera - cameraUp // Camera's up vector - )); - } } void Renderer3D::drawBillboard( @@ -133,25 +131,20 @@ namespace nexo::renderer { "Renderer not rendering a scene, make sure to call beginScene first"); } - // Get camera position from view matrix glm::vec3 cameraPos = m_storage->cameraPosition; - // Calculate billboard rotation to face camera glm::mat4 billboardRotation = calculateBillboardRotation(position, cameraPos); - // Create transformation matrix const glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * billboardRotation * glm::scale(glm::mat4(1.0f), glm::vec3(size.x, size.y, 1.0f)); - m_storage->textureShader->setUniformMatrix("matModel", transform); + m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - // Set material renderer::Material mat; mat.albedoColor = color; setMaterialUniforms(mat); - // Generate mesh data std::array verts{}; std::array texCoords{}; std::array normals{}; @@ -161,7 +154,6 @@ namespace nexo::renderer { for (unsigned int i = 0; i < 6; ++i) indices[i] = i; - // Vertex data for (unsigned int i = 0; i < 6; ++i) { m_storage->vertexBufferPtr->position = glm::vec4(verts[i], 1.0f); @@ -171,7 +163,6 @@ namespace nexo::renderer { m_storage->vertexBufferPtr++; } - // Index data std::ranges::for_each(indices, [this](unsigned int index) { m_storage->indexBufferBase[m_storage->indexCount++] = index; }); @@ -189,28 +180,24 @@ namespace nexo::renderer { "Renderer not rendering a scene, make sure to call beginScene first"); } - // Get camera position from view matrix glm::vec3 cameraPos = m_storage->cameraPosition; - // Calculate billboard rotation to face camera glm::mat4 billboardRotation = calculateBillboardRotation(position, cameraPos); - // Create transformation matrix const glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * billboardRotation * glm::scale(glm::mat4(1.0f), glm::vec3(size.x, size.y, 1.0f)); - m_storage->textureShader->setUniformMatrix("matModel", transform); + m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - // Set material renderer::Material mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; + std::cout << "Material Albedo Texture Index: " << mat.albedoTexIndex << std::endl; mat.specularColor = material.specularColor; mat.specularTexIndex = material.metallicMap ? getTextureIndex(material.metallicMap) : 0; setMaterialUniforms(mat); - // Generate mesh data std::array verts{}; std::array texCoords{}; std::array normals{}; @@ -220,7 +207,6 @@ namespace nexo::renderer { for (unsigned int i = 0; i < 6; ++i) indices[i] = i; - // Vertex data for (unsigned int i = 0; i < 6; ++i) { m_storage->vertexBufferPtr->position = glm::vec4(verts[i], 1.0f); @@ -230,7 +216,6 @@ namespace nexo::renderer { m_storage->vertexBufferPtr++; } - // Index data std::ranges::for_each(indices, [this](unsigned int index) { m_storage->indexBufferBase[m_storage->indexCount++] = index; }); From 194a058f17440537dc4ffbbba4dcf1d369037f1b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:00:03 +0900 Subject: [PATCH 154/450] feat(document-windows): implement depth + stencil buffer handling method + add backface culling --- .../src/renderer/opengl/OpenGlRendererApi.cpp | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/engine/src/renderer/opengl/OpenGlRendererApi.cpp b/engine/src/renderer/opengl/OpenGlRendererApi.cpp index c8270e210..ed023300c 100644 --- a/engine/src/renderer/opengl/OpenGlRendererApi.cpp +++ b/engine/src/renderer/opengl/OpenGlRendererApi.cpp @@ -28,9 +28,12 @@ namespace nexo::renderer { glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); - // glEnable(GL_CULL_FACE); - // glCullFace(GL_BACK); - // glFrontFace(GL_CCW); + glEnable(GL_STENCIL_TEST); + glStencilFunc(GL_ALWAYS, 0, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilMask(0xFF); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); int maxViewportSize[] = {0, 0}; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportSize); m_maxWidth = static_cast(maxViewportSize[0]); @@ -60,7 +63,7 @@ namespace nexo::renderer { { if (!m_initialized) THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } void OpenGlRendererApi::setClearColor(const glm::vec4 &color) @@ -77,6 +80,23 @@ namespace nexo::renderer { glClearDepth(depth); } + void OpenGlRendererApi::setDepthTest(bool enable) + { + if (!m_initialized) + THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + if (enable) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + } + + void OpenGlRendererApi::setDepthFunc(unsigned int func) + { + if (!m_initialized) + THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + glDepthFunc(func); + } + void OpenGlRendererApi::drawIndexed(const std::shared_ptr &vertexArray, const unsigned int indexCount) { if (!m_initialized) @@ -86,4 +106,35 @@ namespace nexo::renderer { const unsigned int count = indexCount ? vertexArray->getIndexBuffer()->getCount() : indexCount; glDrawElements(GL_TRIANGLES, static_cast(count), GL_UNSIGNED_INT, nullptr); } + + void OpenGlRendererApi::setStencilTest(bool enable) + { + if (!m_initialized) + THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + if (enable) + glEnable(GL_STENCIL_TEST); + else + glDisable(GL_STENCIL_TEST); + } + + void OpenGlRendererApi::setStencilMask(unsigned int mask) + { + if (!m_initialized) + THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + glStencilMask(mask); + } + + void OpenGlRendererApi::setStencilFunc(unsigned int func, int ref, unsigned int mask) + { + if (!m_initialized) + THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + glStencilFunc(func, ref, mask); + } + + void OpenGlRendererApi::setStencilOp(unsigned int sfail, unsigned int dpfail, unsigned int dppass) + { + if (!m_initialized) + THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + glStencilOp(sfail, dpfail, dppass); + } } From 35f4e420a43b7c378251abf587ab631c6c8c5f1e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:00:41 +0900 Subject: [PATCH 155/450] fix(document-windows): now pass old and new signature to component manager when deleting array to avoid removing/adding unecessary entities to groups --- engine/src/ecs/Components.hpp | 43 +++++++++++++++++++++------------- engine/src/ecs/Coordinator.hpp | 10 ++++---- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 7ce4c02ab..ecf182df5 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -262,15 +262,18 @@ namespace nexo::ecs { * @tparam T The component type * @param entity The entity to add the component to * @param component The component instance to add - * @param signature The entity's current component signature + * @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 signature) + 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)) { - if ((signature & group->allSignature()) == group->allSignature()) { + // Check if entity qualifies now but did not qualify before. + if (((oldSignature & group->allSignature()) != group->allSignature()) && + ((newSignature & group->allSignature()) == group->allSignature())) { group->addToGroup(entity); } } @@ -285,17 +288,21 @@ namespace nexo::ecs { * @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) - { - for (const auto& group : std::ranges::views::values(m_groupRegistry)) - { - if ((previousSignature & group->allSignature()) == group->allSignature()) - group->removeFromGroup(entity); - } - getComponentArray()->remove(entity); - } + 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); + } + } + getComponentArray()->remove(entity); + } /** * @brief Attempts to remove a component from an entity @@ -306,10 +313,11 @@ namespace nexo::ecs { * @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) + bool tryRemoveComponent(Entity entity, const Signature previousSignature, const Signature newSignature) { auto componentArray = getComponentArray(); if (!componentArray->hasComponent(entity)) @@ -317,8 +325,11 @@ namespace nexo::ecs { for (const auto& group : std::ranges::views::values(m_groupRegistry)) { - if ((previousSignature & group->allSignature()) == group->allSignature()) - group->removeFromGroup(entity); + // 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; diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 7b60e388e..95b8896af 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -126,7 +126,7 @@ namespace nexo::ecs { Signature signature = m_entityManager->getSignature(entity); const Signature oldSignature = signature; signature.set(m_componentManager->getComponentType(), true); - m_componentManager->addComponent(entity, component, signature); + m_componentManager->addComponent(entity, component, oldSignature, signature); m_entityManager->setSignature(entity, signature); @@ -144,7 +144,7 @@ namespace nexo::ecs { Signature signature = m_entityManager->getSignature(entity); const Signature oldSignature = signature; signature.set(m_componentManager->getComponentType(), false); - m_componentManager->removeComponent(entity, oldSignature); + m_componentManager->removeComponent(entity, oldSignature, signature); m_entityManager->setSignature(entity, signature); @@ -164,10 +164,10 @@ namespace nexo::ecs { void tryRemoveComponent(const Entity entity) const { Signature signature = m_entityManager->getSignature(entity); - if (m_componentManager->tryRemoveComponent(entity, signature)) + Signature oldSignature = signature; + signature.set(m_componentManager->getComponentType(), false); + if (m_componentManager->tryRemoveComponent(entity, oldSignature, signature)) { - Signature oldSignature = signature; - signature.set(m_componentManager->getComponentType(), false); m_entityManager->setSignature(entity, signature); m_systemManager->entitySignatureChanged(entity, oldSignature, signature); From 9e812cc20d388277602d3a57c8fdd4f983bd6917 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:01:12 +0900 Subject: [PATCH 156/450] feat(document-windows): add opaque boolean to handle material properly --- engine/src/components/Render3D.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/src/components/Render3D.hpp b/engine/src/components/Render3D.hpp index 7f91d6605..7a0708122 100644 --- a/engine/src/components/Render3D.hpp +++ b/engine/src/components/Render3D.hpp @@ -25,6 +25,8 @@ namespace nexo::components { 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 @@ -35,6 +37,6 @@ namespace nexo::components { std::shared_ptr roughnessMap = nullptr; std::shared_ptr emissiveMap = nullptr; - std::optional> shader = std::nullopt; + std::string shader = ""; }; } From 14fbd584f301c512087203354ea73131e9f9390b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:01:32 +0900 Subject: [PATCH 157/450] feat(document-windows): add outline rendering --- engine/src/systems/RenderSystem.cpp | 125 ++++++++++++++++++---------- 1 file changed, 81 insertions(+), 44 deletions(-) diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index bdcb27274..202d713d7 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -14,6 +14,7 @@ #include "RenderSystem.hpp" #include "RendererContext.hpp" +#include "components/Editor.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" #include "components/Camera.hpp" @@ -29,60 +30,64 @@ 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) - */ - static void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext) - { - shader->bind(); - shader->setUniformFloat3("ambientLight", lightContext.ambientLight); - shader->setUniformInt("numDirLights", lightContext.directionalLightCount); - shader->setUniformInt("numPointLights", lightContext.pointLightCount); - shader->setUniformInt("numSpotLights", lightContext.spotLightCount); - - for (unsigned int i = 0; i < lightContext.directionalLightCount; ++i) + /** + * @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) + */ + static void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext) + { + static std::shared_ptr lastShader = nullptr; + if (lastShader == shader) + return; + lastShader = shader; + //shader->bind(); + shader->setUniformFloat3("uAmbientLight", lightContext.ambientLight); + shader->setUniformInt("uNumDirLights", lightContext.directionalLightCount); + shader->setUniformInt("uNumPointLights", lightContext.pointLightCount); + shader->setUniformInt("uNumSpotLights", lightContext.spotLightCount); + + if (lightContext.directionalLightCount) { - auto directionalLight = lightContext.directionalLights[i]; - shader->setUniformFloat3(std::format("dirLights[{}].direction", i), directionalLight.direction); - shader->setUniformFloat4(std::format("dirLights[{}].color", i), glm::vec4(directionalLight.color, 1.0f)); + auto directionalLight = lightContext.directionalLights[0]; + shader->setUniformFloat3("uDirLight.direction", directionalLight.direction); + shader->setUniformFloat4("uDirLight.color", glm::vec4(directionalLight.color, 1.0f)); } for (unsigned int i = 0; i < lightContext.pointLightCount; ++i) { auto pointLight = lightContext.pointLights[i]; - shader->setUniformFloat3(std::format("pointLights[{}].position", i), pointLight.pos); - shader->setUniformFloat4(std::format("pointLights[{}].color", i), glm::vec4(pointLight.color, 1.0f)); - shader->setUniformFloat(std::format("pointLights[{}].constant", i), pointLight.constant); - shader->setUniformFloat(std::format("pointLights[{}].linear", i), pointLight.linear); - shader->setUniformFloat(std::format("pointLights[{}].quadratic", i), pointLight.quadratic); + shader->setUniformFloat3(std::format("uPointLights[{}].position", i), pointLight.pos); + shader->setUniformFloat4(std::format("uPointLights[{}].color", i), glm::vec4(pointLight.color, 1.0f)); + shader->setUniformFloat(std::format("uPointLights[{}].constant", i), pointLight.constant); + shader->setUniformFloat(std::format("uPointLights[{}].linear", i), pointLight.linear); + shader->setUniformFloat(std::format("uPointLights[{}].quadratic", i), pointLight.quadratic); } for (unsigned int i = 0; i < lightContext.spotLightCount; ++i) { auto spotLight = lightContext.spotLights[i]; - shader->setUniformFloat3(std::format("spotLights[{}].position", i), spotLight.pos); - shader->setUniformFloat4(std::format("spotLights[{}].color", i), glm::vec4(spotLight.color, 1.0f)); - shader->setUniformFloat(std::format("spotLights[{}].constant", i), spotLight.constant); - shader->setUniformFloat(std::format("spotLights[{}].linear", i), spotLight.linear); - shader->setUniformFloat(std::format("spotLights[{}].quadratic", i), spotLight.quadratic); - shader->setUniformFloat3(std::format("spotLights[{}].direction", i), spotLight.direction); - shader->setUniformFloat(std::format("spotLights[{}].cutOff", i), spotLight.cutOff); - shader->setUniformFloat(std::format("spotLights[{}].outerCutoff", i), spotLight.outerCutoff); + shader->setUniformFloat3(std::format("uSpotLights[{}].position", i), spotLight.pos); + shader->setUniformFloat4(std::format("uSpotLights[{}].color", i), glm::vec4(spotLight.color, 1.0f)); + shader->setUniformFloat(std::format("uSpotLights[{}].constant", i), spotLight.constant); + shader->setUniformFloat(std::format("uSpotLights[{}].linear", i), spotLight.linear); + shader->setUniformFloat(std::format("uSpotLights[{}].quadratic", i), spotLight.quadratic); + shader->setUniformFloat3(std::format("uSpotLights[{}].direction", i), spotLight.direction); + shader->setUniformFloat(std::format("uSpotLights[{}].cutOff", i), spotLight.cutOff); + shader->setUniformFloat(std::format("uSpotLights[{}].outerCutoff", i), spotLight.outerCutoff); } - shader->unbind(); - } + //shader->unbind(); + } void RenderSystem::update() { @@ -93,7 +98,7 @@ namespace nexo::system { const auto sceneRendered = static_cast(renderContext.sceneRendered); const SceneType sceneType = renderContext.sceneType; - setupLights(renderContext.renderer3D.getShader(), renderContext.sceneLights); + //setupLights(renderContext.renderer3D.getShader(), renderContext.sceneLights); const auto scenePartition = m_group->getPartitionView( [](const components::SceneTag& tag) { return tag.id; } @@ -135,13 +140,45 @@ namespace nexo::system { continue; const auto &transform = transformSpan[i]; const auto &render = renderSpan[i]; + // This needs to be changed, i guess we should go toward a static mesh/material components, way better + const auto &material = std::dynamic_pointer_cast(render.renderable)->material; if (render.isRendered) { - renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition); + bool isSelected = coord->entityHasComponent(entity); + if (isSelected) + { + renderer::RenderCommand::setStencilFunc(GL_ALWAYS, 1, 0xFF); + renderer::RenderCommand::setStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + renderer::RenderCommand::setStencilMask(0xFF); + } + renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, material.shader); + auto shader = renderContext.renderer3D.getShader(); + setupLights(shader, renderContext.sceneLights); auto context = std::make_shared(); context->renderer3D = renderContext.renderer3D; render.draw(context, transform, static_cast(entity)); renderContext.renderer3D.endScene(); + if (isSelected) + { + renderer::RenderCommand::setStencilFunc(GL_NOTEQUAL, 1, 0xFF); + renderer::RenderCommand::setStencilMask(0x00); + renderer::RenderCommand::setDepthFunc(GL_LEQUAL); + float scaleFactor = 1.015f; + if (material.isOpaque) + renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, "Outline pulse flat"); + else { + renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, "Outline pulse transparent flat"); + scaleFactor = 1.1f; //Sligthly larger for transparency + } + auto outlineShader = renderContext.renderer3D.getShader(); + outlineShader->setUniformFloat("uTime", static_cast(glfwGetTime())); + render.draw(context, {transform.pos, transform.size * scaleFactor, transform.quat}); + renderContext.renderer3D.endScene(); + renderer::RenderCommand::setDepthFunc(GL_LESS); + renderer::RenderCommand::setStencilFunc(GL_ALWAYS, 0, 0xFF); + renderer::RenderCommand::setStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + renderer::RenderCommand::setStencilMask(0xFF); + } } } From bf58ba619497085ef7ade81a5152aeab05ca44da Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:01:44 +0900 Subject: [PATCH 158/450] chore(document-windows): add source files --- engine/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index e16fc028c..66a3e85b9 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -25,6 +25,7 @@ set(COMMON_SOURCES engine/src/core/event/WindowEvent.cpp engine/src/renderer/Buffer.cpp engine/src/renderer/Shader.cpp + engine/src/renderer/ShaderLibrary.cpp engine/src/renderer/ShaderStorageBuffer.cpp engine/src/renderer/VertexArray.cpp engine/src/renderer/RendererAPI.cpp From ddae85f5e16bdf54c17bc3095dafa34052e39f79 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:02:24 +0900 Subject: [PATCH 159/450] feat(document-windows): add albedo unshaded transparent shader to camera props --- editor/src/ImNexo/Panels.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index e2485e68d..e8c628bc5 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -106,8 +106,10 @@ namespace ImNexo { app.getSceneManager().getScene(sceneId).addEntity(static_cast(defaultCamera)); nexo::components::Material billboardMat{}; + billboardMat.isOpaque = false; std::shared_ptr cameraIconTexture = nexo::renderer::Texture2D::create(nexo::Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png").string()); billboardMat.albedoTexture = cameraIconTexture; + billboardMat.shader = "Albedo unshaded transparent"; auto billboard = std::make_shared(); auto renderable = std::make_shared(billboardMat, billboard); nexo::components::RenderComponent renderComponent(renderable, nexo::components::RenderType::RENDER_3D); From 732cf5bf658130081a0e52e954928ee383e55c68 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:03:00 +0900 Subject: [PATCH 160/450] style(document-windows): fix indent --- editor/src/DocumentWindows/SceneTreeWindow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index a17d0b1ef..5f4b4c727 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -105,10 +105,10 @@ namespace nexo::editor { return nodeOpen; if (ImGui::IsItemClicked()) { - auto &selector = Selector::get(); - selector.setSelectedEntity(obj.uuid, obj.data.entity); - selector.setSelectionType(obj.type); - selector.setSelectedScene(obj.data.sceneProperties.sceneId); + auto &selector = Selector::get(); + selector.setSelectedEntity(obj.uuid, obj.data.entity); + selector.setSelectionType(obj.type); + selector.setSelectedScene(obj.data.sceneProperties.sceneId); } return nodeOpen; } From 0e68978f33c9d563c919d51c1e73af1d3176df41 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:03:46 +0900 Subject: [PATCH 161/450] fix(document-windows): now properly set selection type when clicking on camera + properly handle gizmo when clicking on camera --- editor/src/DocumentWindows/EditorScene.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 0ea21e0db..aa349d5a5 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -28,6 +28,7 @@ #include "WindowRegistry.hpp" #include "components/Camera.hpp" #include "components/Uuid.hpp" +#include "components/Editor.hpp" #include "math/Matrix.hpp" #include "context/Selector.hpp" #include "utils/String.hpp" @@ -462,7 +463,7 @@ namespace nexo::editor { { const auto &coord = nexo::Application::m_coordinator; auto const &selector = Selector::get(); - if (selector.getSelectionType() != SelectionType::ENTITY || + if (selector.getSelectionType() == SelectionType::SCENE || selector.getSelectedScene() != m_sceneId) return; const ecs::Entity entity = selector.getSelectedEntity(); @@ -640,8 +641,12 @@ namespace nexo::editor { const auto uuid = Application::m_coordinator->tryGetComponent(data); if (uuid) { + auto &app = getApp(); selector.setSelectedEntity(uuid->get().uuid, data); - selector.setSelectionType(SelectionType::ENTITY); + if (app.m_coordinator->entityHasComponent(data)) + selector.setSelectionType(SelectionType::CAMERA); + else + selector.setSelectionType(SelectionType::ENTITY); } selector.setSelectedScene(m_sceneId); } From b42d230044e8aafaa94cd580e5dc53ac72a35e6f Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:04:21 +0900 Subject: [PATCH 162/450] feat(document-windows): add and remove selected component tag to display the outline in the scene --- editor/src/context/Selector.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/editor/src/context/Selector.cpp b/editor/src/context/Selector.cpp index c48891bb3..6957a7a6c 100644 --- a/editor/src/context/Selector.cpp +++ b/editor/src/context/Selector.cpp @@ -13,6 +13,8 @@ /////////////////////////////////////////////////////////////////////////////// #include "Selector.hpp" +#include "Application.hpp" +#include "components/Editor.hpp" namespace nexo::editor { int Selector::getSelectedEntity() const @@ -27,8 +29,14 @@ namespace nexo::editor { void Selector::setSelectedEntity(std::string_view uuid, const int entity) { + if (m_selectionType != SelectionType::NONE && m_selectionType != SelectionType::SCENE) + { + Application::m_coordinator->removeComponent(m_selectedEntity); + } m_selectedUuid = uuid; m_selectedEntity = entity; + components::SelectedTag selectTag{}; + Application::m_coordinator->addComponent(m_selectedEntity, selectTag); } void Selector::setSelectedScene(int scene) @@ -41,12 +49,16 @@ namespace nexo::editor { return m_selectedScene; } - void Selector::unselectEntity() - { - m_selectionType = SelectionType::NONE; - m_selectedEntity = -1; - m_selectedUuid = ""; - } + void Selector::unselectEntity() + { + if (m_selectionType != SelectionType::NONE && m_selectionType != SelectionType::SCENE) + { + Application::m_coordinator->removeComponent(m_selectedEntity); + } + m_selectionType = SelectionType::NONE; + m_selectedEntity = -1; + m_selectedUuid = ""; + } SelectionType Selector::getSelectionType() const { From 1c1f62de2241d6e42411a0d55074c4acd1a2324b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 19:39:49 +0900 Subject: [PATCH 163/450] refactor(document-windows): now using only one dir light per scene --- engine/src/components/Light.hpp | 14 ++++++-------- engine/src/components/RenderContext.hpp | 2 +- engine/src/core/exceptions/Exceptions.hpp | 7 ------- engine/src/systems/RenderSystem.cpp | 18 +++++++----------- .../src/systems/lights/AmbientLightSystem.cpp | 9 +++++---- .../systems/lights/DirectionalLightsSystem.cpp | 15 ++++++--------- resources/shaders/outline_pulse_flat.glsl | 12 ++++-------- .../outline_pulse_transparent_flat.glsl | 10 +++++----- resources/shaders/phong.glsl | 8 +------- 9 files changed, 35 insertions(+), 60 deletions(-) diff --git a/engine/src/components/Light.hpp b/engine/src/components/Light.hpp index fcbcd6974..0ebb7fee3 100644 --- a/engine/src/components/Light.hpp +++ b/engine/src/components/Light.hpp @@ -17,7 +17,6 @@ #include #include -constexpr unsigned int MAX_DIRECTIONAL_LIGHTS = 8; constexpr unsigned int MAX_POINT_LIGHTS = 8; constexpr unsigned int MAX_SPOT_LIGHTS = 8; @@ -81,12 +80,11 @@ namespace nexo::components { }; struct LightContext { - glm::vec3 ambientLight; - std::array pointLights; - unsigned int pointLightCount = 0; - std::array spotLights; - unsigned int spotLightCount = 0; - std::array directionalLights; - unsigned int directionalLightCount = 0; + glm::vec3 ambientLight; + std::array pointLights; + unsigned int pointLightCount = 0; + std::array spotLights; + unsigned int spotLightCount = 0; + DirectionalLightComponent dirLight; }; } diff --git a/engine/src/components/RenderContext.hpp b/engine/src/components/RenderContext.hpp index fa96e12b4..f5d2bc371 100644 --- a/engine/src/components/RenderContext.hpp +++ b/engine/src/components/RenderContext.hpp @@ -60,7 +60,7 @@ namespace nexo::components { sceneLights.ambientLight = glm::vec3(0.0f); sceneLights.pointLightCount = 0; sceneLights.spotLightCount = 0; - sceneLights.directionalLightCount = 0; + sceneLights.dirLight = DirectionalLightComponent{}; } }; } diff --git a/engine/src/core/exceptions/Exceptions.hpp b/engine/src/core/exceptions/Exceptions.hpp index b915dab0e..3ab8870a0 100644 --- a/engine/src/core/exceptions/Exceptions.hpp +++ b/engine/src/core/exceptions/Exceptions.hpp @@ -55,11 +55,4 @@ namespace nexo::core { 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 TooManyDirectionalLightsException : public Exception { - public: - explicit TooManyDirectionalLightsException(unsigned int sceneRendered, size_t nbDirectionalLights, - const std::source_location loc = std::source_location::current()) - : Exception(std::format("Too many directional lights ({} > {}) in scene [{}]", nbDirectionalLights, MAX_DIRECTIONAL_LIGHTS, sceneRendered), loc) {} - }; } diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 202d713d7..843e723f6 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -51,18 +51,15 @@ namespace nexo::system { if (lastShader == shader) return; lastShader = shader; - //shader->bind(); + if (!lastShader) + return; shader->setUniformFloat3("uAmbientLight", lightContext.ambientLight); - shader->setUniformInt("uNumDirLights", lightContext.directionalLightCount); shader->setUniformInt("uNumPointLights", lightContext.pointLightCount); shader->setUniformInt("uNumSpotLights", lightContext.spotLightCount); - if (lightContext.directionalLightCount) - { - auto directionalLight = lightContext.directionalLights[0]; - shader->setUniformFloat3("uDirLight.direction", directionalLight.direction); - shader->setUniformFloat4("uDirLight.color", glm::vec4(directionalLight.color, 1.0f)); - } + const auto &directionalLight = lightContext.dirLight; + shader->setUniformFloat3("uDirLight.direction", directionalLight.direction); + shader->setUniformFloat4("uDirLight.color", glm::vec4(directionalLight.color, 1.0f)); for (unsigned int i = 0; i < lightContext.pointLightCount; ++i) { @@ -86,7 +83,6 @@ namespace nexo::system { shader->setUniformFloat(std::format("uSpotLights[{}].cutOff", i), spotLight.cutOff); shader->setUniformFloat(std::format("uSpotLights[{}].outerCutoff", i), spotLight.outerCutoff); } - //shader->unbind(); } void RenderSystem::update() @@ -98,8 +94,6 @@ namespace nexo::system { const auto sceneRendered = static_cast(renderContext.sceneRendered); const SceneType sceneType = renderContext.sceneType; - //setupLights(renderContext.renderer3D.getShader(), renderContext.sceneLights); - const auto scenePartition = m_group->getPartitionView( [](const components::SceneTag& tag) { return tag.id; } ); @@ -189,5 +183,7 @@ namespace nexo::system { } renderContext.cameras.pop(); } + // We have to do this for now to reset the shader stored as a static here, this will change later + setupLights(nullptr, renderContext.sceneLights); } } diff --git a/engine/src/systems/lights/AmbientLightSystem.cpp b/engine/src/systems/lights/AmbientLightSystem.cpp index 3a24b4469..f992bae95 100644 --- a/engine/src/systems/lights/AmbientLightSystem.cpp +++ b/engine/src/systems/lights/AmbientLightSystem.cpp @@ -42,10 +42,11 @@ namespace nexo::system { } nexo::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); + else + nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("For scene {}, found {} ambient lights, only one is supported, picking the first one", sceneName, partition->count)); - const auto ambientSpan = get(); - if (ambientSpan.size()) - renderContext.sceneLights.ambientLight = ambientSpan[0].color; - + renderContext.sceneLights.ambientLight = get()[0].color; } } diff --git a/engine/src/systems/lights/DirectionalLightsSystem.cpp b/engine/src/systems/lights/DirectionalLightsSystem.cpp index 562215492..0f5cba667 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.cpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.cpp @@ -43,15 +43,12 @@ namespace nexo::system { } nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No directional light found in scene {}, skipping", sceneName)); - if (partition->count > MAX_DIRECTIONAL_LIGHTS) - THROW_EXCEPTION(core::TooManyDirectionalLightsException, sceneRendered, partition->count); + if (partition->count != 1) + 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)); - const auto directionalLightSpan = get(); - - for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) - { - renderContext.sceneLights.directionalLights[renderContext.sceneLights.directionalLightCount] = directionalLightSpan[i]; - renderContext.sceneLights.directionalLightCount++; - } + const auto &dirLight = get()[0]; + renderContext.sceneLights.dirLight = dirLight; } } diff --git a/resources/shaders/outline_pulse_flat.glsl b/resources/shaders/outline_pulse_flat.glsl index 5212b1725..2f5eacfa2 100644 --- a/resources/shaders/outline_pulse_flat.glsl +++ b/resources/shaders/outline_pulse_flat.glsl @@ -7,10 +7,8 @@ uniform mat4 uMatModel; void main() { - // Calculate position in world space vec4 worldPos = uMatModel * vec4(aPos, 1.0); - // Transform to clip space gl_Position = uViewProjection * worldPos; } @@ -18,22 +16,20 @@ void main() #version 430 core layout(location = 0) out vec4 FragColor; -uniform float uTime; // For subtle animation +uniform float uTime; void main() { - // Base colors - vec4 purpleColor = vec4(0.5, 0.0, 1.0, 1.0); // Purple - vec4 blueColor = vec4(0.0, 0.4, 0.9, 1.0); // Blue + vec4 purpleColor = vec4(0.5, 0.0, 1.0, 1.0); + vec4 blueColor = vec4(0.0, 0.4, 0.9, 1.0); // Color shifting effect float colorShift = (sin(uTime * 3.0) * 0.5 + 0.5); vec4 baseColor = mix(purpleColor, blueColor, colorShift); - // Contrast pulsation (different frequency than color shift) + // Contrast pulsation float contrastPulse = (sin(uTime * 2.3) * 0.5 + 0.5) * 0.5 + 0.5; // Range from 0.5 to 1.0 - // Apply contrast variation vec4 highContrastColor = vec4( pow(baseColor.r, contrastPulse), pow(baseColor.g, contrastPulse), diff --git a/resources/shaders/outline_pulse_transparent_flat.glsl b/resources/shaders/outline_pulse_transparent_flat.glsl index 4fd0fff66..a472efba9 100644 --- a/resources/shaders/outline_pulse_transparent_flat.glsl +++ b/resources/shaders/outline_pulse_transparent_flat.glsl @@ -28,21 +28,21 @@ struct Material { uniform Material uMaterial; uniform sampler2D uTexture[32]; -uniform float uTime; // For subtle animation +uniform float uTime; void main() { if (texture(uTexture[uMaterial.albedoTexIndex], vTexCoord).a < 0.1) discard; - // Base colors - vec4 purpleColor = vec4(0.5, 0.0, 1.0, 1.0); // Purple - vec4 blueColor = vec4(0.0, 0.4, 0.9, 1.0); // Blue + + vec4 purpleColor = vec4(0.5, 0.0, 1.0, 1.0); + vec4 blueColor = vec4(0.0, 0.4, 0.9, 1.0); // Color shifting effect float colorShift = (sin(uTime * 3.0) * 0.5 + 0.5); vec4 baseColor = mix(purpleColor, blueColor, colorShift); - // Contrast pulsation (different frequency than color shift) + // Contrast pulsation float contrastPulse = (sin(uTime * 2.3) * 0.5 + 0.5) * 0.5 + 0.5; // Range from 0.5 to 1.0 // Apply contrast variation diff --git a/resources/shaders/phong.glsl b/resources/shaders/phong.glsl index 9c4007f8f..323ff4558 100644 --- a/resources/shaders/phong.glsl +++ b/resources/shaders/phong.glsl @@ -34,7 +34,6 @@ void main() layout(location = 0) out vec4 FragColor; layout(location = 1) out int EntityID; -#define MAX_DIR_LIGHTS 4 #define MAX_POINT_LIGHTS 8 #define MAX_SPOT_LIGHTS 8 @@ -73,12 +72,12 @@ uniform sampler2D uTexture[32]; uniform vec3 uCamPos; +uniform vec3 uAmbientLight; uniform DirectionalLight uDirLight; uniform PointLight uPointLights[MAX_POINT_LIGHTS]; uniform int uNumPointLights; uniform SpotLight uSpotLights[MAX_SPOT_LIGHTS]; uniform int uNumSpotLights; -uniform vec3 uAmbientLight; struct Material { vec4 albedoColor; @@ -106,7 +105,6 @@ vec3 CalcDirLight(DirectionalLight light, vec3 normal, vec3 viewDir) float shininess = mix(128.0, 2.0, uMaterial.roughness); float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess); // combine results - //vec3 ambient = ambientLight * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); vec3 diffuse = light.color.rgb * diff * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); vec3 specular = light.color.rgb * spec * uMaterial.specularColor.rgb * vec3(texture(uTexture[uMaterial.specularTexIndex], vTexCoord)); return (diffuse + specular); @@ -125,10 +123,8 @@ vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) float distance = length(light.position - fragPos); float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); // combine results - //vec3 ambient = ambientLight * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); vec3 diffuse = light.color.rgb * diff * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); vec3 specular = light.color.rgb * spec * uMaterial.specularColor.rgb * vec3(texture(uTexture[uMaterial.specularTexIndex], vTexCoord)); - //ambient *= attenuation; diffuse *= attenuation; specular *= attenuation; return (diffuse + specular); @@ -151,10 +147,8 @@ vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) float epsilon = light.cutOff - light.outerCutoff; float intensity = clamp((theta - light.outerCutoff) / epsilon, 0.0, 1.0); // combine results - //vec3 ambient = ambientLight * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); vec3 diffuse = light.color.rgb * diff * uMaterial.albedoColor.rgb * vec3(texture(uTexture[uMaterial.albedoTexIndex], vTexCoord)); vec3 specular = light.color.rgb * spec * uMaterial.specularColor.rgb * vec3(texture(uTexture[uMaterial.specularTexIndex], vTexCoord)); - //ambient *= attenuation * intensity; diffuse *= attenuation * intensity; specular *= attenuation * intensity; return (diffuse + specular); From db7380090dc9451224d8f43698ab3335c076cf21 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 20:02:00 +0900 Subject: [PATCH 164/450] fix(document-windows): fix ecs tests --- tests/ecs/Components.test.cpp | 116 ++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 35 deletions(-) diff --git a/tests/ecs/Components.test.cpp b/tests/ecs/Components.test.cpp index 95824bee7..c9c32a481 100644 --- a/tests/ecs/Components.test.cpp +++ b/tests/ecs/Components.test.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include #include "Components.hpp" +#include "Definitions.hpp" #include "ECSExceptions.hpp" #include @@ -130,9 +131,11 @@ namespace nexo::ecs { const Entity entity = 1; const TestComponentA component(42); Signature signature; + Signature oldSignature = signature; + signature.set(componentManager.getComponentType(), true); // Add component to entity - componentManager.addComponent(entity, component, signature); + componentManager.addComponent(entity, component, oldSignature, signature); // Check that the component was added auto& retrievedComponent = componentManager.getComponent(entity); @@ -148,15 +151,19 @@ namespace nexo::ecs { const Entity entity = 1; const TestComponentA component(42); Signature signature; + Signature oldSignature = signature; + signature.set(componentManager.getComponentType(), true); // Add component to entity - componentManager.addComponent(entity, component, signature); + componentManager.addComponent(entity, component, oldSignature, signature); // Verify component exists EXPECT_TRUE(componentManager.getComponentArray()->hasComponent(entity)); // Remove component - componentManager.removeComponent(entity, signature); + Signature newSignature = signature; + newSignature.set(componentManager.getComponentType(), false); + componentManager.removeComponent(entity, signature, newSignature); // Check that the component was removed EXPECT_FALSE(componentManager.getComponentArray()->hasComponent(entity)); @@ -167,12 +174,16 @@ namespace nexo::ecs { const Entity entity = 1; const TestComponentA component(42); Signature signature; + Signature oldSignature = signature; + signature.set(componentManager.getComponentType(), true); // Add component to entity - componentManager.addComponent(entity, component, signature); + componentManager.addComponent(entity, component, oldSignature, signature); // Try to remove component - bool removed = componentManager.tryRemoveComponent(entity, signature); + Signature newSignature = signature; + newSignature.set(componentManager.getComponentType(), false); + bool removed = componentManager.tryRemoveComponent(entity, signature, newSignature); // Check that the component was removed and function returned true EXPECT_TRUE(removed); @@ -182,9 +193,11 @@ namespace nexo::ecs { TEST_F(ComponentManagerTest, TryRemoveComponentReturnsFalseIfNotExist) { const Entity entity = 1; Signature signature; + Signature oldSignature = signature; + signature.set(componentManager.getComponentType(), true); // Try to remove a component that doesn't exist - bool removed = componentManager.tryRemoveComponent(entity, signature); + bool removed = componentManager.tryRemoveComponent(entity, oldSignature, signature); // Check that the function returned false EXPECT_FALSE(removed); @@ -194,9 +207,11 @@ namespace nexo::ecs { const Entity entity = 1; const TestComponentA component(42); Signature signature; + Signature oldSignature = signature; + signature.set(componentManager.getComponentType(), true); // Add component to entity - componentManager.addComponent(entity, component, signature); + componentManager.addComponent(entity, component, oldSignature, signature); // Get component auto& retrievedComponent = componentManager.getComponent(entity); @@ -214,9 +229,11 @@ namespace nexo::ecs { const Entity entity = 1; const TestComponentA component(42); Signature signature; + Signature oldSignature = signature; + signature.set(componentManager.getComponentType(), true); // Add component to entity - componentManager.addComponent(entity, component, signature); + componentManager.addComponent(entity, component, oldSignature, signature); // Try to get component auto optComponent = componentManager.tryGetComponent(entity); @@ -239,13 +256,16 @@ namespace nexo::ecs { TEST_F(ComponentManagerTest, EntityDestroyedRemovesAllComponents) { const Entity entity = 1; Signature signature; + Signature oldSignature = signature; + signature.set(componentManager.getComponentType(), true); // Add multiple components to the entity signature.set(getComponentTypeID()); - componentManager.addComponent(entity, TestComponentA(42), signature); + componentManager.addComponent(entity, TestComponentA(42), oldSignature, signature); - signature.set(getComponentTypeID()); - componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature); + Signature newSignature = signature; + newSignature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature, newSignature); // Verify the components exist EXPECT_TRUE(componentManager.getComponentArray()->hasComponent(entity)); @@ -395,12 +415,14 @@ namespace nexo::ecs { // Create required entity components const Entity entity = 1; Signature signature; + Signature oldSignature = signature; signature.set(getComponentTypeID()); - componentManager.addComponent(entity, TestComponentA(42), signature); + componentManager.addComponent(entity, TestComponentA(42), oldSignature, signature); - signature.set(getComponentTypeID()); - componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature); + Signature newSignature = signature; + newSignature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature, newSignature); // Entity should be automatically added to the group EXPECT_EQ(group->size(), 1); @@ -415,12 +437,15 @@ namespace nexo::ecs { // Create entity with required components first const Entity entity = 1; Signature signature; + Signature oldSignature = signature; signature.set(getComponentTypeID()); - componentManager.addComponent(entity, TestComponentA(42), signature); + componentManager.addComponent(entity, TestComponentA(42), oldSignature, signature); - signature.set(getComponentTypeID()); - componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature); + Signature newSignature = signature; + newSignature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature, newSignature); + signature = newSignature; // Register group auto group = componentManager.registerGroup(get()); @@ -428,8 +453,10 @@ namespace nexo::ecs { // Verify entity is in the group EXPECT_EQ(group->size(), 1); + // Remove a required component - componentManager.removeComponent(entity, signature); + newSignature.set(getComponentTypeID(), false); + componentManager.removeComponent(entity, signature, newSignature); // Entity should be automatically removed from the group EXPECT_EQ(group->size(), 0); @@ -441,11 +468,13 @@ namespace nexo::ecs { for (Entity e = 1; e <= 5; ++e) { Signature signature; + Signature oldSignature = signature; signature.set(getComponentTypeID()); - componentManager.addComponent(e, TestComponentA(e * 10), signature); + componentManager.addComponent(e, TestComponentA(e * 10), oldSignature, signature); + oldSignature = signature; signature.set(getComponentTypeID()); - componentManager.addComponent(e, TestComponentB(e * 1.0f, e * 2.0f), signature); + componentManager.addComponent(e, TestComponentB(e * 1.0f, e * 2.0f), oldSignature, signature); } // All entities should be in the group @@ -454,9 +483,12 @@ namespace nexo::ecs { signature.set(getComponentTypeID()); signature.set(getComponentTypeID()); + Signature newSignature = signature; + signature.set(getComponentTypeID(), false); + // Remove some entities for (Entity e = 1; e <= 5; e += 2) { - componentManager.removeComponent(e, signature); + componentManager.removeComponent(e, signature, newSignature); } // Only remaining entities should be in the group @@ -473,12 +505,14 @@ namespace nexo::ecs { // Create entity with required components const Entity entity = 1; Signature signature; + Signature oldSignature = signature; - componentManager.addComponent(entity, TestComponentA(42), signature); - signature.set(getComponentTypeID()); + signature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentA(42), oldSignature, signature); - componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), signature); - signature.set(getComponentTypeID()); + oldSignature = signature; + signature.set(getComponentTypeID()); + componentManager.addComponent(entity, TestComponentB(1.0f, 2.0f), oldSignature, signature); // Register group auto group = componentManager.registerGroup(get()); @@ -507,44 +541,56 @@ namespace nexo::ecs { // Create entities with various component combinations Signature signature1, signature2, signature3; + Signature oldSignature1, oldSignature2, oldSignature3; // Entity 1: A + B + C + oldSignature1 = signature1; signature1.set(getComponentTypeID()); - componentManager.addComponent(1, TestComponentA(10), signature1); + componentManager.addComponent(1, TestComponentA(10), oldSignature1, signature1); + oldSignature1 = signature1; signature1.set(getComponentTypeID()); - componentManager.addComponent(1, TestComponentB(1.0f, 2.0f), signature1); + componentManager.addComponent(1, TestComponentB(1.0f, 2.0f), oldSignature1, signature1); + oldSignature1 = signature1; signature1.set(getComponentTypeID()); - componentManager.addComponent(1, TestComponentC("Entity1"), signature1); + componentManager.addComponent(1, TestComponentC("Entity1"), oldSignature1, signature1); // Entity 2: A + B + D + oldSignature2 = signature2; signature2.set(getComponentTypeID()); - componentManager.addComponent(2, TestComponentA(20), signature2); + componentManager.addComponent(2, TestComponentA(20), oldSignature2, signature2); + oldSignature2 = signature2; signature2.set(getComponentTypeID()); - componentManager.addComponent(2, TestComponentB(3.0f, 4.0f), signature2); + componentManager.addComponent(2, TestComponentB(3.0f, 4.0f), oldSignature2, signature2); + oldSignature2 = signature2; signature2.set(getComponentTypeID()); - componentManager.addComponent(2, TestComponentD(true), signature2); + componentManager.addComponent(2, TestComponentD(true), oldSignature2, signature2); // Entity 3: C + D + oldSignature3 = signature3; signature3.set(getComponentTypeID()); - componentManager.addComponent(3, TestComponentC("Entity3"), signature3); + componentManager.addComponent(3, TestComponentC("Entity3"), oldSignature3, signature3); + oldSignature3 = signature3; signature3.set(getComponentTypeID()); - componentManager.addComponent(3, TestComponentD(false), signature3); + componentManager.addComponent(3, TestComponentD(false), oldSignature3, signature3); // Verify group membership EXPECT_EQ(groupAB->size(), 2); // Entities 1 and 2 EXPECT_EQ(groupCD->size(), 1); // Entity 3 // Remove components to change group membership - componentManager.removeComponent(1, signature1); + Signature newSignature1 = signature1; + newSignature1.set(getComponentTypeID(), false); + componentManager.removeComponent(1, signature1, newSignature1); signature1.reset(getComponentTypeID()); + oldSignature1 = signature1; signature1.set(getComponentTypeID()); - componentManager.addComponent(1, TestComponentD(true), signature1); + componentManager.addComponent(1, TestComponentD(true), oldSignature1, signature1); // Check updated group membership EXPECT_EQ(groupAB->size(), 1); // Only Entity 2 now From 473cae30e30eb81957a1b2e4fa3cb4a3a05b6a96 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 20:36:33 +0900 Subject: [PATCH 165/450] chore(document-windows): remove test for renderer2D + add source file --- tests/renderer/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/renderer/CMakeLists.txt b/tests/renderer/CMakeLists.txt index 944de208b..cde699127 100644 --- a/tests/renderer/CMakeLists.txt +++ b/tests/renderer/CMakeLists.txt @@ -31,6 +31,7 @@ set(RENDERER_SOURCES engine/src/renderer/Window.cpp engine/src/renderer/Buffer.cpp engine/src/renderer/Shader.cpp + engine/src/renderer/ShaderLibrary.cpp engine/src/renderer/VertexArray.cpp engine/src/renderer/RendererAPI.cpp engine/src/renderer/Renderer.cpp @@ -61,7 +62,6 @@ add_executable(renderer_tests ${BASEDIR}/Shader.test.cpp ${BASEDIR}/RendererAPI.test.cpp ${BASEDIR}/Texture.test.cpp - ${BASEDIR}/Renderer2D.test.cpp ${BASEDIR}/Renderer3D.test.cpp ${BASEDIR}/Exceptions.test.cpp ) From e8f609ed0aa0c67973015dd4f67a7f7be65afc14 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 20:36:51 +0900 Subject: [PATCH 166/450] fix(document-windows): fix shader test, now use map instead of array --- tests/renderer/Shader.test.cpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tests/renderer/Shader.test.cpp b/tests/renderer/Shader.test.cpp index c619bf424..41194c763 100644 --- a/tests/renderer/Shader.test.cpp +++ b/tests/renderer/Shader.test.cpp @@ -166,9 +166,23 @@ namespace nexo::renderer { // Uniforms values constexpr unsigned int nbUniforms = 6; - const char *uniformsName[nbUniforms] = {"uFloat", "uVec3", "uVec4", "uInt", "uModel", "uIntArray"}; - constexpr GLenum uniformsType[nbUniforms] = {GL_FLOAT, GL_FLOAT_VEC3, GL_FLOAT_VEC4, GL_INT, GL_FLOAT_MAT4, GL_SAMPLER_2D}; - constexpr int uniformsSize[nbUniforms] ={1, 1, 1, 1, 1, 3}; + const std::unordered_map uniformsTypeMap = { + {"uFloat", GL_FLOAT}, + {"uVec3", GL_FLOAT_VEC3}, + {"uVec4", GL_FLOAT_VEC4}, + {"uInt", GL_INT}, + {"uModel", GL_FLOAT_MAT4}, + {"uIntArray", GL_SAMPLER_2D} + }; + + const std::unordered_map uniformsSizeMap = { + {"uFloat", 1}, + {"uVec3", 1}, + {"uVec4", 1}, + {"uInt", 1}, + {"uModel", 1}, + {"uIntArray", 3} + }; OpenGlShader shader("TestShader", vertexShaderSourceTestUniforms, fragmentShaderSourceTestUniforms); shader.bind(); @@ -190,21 +204,17 @@ namespace nexo::renderer { // The uniform is not an array if (std::string(name).find('[') == std::string::npos) { - // Validate name - EXPECT_EQ(std::string(name), uniformsName[i]); // Validate size (should be always one) - EXPECT_EQ(size, uniformsSize[i]); + EXPECT_EQ(size, uniformsSizeMap.at(name)); // Validate type - EXPECT_EQ(type, uniformsType[i]); + EXPECT_EQ(type, uniformsTypeMap.at(name)); } else { // Retrieve the base name of the array std::string baseName = std::string(name).substr(0, std::string(name).find('[')); - // Validate name - EXPECT_EQ(baseName, uniformsName[i]); // Validate size - EXPECT_EQ(size, uniformsSize[i]); + EXPECT_EQ(size, uniformsSizeMap.at(baseName.c_str())); // Validate type - EXPECT_EQ(type, uniformsType[i]); + EXPECT_EQ(type, uniformsTypeMap.at(baseName.c_str())); } } From 7adf9008306b721efef062dece31ce63be6605ce Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 20:37:30 +0900 Subject: [PATCH 167/450] chore(document-windows): remove useless print --- engine/src/renderer/opengl/OpenGlShader.cpp | 1 - tests/renderer/Renderer2D.test.cpp | 496 -------------------- 2 files changed, 497 deletions(-) delete mode 100644 tests/renderer/Renderer2D.test.cpp diff --git a/engine/src/renderer/opengl/OpenGlShader.cpp b/engine/src/renderer/opengl/OpenGlShader.cpp index 8cdb92f59..2ae905162 100644 --- a/engine/src/renderer/opengl/OpenGlShader.cpp +++ b/engine/src/renderer/opengl/OpenGlShader.cpp @@ -37,7 +37,6 @@ namespace nexo::renderer { OpenGlShader::OpenGlShader(const std::string &path) { - std::cout << path << std::endl; const std::string src = readFile(path); auto shaderSources = preProcess(src, path); compile(shaderSources); diff --git a/tests/renderer/Renderer2D.test.cpp b/tests/renderer/Renderer2D.test.cpp deleted file mode 100644 index eed8992e5..000000000 --- a/tests/renderer/Renderer2D.test.cpp +++ /dev/null @@ -1,496 +0,0 @@ -//// Renderer2D.test.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: 25/11/2024 -// Description: Test file for the renderer 2D -// -/////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include "renderer/Renderer2D.hpp" -#include "renderer/Texture.hpp" -#include "renderer/SubTexture2D.hpp" -#include "renderer/RendererExceptions.hpp" -#include "renderer/Renderer.hpp" -#include "contexts/opengl.hpp" -#include "../utils/comparison.hpp" - -namespace nexo::renderer { - class Renderer2DTest : public ::testing::Test { - GLFWwindow *window = nullptr; - - protected: - void SetUp() override - { - if (!glfwInit()) - { - GTEST_SKIP() << "GLFW initialization failed. Skipping OpenGL tests."; - } - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - window = glfwCreateWindow(800, 600, "Test Window", nullptr, nullptr); - if (!window) - { - glfwTerminate(); - GTEST_SKIP() << "Failed to create GLFW window. Skipping OpenGL tests."; - } - - glfwMakeContextCurrent(window); - - if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) - { - glfwDestroyWindow(window); - glfwTerminate(); - GTEST_SKIP() << "Failed to initialize GLAD. Skipping OpenGL tests."; - } - - GLint major = 0, minor = 0; - glGetIntegerv(GL_MAJOR_VERSION, &major); - glGetIntegerv(GL_MINOR_VERSION, &minor); - if (major < 4 || (major == 4 && minor < 5)) - { - glfwDestroyWindow(window); - glfwTerminate(); - GTEST_SKIP() << "OpenGL 4.5 is required. Skipping OpenGL tests."; - } - renderer2D = std::make_unique(); - Renderer::init(); - renderer2D->init(); - } - - void TearDown() override - { - renderer2D->shutdown(); - if (window) - { - glfwDestroyWindow(window); - } - } - - std::unique_ptr renderer2D; - }; - - TEST_F(Renderer2DTest, BeginEndScene) - { - glm::mat4 viewProjection = glm::mat4(1.0f); - - EXPECT_NO_THROW(renderer2D->beginScene(viewProjection)); - EXPECT_NO_THROW(renderer2D->endScene()); - } - - TEST_F(Renderer2DTest, DrawQuadWithoutTexture) - { - glm::vec2 position = {0.0f, 0.0f}; - glm::vec2 size = {1.0f, 1.0f}; - glm::vec4 color = {1.0f, 0.0f, 0.0f, 1.0f}; // Red color - - renderer2D->beginScene(glm::mat4(1.0f)); - - // Draw quad without texture - EXPECT_NO_THROW(renderer2D->drawQuad(position, size, color)); - - // Validate number of primitives drawn - GLuint query; - glGenQueries(1, &query); - glBeginQuery(GL_PRIMITIVES_GENERATED, query); - renderer2D->endScene(); - glEndQuery(GL_PRIMITIVES_GENERATED); - GLuint primitivesRendered = 0; - glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitivesRendered); - EXPECT_EQ(primitivesRendered, 2); // 2 triangles - glDeleteQueries(1, &query); - - // Validate vertex and index buffers content - GLuint vertexBufferId = renderer2D->getInternalStorage()->vertexBuffer->getId(); - GLuint indexBufferId = renderer2D->getInternalStorage()->indexBuffer->getId(); - glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId); - std::vector vertexData(4); // Expecting 4 vertices - glGetBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(QuadVertex), vertexData.data()); - - // Validate vertex positions - EXPECT_EQ(vertexData[0].position, glm::vec3(-0.5f, -0.5f, 0.0f)); // Bottom-left - EXPECT_EQ(vertexData[1].position, glm::vec3(0.5f, -0.5f, 0.0f)); // Bottom-right - EXPECT_EQ(vertexData[2].position, glm::vec3(0.5f, 0.5f, 0.0f)); // Top-right - EXPECT_EQ(vertexData[3].position, glm::vec3(-0.5f, 0.5f, 0.0f)); // Top-left - // Validate vertex colors - for (int i = 0; i < 4; ++i) - { - EXPECT_EQ(vertexData[i].color, color); - } - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId); - std::vector indexData(6); // Expecting 6 indices - glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, 6 * sizeof(unsigned int), indexData.data()); - // Validate indices - EXPECT_EQ(indexData[0], 0); // First triangle - EXPECT_EQ(indexData[1], 1); - EXPECT_EQ(indexData[2], 2); - EXPECT_EQ(indexData[3], 2); // Second triangle - EXPECT_EQ(indexData[4], 3); - EXPECT_EQ(indexData[5], 0); - - // Validate stats - RendererStats stats = renderer2D->getStats(); - EXPECT_EQ(stats.quadCount, 1); - EXPECT_EQ(stats.getTotalVertexCount(), 4); // 1 quad * 4 vertices - EXPECT_EQ(stats.getTotalIndexCount(), 6); // 1 quad * 6 indices - } - - TEST_F(Renderer2DTest, DrawQuadWithTexture) - { - glm::vec2 position = {0.0f, 0.0f}; - glm::vec2 size = {1.0f, 1.0f}; - auto texture = Texture2D::create(2, 2); // Create a simple 2x2 texture - glm::vec4 expectedColor = {1.0f, 1.0f, 1.0f, 1.0f}; // White color for textured quads - - renderer2D->beginScene(glm::mat4(1.0f)); - - // Draw quad with texture - EXPECT_NO_THROW(renderer2D->drawQuad(position, size, texture)); - renderer2D->endScene(); - - // Validate vertex buffer content - GLuint vertexBufferId = renderer2D->getInternalStorage()->vertexBuffer->getId(); - glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId); - - // Read back vertex data - std::vector vertexData(4); // Expecting 4 vertices for the quad - glGetBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(QuadVertex), vertexData.data()); - - // Expected vertex positions (untransformed quad) - glm::vec3 expectedPositions[] = { - {-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 - }; - - // Expected texture coordinates - glm::vec2 expectedTexCoords[] = { - {0.0f, 0.0f}, // Bottom-left - {1.0f, 0.0f}, // Bottom-right - {1.0f, 1.0f}, // Top-right - {0.0f, 1.0f} // Top-left - }; - - // Validate each vertex - for (int i = 0; i < 4; ++i) - { - EXPECT_VEC3_NEAR(vertexData[i].position, expectedPositions[i], 0.01f); - EXPECT_VEC2_NEAR(vertexData[i].texCoord, expectedTexCoords[i], 0.01f); - EXPECT_VEC4_NEAR(vertexData[i].color, expectedColor, 0.01f); - EXPECT_EQ(vertexData[i].texIndex, 1.0f); // Texture index in the shader - } - - // Validate index buffer content - GLuint indexBufferId = renderer2D->getInternalStorage()->indexBuffer->getId(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId); - - std::vector indexData(6); // Expecting 6 indices for the quad - glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, 6 * sizeof(unsigned int), indexData.data()); - - // Expected indices for two triangles - unsigned int expectedIndices[] = {0, 1, 2, 2, 3, 0}; - for (int i = 0; i < 6; ++i) - { - EXPECT_EQ(indexData[i], expectedIndices[i]); - } - - // Validate stats - RendererStats stats = renderer2D->getStats(); - EXPECT_EQ(stats.quadCount, 1); // One quad drawn - EXPECT_EQ(stats.getTotalVertexCount(), 4); // 1 quad * 4 vertices - EXPECT_EQ(stats.getTotalIndexCount(), 6); // 1 quad * 6 indices - } - - - TEST_F(Renderer2DTest, DrawQuadWithSubTexture) - { - glm::vec2 position = {0.0f, 0.0f}; - glm::vec2 size = {1.0f, 1.0f}; - auto texture = Texture2D::create(4, 4); // Base texture - auto subTexture = SubTexture2D::createFromCoords(texture, {1, 1}, {2, 2}, {1, 1}); // SubTexture - - renderer2D->beginScene(glm::mat4(1.0f)); - - // Draw quad with SubTexture - EXPECT_NO_THROW(renderer2D->drawQuad(position, size, subTexture)); - - renderer2D->endScene(); - - // Validate vertex buffer content - GLuint vertexBufferId = renderer2D->getInternalStorage()->vertexBuffer->getId(); - glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId); - std::vector vertexData(4); // Expecting 4 vertices - glGetBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(QuadVertex), vertexData.data()); - - // Normalize expected texture coordinates - float texWidth = static_cast(texture->getWidth()); - float texHeight = static_cast(texture->getHeight()); - glm::vec2 expectedTexCoords[] = { - {2.0f / texWidth, 2.0f / texHeight}, // Bottom-left - {4.0f / texWidth, 2.0f / texHeight}, // Bottom-right - {4.0f / texWidth, 4.0f / texHeight}, // Top-right - {2.0f / texWidth, 4.0f / texHeight}, // Top-left - }; - // Validate texture coordinates - for (int i = 0; i < 4; ++i) - { - EXPECT_VEC2_NEAR(vertexData[i].texCoord, expectedTexCoords[i], 0.01f); - } - - // Validate stats - RendererStats stats = renderer2D->getStats(); - EXPECT_EQ(stats.quadCount, 1); - } - - - TEST_F(Renderer2DTest, DrawQuadWithRotation) - { - glm::vec2 position = {0.0f, 0.0f}; - glm::vec2 size = {1.0f, 1.0f}; - float rotation = 45.0f; // Degrees - glm::vec4 color = {1.0f, 0.0f, 0.0f, 1.0f}; - - renderer2D->beginScene(glm::mat4(1.0f)); - - // Draw quad with rotation - EXPECT_NO_THROW(renderer2D->drawQuad(position, size, rotation, color)); - - renderer2D->endScene(); - - GLuint vertexBufferId = renderer2D->getInternalStorage()->vertexBuffer->getId(); - glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId); - std::vector vertexData(4); // Expecting 4 vertices - glGetBufferSubData(GL_ARRAY_BUFFER, 0, 4 * sizeof(QuadVertex), vertexData.data()); - - glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(position, 0.0f)) * - glm::rotate(glm::mat4(1.0f), glm::radians(rotation), glm::vec3(0.0f, 0.0f, 1.0f)) * - glm::scale(glm::mat4(1.0f), glm::vec3(size, 1.0f)); - - // Validate rotated positions - glm::vec4 expectedPositions[] = { - transform * glm::vec4(-0.5f, -0.5f, 0.0f, 1.0f), - transform * glm::vec4(0.5f, -0.5f, 0.0f, 1.0f), - transform * glm::vec4(0.5f, 0.5f, 0.0f, 1.0f), - transform * glm::vec4(-0.5f, 0.5f, 0.0f, 1.0f), - }; - for (int i = 0; i < 4; ++i) - { - EXPECT_VEC3_NEAR(vertexData[i].position, expectedPositions[i], 0.01f); - } - - - // Validate stats - RendererStats stats = renderer2D->getStats(); - EXPECT_EQ(stats.quadCount, 1); - } - - TEST_F(Renderer2DTest, DrawMultipleQuads) - { - glm::vec2 position1 = {0.0f, 0.0f}; - glm::vec2 position2 = {2.0f, 2.0f}; - glm::vec2 size = {1.0f, 1.0f}; - glm::vec4 color1 = {1.0f, 0.0f, 0.0f, 1.0f}; - glm::vec4 color2 = {0.0f, 1.0f, 0.0f, 1.0f}; - - renderer2D->beginScene(glm::mat4(1.0f)); - - // Draw two quads - EXPECT_NO_THROW(renderer2D->drawQuad(position1, size, color1)); - EXPECT_NO_THROW(renderer2D->drawQuad(position2, size, color2)); - - renderer2D->endScene(); - - // Validate stats - RendererStats stats = renderer2D->getStats(); - EXPECT_EQ(stats.quadCount, 2); - EXPECT_EQ(stats.getTotalVertexCount(), 8); // 2 quads * 4 vertices - EXPECT_EQ(stats.getTotalIndexCount(), 12); // 2 quads * 6 indices - - // Validate vertex buffer content - GLuint vertexBufferId = renderer2D->getInternalStorage()->vertexBuffer->getId(); - glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId); - std::vector vertexData(8); // Expecting 8 vertices (2 quads * 4 vertices) - glGetBufferSubData(GL_ARRAY_BUFFER, 0, 8 * sizeof(QuadVertex), vertexData.data()); - - // Expected vertex positions for the two quads - glm::vec3 expectedPositions[] = { - // Quad 1 (position1) - {-0.5f, -0.5f, 0.0f}, {0.5f, -0.5f, 0.0f}, {0.5f, 0.5f, 0.0f}, {-0.5f, 0.5f, 0.0f}, - // Quad 2 (position2) - {1.5f, 1.5f, 0.0f}, {2.5f, 1.5f, 0.0f}, {2.5f, 2.5f, 0.0f}, {1.5f, 2.5f, 0.0f} - }; - // Expected colors for the two quads - glm::vec4 expectedColors[] = { - color1, color1, color1, color1, // Quad 1 - color2, color2, color2, color2 // Quad 2 - }; - // Validate vertex positions and colors - for (int i = 0; i < 8; ++i) - { - EXPECT_VEC3_NEAR(vertexData[i].position, expectedPositions[i], 0.01f); - EXPECT_VEC4_NEAR(vertexData[i].color, expectedColors[i], 0.01f); - } - - // Validate index buffer content - GLuint indexBufferId = renderer2D->getInternalStorage()->indexBuffer->getId(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId); - std::vector indexData(12); // Expecting 12 indices (2 quads * 6 indices) - glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, 12 * sizeof(unsigned int), indexData.data()); - - // Expected indices for the two quads - unsigned int expectedIndices[] = { - // Quad 1 - 0, 1, 2, 2, 3, 0, - // Quad 2 - 4, 5, 6, 6, 7, 4 - }; - - // Validate indices - for (int i = 0; i < 12; ++i) - { - EXPECT_EQ(indexData[i], expectedIndices[i]); - } - } - - TEST_F(Renderer2DTest, DrawMultipleTexturedQuads) - { - glm::vec2 position1 = {0.0f, 0.0f}; - glm::vec2 position2 = {2.0f, 2.0f}; - glm::vec2 size = {1.0f, 1.0f}; - auto texture1 = Texture2D::create(4, 4); // Texture for the first quad - auto texture2 = Texture2D::create(8, 8); // Texture for the second quad - - renderer2D->beginScene(glm::mat4(1.0f)); - - // Draw two textured quads - EXPECT_NO_THROW(renderer2D->drawQuad(position1, size, texture1)); - EXPECT_NO_THROW(renderer2D->drawQuad(position2, size, texture2)); - - renderer2D->endScene(); - - // Validate stats - RendererStats stats = renderer2D->getStats(); - EXPECT_EQ(stats.quadCount, 2); - EXPECT_EQ(stats.getTotalVertexCount(), 8); // 2 quads * 4 vertices - EXPECT_EQ(stats.getTotalIndexCount(), 12); // 2 quads * 6 indices - - // Validate vertex buffer content - GLuint vertexBufferId = renderer2D->getInternalStorage()->vertexBuffer->getId(); - glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId); - - // Read back vertex data - std::vector vertexData(8); // Expecting 8 vertices (2 quads * 4 vertices) - glGetBufferSubData(GL_ARRAY_BUFFER, 0, 8 * sizeof(QuadVertex), vertexData.data()); - - // Expected vertex positions for the two quads - glm::vec3 expectedPositions[] = { - // Quad 1 (position1) - {-0.5f, -0.5f, 0.0f}, {0.5f, -0.5f, 0.0f}, {0.5f, 0.5f, 0.0f}, {-0.5f, 0.5f, 0.0f}, - // Quad 2 (position2) - {1.5f, 1.5f, 0.0f}, {2.5f, 1.5f, 0.0f}, {2.5f, 2.5f, 0.0f}, {1.5f, 2.5f, 0.0f} - }; - - // Expected texture coordinates (default for full texture) - glm::vec2 expectedTexCoords[] = { - {0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}, // Quad 1 - {0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f} // Quad 2 - }; - - // Expected texture indices - float expectedTexIndices[] = { - 1.0f, 1.0f, 1.0f, 1.0f, // Quad 1 - 2.0f, 2.0f, 2.0f, 2.0f // Quad 2 - }; - - // Validate vertex positions, texture coordinates, and texture indices - for (int i = 0; i < 8; ++i) - { - EXPECT_VEC3_NEAR(vertexData[i].position, expectedPositions[i], 0.01f); - EXPECT_VEC2_NEAR(vertexData[i].texCoord, expectedTexCoords[i], 0.01f); - EXPECT_EQ(vertexData[i].texIndex, expectedTexIndices[i]); - } - - // Validate index buffer content - GLuint indexBufferId = renderer2D->getInternalStorage()->indexBuffer->getId(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId); - - // Read back index data - std::vector indexData(12); // Expecting 12 indices (2 quads * 6 indices) - glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, 12 * sizeof(unsigned int), indexData.data()); - - // Expected indices for the two quads - unsigned int expectedIndices[] = { - // Quad 1 - 0, 1, 2, 2, 3, 0, - // Quad 2 - 4, 5, 6, 6, 7, 4 - }; - - // Validate indices - for (int i = 0; i < 12; ++i) - { - EXPECT_EQ(indexData[i], expectedIndices[i]); - } - } - - TEST_F(Renderer2DTest, BeginSceneWithoutInit) { - // Manually delete the storage to simulate an uninitialized renderer - renderer2D->shutdown(); - - glm::mat4 viewProjection = glm::mat4(1.0f); - - // Expect RendererNotInitialized exception - EXPECT_THROW(renderer2D->beginScene(viewProjection), RendererNotInitialized); - // Re-init for TearDown function - renderer2D->init(); - } - - TEST_F(Renderer2DTest, EndSceneWithoutBeginScene) { - // Expect RendererSceneLifeCycleFailure exception - EXPECT_THROW(renderer2D->endScene(), RendererSceneLifeCycleFailure); - } - - TEST_F(Renderer2DTest, DrawQuadWithoutBeginScene) { - glm::vec2 position = {0.0f, 0.0f}; - glm::vec2 size = {1.0f, 1.0f}; - glm::vec4 color = {1.0f, 0.0f, 0.0f, 1.0f}; - - // Expect RendererSceneLifeCycleFailure exception - EXPECT_THROW(renderer2D->drawQuad(position, size, color), RendererSceneLifeCycleFailure); - } - - TEST_F(Renderer2DTest, ResetStatsWithoutInit) { - // Manually delete the storage to simulate an uninitialized renderer - renderer2D->shutdown(); - - // Expect RendererNotInitialized exception - EXPECT_THROW(renderer2D->resetStats(), RendererNotInitialized); - // Re-init for TearDown function - renderer2D->init(); - } - - TEST_F(Renderer2DTest, GetStatsWithoutInit) { - // Manually delete the storage to simulate an uninitialized renderer - renderer2D->shutdown(); - - // Expect RendererNotInitialized exception - EXPECT_THROW(static_cast(renderer2D->getStats()), RendererNotInitialized); - // Re-init for TearDown function - renderer2D->init(); - } - - -} From d3446dcb074ae8acb9b668d9ddffc86e642f5bf7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 22:51:53 +0900 Subject: [PATCH 168/450] fix(document-windows): fix warning --- editor/src/ImNexo/Widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/ImNexo/Widgets.cpp b/editor/src/ImNexo/Widgets.cpp index fe2402881..7e375d5a8 100644 --- a/editor/src/ImNexo/Widgets.cpp +++ b/editor/src/ImNexo/Widgets.cpp @@ -154,7 +154,7 @@ namespace ImNexo { button.onRightClick(); } if (!button.tooltip.empty() && ImGui::IsItemHovered()) - ImGui::SetTooltip(button.tooltip.c_str()); + ImGui::SetTooltip("%s", button.tooltip.c_str()); } } // Check for clicks outside to close menu From f73cf22fc0d0184e1fac7370d8d9eb86474d52d8 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 22:52:25 +0900 Subject: [PATCH 169/450] feat(document-windows): add utils function to attach render component to camera and lights --- editor/src/utils/EditorProps.cpp | 86 ++++++++++++++++++++++++++++++++ editor/src/utils/EditorProps.hpp | 27 ++++++++++ 2 files changed, 113 insertions(+) create mode 100644 editor/src/utils/EditorProps.cpp create mode 100644 editor/src/utils/EditorProps.hpp diff --git a/editor/src/utils/EditorProps.cpp b/editor/src/utils/EditorProps.cpp new file mode 100644 index 000000000..e2f367bcf --- /dev/null +++ b/editor/src/utils/EditorProps.cpp @@ -0,0 +1,86 @@ +//// EditorProps.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: 19/04/2025 +// Description: Source file for the editor props utils +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EditorProps.hpp" +#include "renderer/Texture.hpp" +#include "components/Render3D.hpp" +#include "components/Shapes3D.hpp" +#include "Path.hpp" +#include "Nexo.hpp" +namespace nexo::editor::utils { + + static void addCameraProps(ecs::Entity entity) + { + auto &app = getApp(); + nexo::components::Material billboardMat{}; + billboardMat.isOpaque = false; + static const std::shared_ptr cameraIconTexture = + nexo::renderer::Texture2D::create(nexo::Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png").string()); + billboardMat.albedoTexture = cameraIconTexture; + billboardMat.shader = "Albedo unshaded transparent"; + auto billboard = std::make_shared(); + auto renderable = std::make_shared(billboardMat, billboard); + nexo::components::RenderComponent renderComponent(renderable, nexo::components::RenderType::RENDER_3D); + app.m_coordinator->addComponent(entity, renderComponent); + } + + static void addPointLightProps(ecs::Entity entity) + { + auto &app = getApp(); + nexo::components::Material billboardMat{}; + billboardMat.isOpaque = false; + static const std::shared_ptr pointLightIconTexture = + renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/pointLightIcon.png").string()); + billboardMat.albedoTexture = pointLightIconTexture; + billboardMat.shader = "Albedo unshaded transparent"; + auto billboard = std::make_shared(); + auto renderable = std::make_shared(billboardMat, billboard); + nexo::components::RenderComponent renderComponent(renderable, nexo::components::RenderType::RENDER_3D); + app.m_coordinator->addComponent(entity, renderComponent); + } + + static void addSpotLightProps(ecs::Entity entity) + { + auto &app = getApp(); + nexo::components::Material billboardMat{}; + billboardMat.isOpaque = false; + static const std::shared_ptr spotLightIconTexture = + renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/spotLightIcon.png").string()); + billboardMat.albedoTexture = spotLightIconTexture; + billboardMat.shader = "Albedo unshaded transparent"; + auto billboard = std::make_shared(); + auto renderable = std::make_shared(billboardMat, billboard); + nexo::components::RenderComponent renderComponent(renderable, nexo::components::RenderType::RENDER_3D); + app.m_coordinator->addComponent(entity, renderComponent); + } + + void addPropsTo(ecs::Entity entity, PropsType type) + { + switch (type) + { + case PropsType::CAMERA: + addCameraProps(entity); + break; + case PropsType::POINT_LIGHT: + addPointLightProps(entity); + break; + case PropsType::SPOT_LIGHT: + addSpotLightProps(entity); + break; + default: + break; + } + } + +} diff --git a/editor/src/utils/EditorProps.hpp b/editor/src/utils/EditorProps.hpp new file mode 100644 index 000000000..1e8edc334 --- /dev/null +++ b/editor/src/utils/EditorProps.hpp @@ -0,0 +1,27 @@ +//// EditorProps.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: Mehdy MORVAN +// Date: 19/04/2025 +// Description: Header file for the utils function to create editor props +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "ecs/Coordinator.hpp" + +namespace nexo::editor::utils { + + enum class PropsType { + CAMERA, + POINT_LIGHT, + SPOT_LIGHT + }; + + void addPropsTo(ecs::Entity entity, PropsType type); +} From c666611dcbd85218abec745795d9e1f0f18b1047 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 22:53:05 +0900 Subject: [PATCH 170/450] feat(document-windows): now add render component to lights when creating them --- editor/src/DocumentWindows/EditorScene.cpp | 3 +++ editor/src/DocumentWindows/SceneTreeWindow.cpp | 12 +++++++++++- editor/src/ImNexo/Panels.cpp | 12 ++---------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index aa349d5a5..15d3aa99e 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -32,6 +32,7 @@ #include "math/Matrix.hpp" #include "context/Selector.hpp" #include "utils/String.hpp" +#include "utils/EditorProps.hpp" #include "ImNexo/Widgets.hpp" #include @@ -82,10 +83,12 @@ namespace nexo::editor { const ecs::Entity ambientLight = LightFactory::createAmbientLight({0.5f, 0.5f, 0.5f}); scene.addEntity(ambientLight); const ecs::Entity pointLight = LightFactory::createPointLight({1.2f, 5.0f, 0.1f}); + utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); scene.addEntity(pointLight); const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.2f, -1.0f, -0.3f}); scene.addEntity(directionalLight); const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, -2.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}); + utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); scene.addEntity(spotLight); const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, -5.0f, -5.0f}, {20.0f, 1.0f, 20.0f}, {0.0f, 0.0f, 0.0f}, {1.0f, 0.5f, 0.31f, 1.0f}); diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index 5f4b4c727..5e0e2ab71 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -16,8 +16,10 @@ #include "ADocumentWindow.hpp" #include "Coordinator.hpp" #include "utils/Config.hpp" +#include "utils/EditorProps.hpp" #include "DocumentWindows/InspectorWindow.hpp" #include "Primitive.hpp" +#include "Path.hpp" #include #include @@ -153,10 +155,12 @@ namespace nexo::editor { } if (ImGui::MenuItem("Point")) { const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); + utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); sceneManager.getScene(sceneId).addEntity(pointLight); } if (ImGui::MenuItem("Spot")) { const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); + utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); sceneManager.getScene(sceneId).addEntity(spotLight); } ImGui::EndMenu(); @@ -585,7 +589,13 @@ namespace nexo::editor { return this->newCameraNode(sceneId, uiId, entity); }); - generateNodes>( + generateNodes< + components::RenderComponent, + components::TransformComponent, + components::SceneTag, + ecs::Exclude, + ecs::Exclude, + ecs::Exclude>( sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return this->newEntityNode(sceneId, uiId, entity); diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index e8c628bc5..7e40caa23 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -22,6 +22,7 @@ #include "IconsFontAwesome.h" #include "components/Uuid.hpp" #include "context/Selector.hpp" +#include "utils/EditorProps.hpp" namespace ImNexo { bool MaterialInspector(nexo::components::Material *material) @@ -104,16 +105,7 @@ namespace ImNexo { const auto renderTarget = nexo::renderer::Framebuffer::create(framebufferSpecs); 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(static_cast(defaultCamera)); - - nexo::components::Material billboardMat{}; - billboardMat.isOpaque = false; - std::shared_ptr cameraIconTexture = nexo::renderer::Texture2D::create(nexo::Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png").string()); - billboardMat.albedoTexture = cameraIconTexture; - billboardMat.shader = "Albedo unshaded transparent"; - auto billboard = std::make_shared(); - auto renderable = std::make_shared(billboardMat, billboard); - nexo::components::RenderComponent renderComponent(renderable, nexo::components::RenderType::RENDER_3D); - app.m_coordinator->addComponent(defaultCamera, renderComponent); + nexo::editor::utils::addPropsTo(defaultCamera, nexo::editor::utils::PropsType::CAMERA); return defaultCamera; } From c235318fb6033afb702a4058d61be5a0b6930f63 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 22:53:53 +0900 Subject: [PATCH 171/450] refactor(document-windows): since we now use transform component for point and spot lights, we have to check in the transform not to render it + to use the transform to render in the approriate component the pos --- .../DocumentWindows/EntityProperties/PointLightProperty.cpp | 4 +++- .../src/DocumentWindows/EntityProperties/RenderProperty.cpp | 5 ++++- .../DocumentWindows/EntityProperties/SpotLightProperty.cpp | 3 ++- .../DocumentWindows/EntityProperties/TransformProperty.cpp | 4 ++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp index 3d6c05787..e1c5a1683 100644 --- a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp @@ -14,6 +14,7 @@ #include "PointLightProperty.hpp" #include "components/Light.hpp" +#include "components/Transform.hpp" #include "math/Light.hpp" #include "ImNexo/Widgets.hpp" @@ -22,6 +23,7 @@ namespace nexo::editor { void PointLightProperty::show(const ecs::Entity entity) { auto& pointComponent = nexo::Application::getEntityComponent(entity); + auto &transform = Application::getEntityComponent(entity); if (ImNexo::Header("##PointNode", "Point light")) { @@ -43,7 +45,7 @@ namespace nexo::editor { ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImNexo::RowDragFloat3("Position", "X", "Y", "Z", &pointComponent.pos.x); + ImNexo::RowDragFloat3("Position", "X", "Y", "Z", &transform.pos.x); ImGui::EndTable(); } diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index a119a372a..bf76c19c1 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -18,6 +18,7 @@ #include "AEntityProperty.hpp" #include "Application.hpp" #include "Framebuffer.hpp" +#include "components/Light.hpp" #include "utils/ScenePreview.hpp" #include "components/Camera.hpp" #include "components/Render.hpp" @@ -126,7 +127,9 @@ namespace nexo::editor { void RenderProperty::show(ecs::Entity entity) { - if (Application::m_coordinator->entityHasComponent(entity)) + if (Application::m_coordinator->entityHasComponent(entity) || + Application::m_coordinator->entityHasComponent(entity) || + Application::m_coordinator->entityHasComponent(entity)) return; auto& renderComponent = Application::getEntityComponent(entity); diff --git a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp index 8ac38c288..0c9a8a4d5 100644 --- a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp @@ -22,6 +22,7 @@ namespace nexo::editor { void SpotLightProperty::show(ecs::Entity entity) { auto& spotComponent = Application::getEntityComponent(entity); + auto &transformComponent = Application::getEntityComponent(entity); if (ImNexo::Header("##SpotNode", "Spot light")) { @@ -44,7 +45,7 @@ namespace nexo::editor { ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImNexo::RowDragFloat3("Direction", "X", "Y", "Z", &spotComponent.direction.x, -FLT_MAX, FLT_MAX, 0.1f); - ImNexo::RowDragFloat3("Position", "X", "Y", "Z", &spotComponent.pos.x, -FLT_MAX, FLT_MAX, 0.1f); + ImNexo::RowDragFloat3("Position", "X", "Y", "Z", &transformComponent.pos.x, -FLT_MAX, FLT_MAX, 0.1f); ImGui::EndTable(); diff --git a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp index f8d642500..b67a5f900 100644 --- a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp @@ -17,11 +17,15 @@ #include "TransformProperty.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" +#include "components/Light.hpp" namespace nexo::editor { void TransformProperty::show(ecs::Entity entity) { + if (Application::m_coordinator->entityHasComponent(entity) || + Application::m_coordinator->entityHasComponent(entity)) + return; auto& transformComponent = Application::getEntityComponent(entity); static glm::vec3 lastDisplayedEuler(0.0f); From 3e51fb01bc44cb0e3661383ebf9904c744a8b55a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 22:54:38 +0900 Subject: [PATCH 172/450] chore(document-windows): add source file --- editor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 5741a5354..f425cca50 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -25,6 +25,7 @@ set(SRCS editor/src/utils/Config.cpp editor/src/utils/String.cpp editor/src/utils/FileSystem.cpp + editor/src/utils/EditorProps.cpp editor/src/Editor.cpp editor/src/WindowRegistry.cpp editor/src/DockingRegistry.cpp From 3f3b33393b6b0f6b9cdbca20714e6ad27afca285 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 22:54:58 +0900 Subject: [PATCH 173/450] chore(document-windows): remove useless print --- engine/src/renderer/primitives/Billboard.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/src/renderer/primitives/Billboard.cpp b/engine/src/renderer/primitives/Billboard.cpp index 82e2a861f..75c372730 100644 --- a/engine/src/renderer/primitives/Billboard.cpp +++ b/engine/src/renderer/primitives/Billboard.cpp @@ -193,7 +193,6 @@ namespace nexo::renderer { renderer::Material mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; - std::cout << "Material Albedo Texture Index: " << mat.albedoTexIndex << std::endl; mat.specularColor = material.specularColor; mat.specularTexIndex = material.metallicMap ? getTextureIndex(material.metallicMap) : 0; setMaterialUniforms(mat); From 393f213d40628300229c2f450f4986febee6da22 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 22:55:25 +0900 Subject: [PATCH 174/450] refactor(document-windows): lights use transform component for their positions --- engine/src/LightFactory.cpp | 9 +++- engine/src/components/Light.hpp | 46 +++++-------------- engine/src/systems/RenderSystem.cpp | 19 ++++++-- engine/src/systems/RenderSystem.hpp | 2 + .../src/systems/lights/PointLightsSystem.cpp | 4 +- .../src/systems/lights/SpotLightsSystem.cpp | 4 +- 6 files changed, 39 insertions(+), 45 deletions(-) diff --git a/engine/src/LightFactory.cpp b/engine/src/LightFactory.cpp index a32f8c736..a7b7e55bf 100644 --- a/engine/src/LightFactory.cpp +++ b/engine/src/LightFactory.cpp @@ -16,6 +16,7 @@ #include "Application.hpp" #include "components/Light.hpp" +#include "components/Transform.hpp" #include "components/Uuid.hpp" namespace nexo { @@ -42,7 +43,9 @@ namespace nexo { ecs::Entity LightFactory::createPointLight(glm::vec3 position, glm::vec3 color, float linear, float quadratic) { ecs::Entity newPointLight = Application::m_coordinator->createEntity(); - components::PointLightComponent newPointLightComponent(position, color, linear, quadratic); + components::TransformComponent transformComponent(position); + Application::m_coordinator->addComponent(newPointLight, transformComponent); + components::PointLightComponent newPointLightComponent(color, linear, quadratic); Application::m_coordinator->addComponent(newPointLight, newPointLightComponent); components::UuidComponent uuid; Application::m_coordinator->addComponent(newPointLight, uuid); @@ -55,7 +58,9 @@ namespace nexo { float outerCutOff) { ecs::Entity newSpotLight = Application::m_coordinator->createEntity(); - components::SpotLightComponent newSpotLightComponent(position, direction, color, cutOff, outerCutOff, linear, quadratic); + 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); diff --git a/engine/src/components/Light.hpp b/engine/src/components/Light.hpp index 0ebb7fee3..c5561bd07 100644 --- a/engine/src/components/Light.hpp +++ b/engine/src/components/Light.hpp @@ -17,6 +17,8 @@ #include #include +#include "ecs/Definitions.hpp" + constexpr unsigned int MAX_POINT_LIGHTS = 8; constexpr unsigned int MAX_SPOT_LIGHTS = 8; @@ -37,53 +39,29 @@ namespace nexo::components { }; struct PointLightComponent { - PointLightComponent() = default; - explicit PointLightComponent(const glm::vec3 lightPos, - const glm::vec3 &lightColor = {1.0f, 1.0f, 1.0f}, - const float linear = 0.09f, - const float quadratic = 0.032f) : - pos(lightPos), color(lightColor), - linear(linear), quadratic(quadratic) {}; - - glm::vec3 pos{}; glm::vec3 color{}; - float maxDistance = 50.0f; - float constant = 1.0f; float linear{}; float quadratic{}; + float maxDistance = 50.0f; + float constant = 1.0f; }; struct SpotLightComponent { - SpotLightComponent() = default; - explicit SpotLightComponent(glm::vec3 lightPos, - glm::vec3 lightDir, - glm::vec3 lightColor, - float cutOff, - float outerCutoff, - float linear = 0.0014f, - float quadractic = 0.0007f) : - pos(lightPos), color(lightColor), - direction(lightDir), cutOff(cutOff), - outerCutoff(outerCutoff), linear(linear), - quadratic(quadractic) {} - - glm::vec3 pos{}; - glm::vec3 color{}; - glm::vec3 direction{}; - float maxDistance = 325.0f; - float cutOff{}; - float outerCutoff{}; - - float constant = 1.0f; + glm::vec3 direction{}; + glm::vec3 color{}; + float cutOff{}; + float outerCutoff{}; float linear{}; float quadratic{}; + float maxDistance = 325.0f; + float constant = 1.0f; }; struct LightContext { glm::vec3 ambientLight; - std::array pointLights; + std::array pointLights; unsigned int pointLightCount = 0; - std::array spotLights; + std::array spotLights; unsigned int spotLightCount = 0; DirectionalLightComponent dirLight; }; diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 843e723f6..93dc02256 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -15,6 +15,7 @@ #include "RenderSystem.hpp" #include "RendererContext.hpp" #include "components/Editor.hpp" +#include "components/Light.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" #include "components/Camera.hpp" @@ -45,7 +46,7 @@ namespace nexo::system { * - pointLights (and pointLightCount) * - spotLights (and spotLightCount) */ - static void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext) + void RenderSystem::setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext) { static std::shared_ptr lastShader = nullptr; if (lastShader == shader) @@ -61,20 +62,28 @@ namespace nexo::system { shader->setUniformFloat3("uDirLight.direction", directionalLight.direction); shader->setUniformFloat4("uDirLight.color", glm::vec4(directionalLight.color, 1.0f)); + // Well we are doing something very stupid here, but any way this render system is fucked + // In the future, we should have a material/light pre-pass that sets all uniforms of the the material + // But for now the material is embedded into the renderable which is also scuffed + const auto &pointLightComponentArray = coord->getComponentArray(); + const auto &transformComponentArray = coord->getComponentArray(); for (unsigned int i = 0; i < lightContext.pointLightCount; ++i) { - auto pointLight = lightContext.pointLights[i]; - shader->setUniformFloat3(std::format("uPointLights[{}].position", i), pointLight.pos); + const auto &pointLight = pointLightComponentArray->get(lightContext.pointLights[i]); + const auto &transform = transformComponentArray->get(lightContext.pointLights[i]); + shader->setUniformFloat3(std::format("uPointLights[{}].position", i), transform.pos); shader->setUniformFloat4(std::format("uPointLights[{}].color", i), glm::vec4(pointLight.color, 1.0f)); shader->setUniformFloat(std::format("uPointLights[{}].constant", i), pointLight.constant); shader->setUniformFloat(std::format("uPointLights[{}].linear", i), pointLight.linear); shader->setUniformFloat(std::format("uPointLights[{}].quadratic", i), pointLight.quadratic); } + const auto &spotLightComponentArray = coord->getComponentArray(); for (unsigned int i = 0; i < lightContext.spotLightCount; ++i) { - auto spotLight = lightContext.spotLights[i]; - shader->setUniformFloat3(std::format("uSpotLights[{}].position", i), spotLight.pos); + const auto &spotLight = spotLightComponentArray->get(lightContext.spotLights[i]); + const auto &transform = transformComponentArray->get(lightContext.spotLights[i]); + shader->setUniformFloat3(std::format("uSpotLights[{}].position", i), transform.pos); shader->setUniformFloat4(std::format("uSpotLights[{}].color", i), glm::vec4(spotLight.color, 1.0f)); shader->setUniformFloat(std::format("uSpotLights[{}].constant", i), spotLight.constant); shader->setUniformFloat(std::format("uSpotLights[{}].linear", i), spotLight.linear); diff --git a/engine/src/systems/RenderSystem.hpp b/engine/src/systems/RenderSystem.hpp index d5f3e403c..a1fb81aa3 100644 --- a/engine/src/systems/RenderSystem.hpp +++ b/engine/src/systems/RenderSystem.hpp @@ -48,5 +48,7 @@ namespace nexo::system { ecs::WriteSingleton> { public: void update(); + private: + void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext); }; } diff --git a/engine/src/systems/lights/PointLightsSystem.cpp b/engine/src/systems/lights/PointLightsSystem.cpp index 89bf1c928..ad1c611a3 100644 --- a/engine/src/systems/lights/PointLightsSystem.cpp +++ b/engine/src/systems/lights/PointLightsSystem.cpp @@ -47,11 +47,11 @@ namespace nexo::system { if (partition->count > MAX_POINT_LIGHTS) THROW_EXCEPTION(core::TooManyPointLightsException, sceneRendered, partition->count); - const auto pointLightSpan = get(); + 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] = pointLightSpan[i]; + renderContext.sceneLights.pointLights[renderContext.sceneLights.pointLightCount] = entitySpan[i]; renderContext.sceneLights.pointLightCount++; } } diff --git a/engine/src/systems/lights/SpotLightsSystem.cpp b/engine/src/systems/lights/SpotLightsSystem.cpp index 866bd49ac..6ebf5b50c 100644 --- a/engine/src/systems/lights/SpotLightsSystem.cpp +++ b/engine/src/systems/lights/SpotLightsSystem.cpp @@ -46,11 +46,11 @@ namespace nexo::system { if (partition->count > MAX_SPOT_LIGHTS) THROW_EXCEPTION(core::TooManySpotLightsException, sceneRendered, partition->count); - const auto spotLightSpan = get(); + 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] = spotLightSpan[i]; + renderContext.sceneLights.spotLights[renderContext.sceneLights.spotLightCount] = entitySpan[i]; renderContext.sceneLights.spotLightCount++; } } From f604ad5941d0c9c7460e8750958cfc7bc5c122ef Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sat, 19 Apr 2025 22:55:47 +0900 Subject: [PATCH 175/450] asset(document-windows): add point and spot light icon --- resources/textures/pointLightIcon.png | Bin 0 -> 1283823 bytes resources/textures/spotLightIcon.png | Bin 0 -> 1330186 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/textures/pointLightIcon.png create mode 100644 resources/textures/spotLightIcon.png diff --git a/resources/textures/pointLightIcon.png b/resources/textures/pointLightIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..4f89b18ebc8c7025305d58bb052c47aeed5680ff GIT binary patch literal 1283823 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

(L{SKq|dTi2!QP)c8$d=*-rRh92i| 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%0A}@pbnbJw$YPDt#JDoi|=;WaXI-utg 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

fpBS%GyS?|^^(7|k))G!VF1^t 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�*L~f;_Z$9K|I5dl$K%~uwQ`&$WjRoEN5f@m)?8z8MibkY zoLr*Asmhf$5mz)6G|sv?aBzd3x_5E<2D|i1`MW|g$Idy7;`0b4TJ6-l%iL~k$Xb9` z@=bxpG=s}$4$!e0-Hy2XR*8@>jY-r70m0zqO;6Ld9Rq1pUWF?&F~f%p{moVWj3MVy zyUHcja6A}gQa*~KJ-_)1GLd(Doi|^#mV+7n7X%L%(X8r9M1Og|*ClDW*PkF@i28d`0tF{n81>N{5f~4m zUB#WCv(ysuA?%#br1?ebcl7avB}A^WD7o_{7GzdhxD*L7G8t3fDT9|UqfkIt3Vkk= zP4au(2|ERRWWcvsF0aopu|D?G&b$iJnqFeBVq`7yiAz>z9Q?b7XY2ml^!4xMK?>T` zvYvVO)wjEnQs~K6TCWt)aYx!iYaE}9CO6hhZFa`+N@bA7UHE&ilTv~)Ft%R&Rc_G`Z8@t42*Kl_t^=`a1iKIel^d?3_hv1aeh)+(#n zZWa6AMbXQ#_HvyGc2InGrKAiIml<_TeIf$FG(diwMwW{=NqWzV6p zEjGQDxW>P<1hoQ^1-So^PayD#xc;9-(+(!j26#JaX|M%93Mo5Q#_`VUE^*o6M955cC-p zt>NIOHLNk(+wU*fYFkaxU7(s@3s~n!Wdch{Py-6|@%t78xJQ!}c#OG$1YP95V+|yG zk4Igb;pV73wqml6eXNvJZDDNQ@4I+GP;RzxLOS0jP_mVzuIEz*j|}e5G1MG_=y*n9 zP{ssu1Tvk5Kz6z^Q2oxtWNKEC{%(QP9glK6dz<{Cv4J&ARZFU;i+qH0;3|~T=k{xp za0IkjQ^H>jxKgHdkH+e_Zop^2M;tmK`MhmU6}-)a-)+!`7o9+~x{orYYie=}jkU_{ z{+^PQKq9Y09<%9F4e}^VUkHIz={+Qx+!C{{Qfcd1tE2{JX9(l@99#|CnlY;xnk+d+Ju3& zt*;c5`Elt*>(UJbvpV!^A**f-=xrm_Px}_*!eDnku{U6+o_JkS@lpNUk{6~ODaeaYI)8!Y}Wt_PIv~^dkQDK9e|gT zwO&ei&&!o=&)gD4 zCDT7!EoOK=kW*?fM;ulLt`BosB6 z{y3HNixeW%3bYkzmr>Cd`ADYtnx)<%^pqIDtPe-;Y61){l4+8jg>_$_B+CbLuXf2gCN6W*wcrQw4hs&1J z^<4T-B2uOPRnG+7tB^*++x|1)a0co)C+;=au2qnoj|d8+SblC5YReB*l8~HZ=-%2= zfXLyPVU-X{=+1Cr7^7oby5C%iNNqhNdqK0k%2+LX4b_VIieUe6z@9Jn8P@qYJ1 zmPKIPDQxY9clO@zOOtr{8AQb0C!x@fqmwB#Ma3&Lh9}9)>TSn`Nk)Jb4_ea(E%yQG z>k^`sY6zg~88AjBGElGm;wsf%E8t(E9}fU)f(bq)55)%UO?h%(iqrJDR~Nwu_~`Mz z=r5jwx21T0KY_-D1NVn+H}h`GomHcOsfBgC7ewxGFnE-LZp-VO^-g)3Y~Gj}m2m*d zZckI(93o*A-@wM1(*>Nv*pmUGpm1F493En|Y62ZVuK06+1DRX)51429P~H&$Db!W! zo@jqhX0#=Q+4s??K4V2&o|hQfyM33;KLv8Z{S45BbqcYj?V!D4s=V6~iX}=5#;fM6 zcUD^Px;Z%Pz_qu9R!J40-Il^0SVMiTTK=eA0Ru2i&xrs+Yrk$|WFV&P`BoIZAfDkJ z^Ow87>hn#nz^xCEvxv-7wUplK9F&m8aB75jvdy6f*{zvJ_FXklj!TA7tfS!V5=(s1 zg`CUlQyo?WeU4>(B7Ou4Yh0qx#X7#Bq!H*`36O+Y1$CEw%fbdRHRx>50bE}&rYOs3MMF)f`v z?`I6$Q9_U;XE|R(ChC&+%Cq4xCsoiGtqYtB|0};jjKCMzN7aAix|-{gfWVkmzIAHw z%58Nn;{e$*gZqI6p&X6q-vLVa^Ypv~JJwB5-7lq1tK6w(m;q=dU-8cwomA!!WQH8S z0?4K=rT;sRu9(e6&nW>mDEx{D5C57mTzj78mmzm$earlLt=Y@>fKMI2X)=shhHae+ z;qX(CQKPhDN$Pr+UFE1LF8Z~`2!2IQlYB_uw_hrR^(*&3C zfhcEK!*hYNdF=ji6du{^k^d%Lx~s!yFEU(+GqoI!!Tg5u(@)?CjlXAu_~@{%JOMluORp{Ge}Pq_<2=K8oOO}kIlX=8yYnR z7Z>el-a;plv^CG>1Dc?Hp0q}zR5j{JDlkyw5?*bx*quaU5RxdgSkSNo(K2-}pA@d@ zQ>OOd+HfKtQT)T6ObwUd=P*|f-P~ubZeeY+ozx9n&R#0NrjF}NL-Mq=+vo`X(soH_ z!dGkW49Fim-`;_yp;3+5$rd3Y)(8zXky{o}v!h}YX)b;&7^E1ZpiT}_2{0fSdd2a$ z027%Q3n_hMwp0%=U|w!+A8{aCo8Gjc=9Sok?@qZ-neh2a1txqnVOhB*v&2dbu?FVXDp8mw90jY;ZOCPu;P*E7St((oen7vA^XoRi$ z+|p!i6m(}}j!>MIzV4;=XDWc?Iq76%GQ-Ndyhu48c>|~&VPFTK$xB}?W~o{0@OebYh|BE144`3CY*~_yF>%61xkTgX>K&vjgwOKb$xEF$Y9DS*BZlM zZorg}DFz^Ab^&LBTbe!JEmw>Inogl)R==pU9ne`x8ff6OFCM z{hjaozCZjGU;bs!)8|dE+O?_|Ypig3)oy%@s5$`3uFRhYp|{m&0pUnp)y0CRYB;hObB}8GF-rW*(b9fb0qvra=QZt` z_cAn(NB#1BSF8D09&UIe!B^bwY#N)G4nSplVBx(P>piMDflcTEdgm<-!OPYH4TSzl z@8M!4n0|6qo@!8JPhbX8hRH_505VU=2;OPD%*`a84vmCj8D90u+ifuu6zVC_&}!;w zN=@2o=iUN|@!x<^be>)94RxIt!wpb4pwm`e8CABy0NyBnJ^g;uMXH;33hy(V$kswI zm0s5r0I>NWX1onX8XcvYPRDw^qrqv0)wJY0w&v*U57w zefgm)Q@p-){Rg0*GKsKeuCi4GK)({WtBV3>6)M2hunPxL27g{-X9#HVJQCq|KpsnE zQ~7(FMKIKP`K*s!BYo@f$N{Nq;ulb25v|5LshWYH>y~UBC~q7yW`?^y7cazxi+e z>yI~Ym>u1PXE)M($V=f*k6+5(AL`i+5gt$nMc`b#=Lk8-~vv0UzhwP zwVYT>-PQeHds`wI#{0_464$(+C1b_0mc0+8+JFqFB*fL+uL?{w)N}Tq_EZU;_bP(} zMr`_pvQP)t8N_#zmRv^X3OABKpOwB=Kh>D;u_a>+z)s2QScO$P z3eso@#gduzW!F+l>A3g=hkcP2kZr&htyA?;W(o&!9WaQ&7#m7DG$3c&|1#Z0(_`U~ zZ*_LIIzxUs-7#s_C|I8ZZ=YWQ=&>)iQQIQQ^(-`=JYUd;5OFplhHDa;-ZJ1g$j+6M znm$d;b7;$l=q1|IG19Z<{BVH}2As4OZD-=*0RY&=ZL;xwH{u#4Ts)B*hpik7`6s5+ zbaW|-gKw#r6&XHP3@Fg-xJs8V^{H&iRdD>RHA5jy!81N9D$5jCuangmuV|YP`xLCK zv)97ga;dUf`lgxXVsRc=nsKRhqJVtJX`yBXH9_bIq_O5g#g)-A1tqyKc{ruAjB?U< zMl!AG^bWZHQ0ub>6~ZnDs}?k|f!xRRj4LS!!Ah;g9;dK`>`Ij!B3ZV>T*HF(cWV!o zOHSNSQ zBhNsEdwZG0_Pqtf`frZS>x%|zQz!?BA&;{0-zcn<|1V>sqWyxh(zXxkr71&GVAX~z zH>#HQ@jp}O&3-J9b(+N=$<2r3ISk9 z1}W7C`I{$2a19<00PldcAN>UIH~!jR`|w-;^uP0+|KdOYtKYtR9yYLh+PA6=D&~gH ztk5jaXaaLsTvDsCww8hPur4}nKi&nGucx(L?s^!`;$b#&#Nx-tqiZ0Axd`rI-Bf5} z3kI<14zG-yqI1Z<<@%~e#x@uYTA{f_EotgL>1;kp1$uUkQhvr^M7PyP-hcgm=UAY` zV_)C9pR3i(f^s*a&JE2b_Mjz*(4=>#`l3J4#=d8RWM3N0e1JI;Q13qqcjRB7Hn?6} z3d>_R+n8I8WJ3fpE6>uYU|MT@2Wxr`+MY)1O*Qme?Lpn|BpW?tBRNCQ1h>2+p8n0 zWvR^!?d?$WScyo2;X<@Yd3!?x?Xz{HAuFo#o+q3W>d?p-|=%l_{4|kcfI#o>8)1)ytlu#1hrQ3BedTY zM62xo$yZp5_SW2103PxWxo<+B_thLw2#te|Bq7R~K*@nC^;S(lg+)BwEKoFv=kix$ zc-F#NPX<0qOCi6-ryWpM=+n@G9PBzt5MA9i@D3YLLT9!8X7^G!FWf6l&gB#6P?p~U zCEo^W(dSKWTMFbX`(g{-Px?#?DEYpMf3@v&zyywG!-Zd6-tgFPk}Toas`2xbILf$3 z(k>%oAFrY~ZfCBIW~LV{nN{s-x@Xjw9Isnhmu9Li_sMx;@(@^yYfez50+_JGtFt;bXd5!qSP2&D6h7+pO_@-#Zv@ZCK&hH ztnk*cH1q&Vj(^8$9wyp-+J$22&^|NON7^hImP5po*NJ}$14IDY4q=1Kv7S?G&QlbixD=;R$Iup4-;&1|1EyZjOAu)v z&U-xvb&M-SXOQK8i941M;t5H8a)DJ5Z&URLo)e@)icHV9;`S*BSm5~EwqB?tl$Cch zBU@8ipRFwgr|j!BjL25=oaE&m9J6{d{MHdmigkFCeF&b~0-*)vHYP{Il!!;+O)dP6 zZ~9%|{6G3tzxK2C;Fj}+Z z`DFPBgHWhrOv^4%iIJga%5v6F*8`a_AU~aeRy{nd;KE6M$Qj_o0xM9&-WSJO?L(6W zh?y1llb4W#>mzh}US7$Egy6HTZrC6Zyk|IH0|;)38p`Qo0YDg@f;hEuo1Oi8897c} z8BX|XpJ-LPquWu5}As>N@`NZQzgx#Sw{#agebh{1F z9FKC1;8JfS#%k}zR7&EO_IjT;yxA2mH71n;5|hDOq)aMjvMPEgV{3D29iUg&sj5}0 za)68;(p|t0{@};;tG?>1@$LW4pZR0o{TILI@Bi>KKJnb^!IKTwW4ZE`{*5zllF;(( zeF~Hox3>~U%F{PNewIAju5)>6TPd02r_9^$IL8TxZK~-Ud$=-R{(ZqE$X=O}|FOG} z_RB|Aod>-3903qJ07GDAI~J|q)7;>^%t()Do}gS%a9Aa0=+*bG3B6Xtcn*qoMKT3#B-SbMOx}-ejiEkup6xALR1U2|OHuCWU?36#zaq>oVmSruGY5$FYh5 zVfKWy%HvOyNnGq^e*wxs8W}1$3bZ2^A?e0TH~!4D6cn98V>1J}j!IVqmx}?cG#oS@ zMP=QL;nL)|=&z;!fs^A#X)$K2^-1~!=n)QZTY41wrOE1keJczZp-*cd()|bi#1rdVT`Yf}0Dh9ZVGac{28r z3nFY_f_1OQwqj&J%Ka%amgXf?Quo|qA+MvnJ_-#lAED7WNAZoGzkoEL6ht$$>}&Dt zJd}fK(aJ`qtk~?9+(zg^RHGG1h|)<7q~~;1J%#`h{>jJ-@nZ_B1BB67i2IHL>q}-V z#Q7LfEP#s|r7S^OAN_5_QO60oQBmy$?=3qT$I<@$D739>Ys&px$#QyO?Zsw8rjunz z5mOgbuBfuS)#!c%SuN=XmoU{jI<=vI+T_2ru(p~P$(iZ3@OFQMul?Gu{h$7vpZT-? z2cPx1Kk}@q^}*YX@)?F;rvdBbEMNhp{S4zK;%2hIJ_v{A`yNK%HKR8A)tjEbQ{D`- zIT8q&Rl|;2S7SPZ#uTcQG(2(f$4Xo-Z-S`gA7AEBwx%90eIr+&6;544zJ3(h)FE$L zjj)DqVlEGah-^g0V=&S)G(8zZerm|2F68#Lbf52MLt}K`&nS1#n(L!P&7njuB&r!U zWURBcj{WTiS=&w@O@7%Je*rLn;d#rjS{p&NL7IJ{*v>n0QnQuTJr|%;qoZ(*cN(Ar zros`j+Imjrko?Y+j~&ceC#eCtPHory<$!d!Tr8~*!+v0Sn%^l7V%})hCGLQ@hL+3P z_TrS#pt^6%o$p%N_oNa^@kQD7rr@;dU8SL))>WnPjL9vbr07lqQJenq(#l)9`K;%W z!zX8^xtvj$H7?IzD$2--TWR};Qd^)_zvViAy?Qdi$h=zZ>0Rq()o5N`bA_Un;N&%C zkqSiX=Ys$Ns=PeVh=n>t*$IPP-+(kifDWoxTk5%ols(_1-ynacqKviD9X=fWd9NEs zwB*vRm>-~5RaN2f_JcS0@Dm^4fA<^yH~6}*|GNL^Z~yJTyRi4EV%ynOOBsBO?|VCF z-eTxv0iecmV)f-MoE$((x3N@Je(%+05RR?jw-%~=LRGUqNwW(4<{(iV!tUwvDFX-q zKH|+x!b;T06PT0{`3kIZP(WSUt3K^(8IB)~I~B5PgWLEPh8zY#Do`Fv(JePUdaPPC zSDFvlYnm#iG&3|eB(hl7dWG~gOm6}P*QKM8YipVy-RNivXI`(g+2OYr* z2b;!a&omB^o_B)Ad4$!IU}B7=ajL&rv~O@&HD_)h=VghnCQ%}kHCD)$v97)Z%5qyg zt~i#oBfqR-e?9bKk4>p8ZyuVWAgxh`o89|3;4_Z2rNYfiCpC_H-JM3iy9{kRsm1uH zyV5+R7L-X{L%xwwbp7xXr&&$)4O8T z#kN+CR}Ug=pDSBLQ)vXE|AljCO)^m_m*UZeWpL#(Iz9y3N}qej7}&du{YsTc37`?} z04dH%Il=tr9N^eSAZu?$D5K{PCt>Fvqx{rd)I_ty&^b}a#`duBA)^tko#bSd#8OI- z2FL3rpzrByh76MoQa}|3hAF?}TY#;~hUyKwDOxvPU(N4OxBB@fC)bl$yEwP- zdM!D#Udzm_o$PeK0tQ@fvqiPah;i|WSIUnVwn{6as>zni{Ez5fue%?R;+Kp2dWISH z4L!RIuW{2`9%i%J{u2JzrT8*B2YZGx?)TVM$F~am>$)B&_kI7%l6mmyYtmkY;`d1$ z^?YsqvDAfB2|bW2$^!^F`{k86nMovJeTCd8btD=nY6=8AhK#KF@W16=DJ;xM=^X|K}iE@{Rw#q@<3cm!{n#LODPSVL6#@Y#761xgBjDYM8; zW?TvRHm6u#N4VdRqqSDhVy_lIf0n2|2^@nKT%m=nK)4UEHvRF?6;v^7S7`?#V)9O~_?F}v1YySSR-w5rX> z1Wg+8ja@!ui^gd&cyx6zM`&DL%)ReXQ`xoNM@Uk>Xi1A>dQAavgIA=i>Wm+34UugD zT5j)=4=>qJX3@A7>xTWCJqg&boQWp)n`#3Jt+gvBJeMp3aO&U@iI$!bU}S~ko@}Kt zvp)diOd3k7P@jl_cpgA~!2Dgzcz8x$%| z>Mo1QCDTnYHD+qYhTj+aKK`lCj?6Q(-w2|U%p;86EGd)K<1>^MrUqVSDwW0Cb82Pu zeU7I0fvb&%_L-2dwN~u*I^dogXV&AQaMCW#v7ND%D!ZzfeJBm^9CsuJu<$FC^4GVCJ7j0mhBs8E)5+Y$0J2%Z@n$+3Jnjo8i|zk5bRA%6d`Pv z4u{snasYYVvrlSyEyX3)hR!c>%L5nF{MJX^Yf&n*|xj{*$R-Y7acQZgxTEANOxkZvqD z(`;rtTJPJ|G*6_APoPsDAEV^+V&ypP!sD^=iH|E zLFn(rz1;083r|j>nmA>sp_htg_x%cJ2)VObWqX>3!aLzPnaNbkz5)`Bk1& z!~R;HF{T&aI5^^uv+@3iHrEd5aDN|Ws{~|y^R3?(-{s;?s<-#}_XVlBf4L=rmR3^& zuID2LanvY!)&XOzWPFCHEtQ^#dv^CLHs{w~kcd25t7BE$9VueL(c}}m2Yt(LxQVxS zL}^y49nQ-CzUVqT6YJ#;jQU&Y!`MdfI`2P$kVn8gx0wC;>0FTc;GW!2x$avpj`2d= zkM4Q&bnmQ}`|h8r5oD^x@!|Pkl_Ryi{7CwMIX2y>s!r?m96LEIeZ9N$81uQ(Y3Q4N zGElXpaYhbB-v;6}LWz>=>Gv2fwg=a5*YiBPe$0>gvG0EFKlyV$|9AcF-}68JVW0UK zc=K3zQw&ttr&|6e&;7+%z!iHh(kR#l8CdFq-f26_o zmQ4G^+z(z^2zoY^z0+7gj@H{8{}F@KHp4v-6LmHiiDT5igBctCcG9Vn_-N0zDhaq% zg|n4zJ8Ib0o?62d4e*?N5(0n{#6LT}xSFe$8X((Gw6GSoKWBC>3aeM~uq?;dHU4bj zU~O(uND{(|C4O9I&8P^~d|D4|B@5$Kws^%3ZY#ZFi`D?QiW~t8${)nn^)>|ud;@H( z)mXg|U#tXt0N`02U@W!l&)wr_xX(c3sN{V)Ueh{Xih;KtzoWQ)V;L#cTrA4(q;|A@ zw9PBsD5mYP&T;`KmZaN+~yi zq{eGOFk3-F%f75pjBtwYv~mv(n87{HB?X99D}uvosxLYe(;QgpCq<=xR_fYlRxT_Pyq(2i($)J_z;yE;upSiy8RX91%P)z$^ ze!HPybRDGNvb^@C=Ut}&G(T_g!nK=?qcdVFv&q*rjA7KPkf0%H6y3 zYn3Vo6xAr(&v7r*<{lP0DqQ2Uh!EE*tgYPWreSJ>O12-Xf=uGr9U)k>YRRY(l<*w7 zkTN$nD|)jwzTtO#!*Bj)f7zFR?nj^T8PCjl<`pKcHvACxw*eLJkawkSt#-{3HB3G~ z)1sAX$ptyS|Bkhpes*>&o~I>wUf$ClyJ7&c^3vk0WXV{^?1L6UMM$^dl3=%9-Ph+C ztH$EV`q_SIhSHP&@Ehbf_dFGdRc+$Bi!kQ}DVC@9dfq&Uci>`tzONcAV$9KXrRy5X z`!uk+RmycO941M$RPT7#s7I@1{PnWftu^*Dd#vmDM}4iyy=u7r;kCvvljqJ8qv+aA zxscW7&|B}1(H(lfj&FV!u8wG|_eXXAcJ0{?a}t~6QOuPH?o~;>|4UhJtNOHh^?f?=SXt39tU$`$^93q6uBcygqNKe> ze;YBC65DIz%@b$+qF?w6&kuh85B$de=s*5{{J1wCyv2I+z%Cgnuh(H2FzuLpWmKXi zm+kU1Q))$NS3lrHOxL0N-KfvhIB zvhbQ%1)%*7Dreo;hCoH1eF|(iO(4fry?hUgJX6K+VhlC@W&E)4s9_xqGwPACfB0AG%yHa@zEM}&-1&~?Av;#$h(DE(}mty(9@0VW3f3= zPV01wuCn0AjMjsqKxf`Bp>*UXA~7jYlM?jSfVvwBYN1PkU<2OkFHIc6r6Z$+){3Pa zAZScx?$uxki4<8qH@vmAPqVMUCA?H164=A?^BojY>u?K6H}F)Zxk`N(PR6>VTv01$ zG(yYG5*!(YMa32qB&Og{ef)@wgKSzuO)j)x1rTLIRF2MI)xb+aaymk>`UzpaM2QnC z_#IQb3}xJ_TjgJF;z(uJ3+N}o1#Z#r(DX9x=0712&{{=+Fu{(Y7%z zGb3tPF#k1@CDQhRLZjAZ5G;W@F4OTq)`dE+%9yK<5Kk*iWG^2yzKw>rOB5P7EJs@n zm8y|v$|ye8l`ZFbG1xA+3tvtrXfGe-T%@&*GHalzY8wVXe0pzyAxqd{cz{0B=DLX zf@wJrj!8tS_*w}NpCg`E#8DWEtG6QKLmBgwGso@%-$Y*}SvG2ko%h_GNuFc)h$c zTrXR$eTkE2itZCUgF@EA!hEg#%lt=lU#^wbL3 zas`+Awmk9~HR4NqtYpM1krkTFa;HW=bGZauF{t~Zu6%u`$6!@Th2AfO7|ImS^teRl z8pGUSF+*RA6~~Qo=|Z{4V#%HC`@J681jq9*zUg9yta)hHGnS~h_A@JW{gzL2NMCdg zDXO@OT;ejpdU#zWTi5!D08)I@hL@zw!|yb$O}X_@3ooHJ&;Wb$z-p(p+t#kpt=78M zdRwQT{ggjl_|#W@&97L$_xJzX-}ys7@cr0}fyC;?tt7?7!z%BzcXq1a%xAd@5w32m znY6|?xhGYK0$Aq=Byi*u_F`16@A zudnf43o41GIw^0!?!jFMQpgij=3SE@h@q8spD3v89cXMXsG@zB{ae?zeNmgwc4I`f zXK64oWX{sRYeW9h(TNNf%hr+~cDSz`Ow`og$=s$llh?L@R;&JCTrJ;72Jln^=Lz?U zIw$cN6>@Rj>k#J~s;B)AcI)qZ!fURiYduN)~sD$ zQ%$Co>$*>uwhdU&eWsMqNd?ENOt38_U3B!b`reeP+-(8SexESZWx*T>9cjlx5rwO! zr=j0lTON-4)bwK0jJ2>c4r*7nbvQO>y57mAH3FiF*r#Y$wRD!e23LA63OaB?%2al9 znaQ0Byiq{CS}35!g3Tosc3SZ+?TZ)PrcD$DLFn(XzBJvJlz*xPO!>WeUpuU}iAC>m9n2Q!`*1je!cj}-I@OZq%fBFCUe}Cqm`*Yv* zM}Fxq{Uvxd*6wv0OJZ9VKc685NW zj4tbOHHJ`#vTIz?pXQ~+n+(9fROmj+;~W09?`SOV@QK+qYTwu=5j;H%iJ(J^676j= zv%Z&v`jN7(Ll+DQM890q=1nazeFY;*4S8PjX~^2|hkuuGo8~t$y)cdu%-4a+`DH4s#Q%Gb};QoTysM`%8!TJ5q(a6%5BMmM7m4p^ek1q_-idJ+|M+c}2-XI_GJ zW&USMoUVxd#Q0{>q#oO0Was3aS-b$9!;@7t50u$fpKH%zK`kKD<`0Gex*~Th`?H4yet*c1E=xKx?XFf57_!fugaXQ z{{W!cy&DeTGv`B}HXW7SJm?wwKucu?kL(FRL3ia`NFXp>P7tijZ_{9&K`@cC6=`IUd~Z~i}g z!|#0_pjPpKp2b8G6`F#p0^I$-Unaxfjw{wyW&b|5x14(6wshc9P%HZk#E%P-uabij zpBy+tE(U;-44O5YW)sBdbGUWaTNxnVsTO^rEwh3KR7z|U%WD6Qb5A{RV^dt~5GyJP~0AndBUAXWcOFWT7gZ zf9pFaX-_6YU0Le@)oSMg5Q6>7MocqguNjklYm*?xC0pmow}NpWh0UgW3)P1ene8Bu zb0FqY5f+%o6+F74qTD4~?`P$CfkE=h&S1M0Ot8^0bI-q&aER~dH&PQmCH%cQ+jEzl z8aV-OaD7@}hWO;Pm9%52fmKOL#y#y=&OI*#j1>-ZlB=-m8NjG&YagWw0NIgmwkuSP zLkKG4(mYB&cX)F~zBb+FNrT2q_)?{Km$^fZO@x(7bs)CX!+8ebm4nKNCm;>T zuz+Vbvy=toNVzkJQ~Y57mDU(>xsg+tT8cD)LCnuIwEzWQ5fcepBA7hM<)iyr`+y2$ zp0#1^%51+EOhLP=PcUbB76z?}Nj&ysr@hHDR1tGuKK1$J4F7#P*)+T4z{>_rG| z6|$L99VvS6?Qw3dpwyYk5XOWL0MN+Cuz1@T_hXGwbGJpDNMvh}y1N$?6vsZ7mS2(v}faBJlkMa8RZi+A;nEawiWuEtOpgtpyobu2DMaPXQnaztQTzn zSU{)0$oW6vpIA$sV%v0*!EGAXg{6jxoHGw)IKm#5bV zyK87Rmz0&2B5hk=MqLJ8TIucE5AoQA-~D_4tv~VK|M|b*@zIALeQJS#=Q+oPojRG= z8)qPsM6dp!Dg!*`bF)y!>7+96WwK6nxqn0{g>g`rzR^rMKH*ltlvdp;Siv+vYuFgZ5CsN3`9-e}1}Xe%+sm0>%j@nLT0MHS->=PgD5@<0m34PxVYxbV~rJfO1Iu-{u>z+Bs$?E|7(B>S)E)M%I!b!!|g!z~Lnm&>m6XkC-Y+sf8D@<(-H6vn*N1CV0 z^RI|VCZN2M^Dw-yaSqn=T{jMP^}%`e`TU>o57&?V2Y&27{RjW>AOEL6`O!zTn)`vm zgFS5u*lP3_ZEKsj1Ub|qU=VV&7GIx;~9CY9Brfo;1;1N&-oz6ft*#18{5W(?Ll?)gz5so4o|qWK%Xas_0{)30BowNhC7XARm=L2dy{_)Lygw zAbhRCFxyBzQ@;Riqj2MQ#y6W*O_%E%ZMU$|Nojnw=reS@E5{2x01_ad6t;kHRrZv) z)r>!}tJbH1cs2&kR1IW+p`^9cQiXF+vb}o1uET~1QKRy|bU)qOvNKm!&5ZyC(h)4f ze4Z!_@6|{niXy60^5RWuKkzJD{43Bb1tP5-&}oujEd=yg>H64pBPIY&>9he^p7P?> zWE>?JOB7o|njFkXwIg}Nf>5(Vg}135Q92m>(ukM!QlREU98gcAI2M?KYYw-|bxz@3 ziCGsHhXH-X9nYatTg)KEEIl#jbh^?2K&v`|$qAxseBM)^jYDFF5O;{Vd{EHX#|6^c zd$h^?YW||XTMAk2igl!l353kwf?1M|gW?{nMc$x|Qs5u!)|W5dC_HBYgpLEPeeTnK zc-a7y$4;N~XdD1tw?e_qD#yJP3Ry<^I)N&hC0PWb#7FO*38>AGbht)hxrIbwb_}h| zu}%+IK_e4Zv3CGEpB)_+33;jAV8avhHH}UXG73rXzi`DzC{AZk18D{+*Nz#L=gjV}=!) zU8|UH4W5fDqejUzO=opE{@_C*gD=U4@*UN8;UeK}So!A*eJ_?tH1RSBEzF3NNA$9O z6!BoV_v&j+9EiD7hK+mpdWT8;LXno;Rr0AL z1hCB5Sox`)KwI>q5we$Ho$m9U3reYQ{LThw6xQH#gkl_CAHs~H4}^48K{Hf^#p>Ao z2!n+9buFn2k*)1!hAiedQyUjo(&u}W%RX+NFceTBA)=wL_XAl2xg)qw*({BzhUy{G z_<`n`lXS{Frlmixb&-c48@diHH%G-Z1sea&eP1`4anU`fvU8*1Bj6EP|Vumea2@&y__At05*m0g#=VeSXI=A zyQWus0M6fup~`m(gLGQ6gC}Nqt^A)2ENwZc?sRNdVx8so5Kbs7bi6)`BC)m`H8#-f zMdWjB3WtH(DR?KuMPRO?Nonza$>1~myJ#L8uCYPz7L)DvPb{*7t!meUN9V}%b ze16W!Q5ftI5Oi!ikXmpld4%7YcA6$4gv~`dfHJq5!&-9=Dgb9J=SU6)aI)fLMF?^z3C*lR~sj^9`YXK@Iv_*Y~USkN%c(H0l|HXlypHW6(HI9tf$=fy>&Dr)IoC$BiTbr z-0w_gnQ?-HXa=KKDAw(mACH#Mz(K3?Q>4R7jxBES(02jYi@?JH0hZc}ongft7HZQ_ z)(mb;xf(Grtu=Rgb;$@7O5XU@$YT}uN-G>R`T|WL#N30ofz>*wqQxOluXx_z6E~H_ zE^f)uQuBN9)dC(xtLbsOVuYbG0j*VQ&MPh}Ln_X_<k~y>5Qr>Y? zrII^!DoKyJieH>zSd_7RJRmtMVDB<>U~Eu+;j5RJs|Q+&r(mDZn@Gl+hRq++-G+ztg~vns9Iubmab7vGl>gU-&Y>Yjf;eqYMOe8NFb2Ql z++RAwaPC8fQ+Gzl3cZMfv{zDYwhwqcFFs*dY1Uy0?r6rAA#V{<>K*pN`zwK7 z>b$F7N9K=P78$Sts6h#D9$Z<{5Ycv6&jNt!MG8^9qItRw3YFQ%AViTxA4cFT?NXOV)ptk@%OorQuPax zmeSS~uR*Oh!(w=FXzNYA*&jYWc++3?%fI}SfBf72=(qjg$4@-oJnFF@T=80{-f$CN z?xtfIZ!HJj!mpx|97qcLEF=gfF|7sHEQGqso3~Q&S$s~3DoY8OrK*IMe_K&5r6=PG zL>-8aFX4vOD#F6OyAw`Y77Cn4=CPsCW+wreT(c~Wz1A(D`yl&{MN6LGYKnAW|obLY98*>yyDmezd6 z*_!_VJ#=L5tcQJp89VQn)E2PtkdF^cFRNz4Q%Y>hdZP;O>Y$&F#UwLwpA6I#px%|@ z)C#NxYkOPTR06cCyDQ@)$}VN>e=D}LEX6@6FP+DIL&>D8!bTJbB3WDeM6Xehj>E`5 zt@X;>&Ll14mGue#l}nE`{&QuimiK74_e5OlvKDJQ(p9ph+v6r^;&8JL&haP}u^brp z8WcmN6*{M?iMGyE~r*ap1s$tvn<7uSX}-i+f*%2qYJF$M1rP5!;=nIll%8v~oQTSz_laVzUaH=Z%jS_6gsRpRO!GMrVmIn6%AYu#s6|b*p=U9%u2QldUOT;13BpdbB&>Tefm?z694mdW zj|MU?dj89MJ$ekY`$QD>dUu{&+jsr(o40T9um2mr<rmg_Y%)+b|p#a^uoLc(;mpZ?osQYSv9g!?g9G)PENl9}%!t*s$x4p)B zmiuhKQ6Zyl?-2P`V`C;pu#uNkGq^f0!twfB3pmiyyu`24SQ^DC#|>Fx>4N*dx*so3 zuVwpY?q(UFbW*!N*Ak6!yvEyFHbsP4XRn==+3Qc9t`@+4m(_!mXH7c;QJt$d50V8fe9-ct$v%nD@}YRx5m z4`7BB`HAqnveGg_d`exz-g{1}e6R6HfnYiIWk}oBHWnHVOOnw+5@R|D*D7D&)i)?` zJbp59Fse)Dpx}?z^cuL$H|>=-xeRakvX%88A-f(fM?v1bW>jrUEd5kHQUoMu_xs1P z>S-APrB|=6kx6c6xE0lV!sTP69pC7-bGz?SIQr;raM;%aQGJ2|mm%|PGhbeN)lc0l zN^q>YcsTbhPc)szM}32l+!;ks3ZVM5q99ziRH`wi%PkOK&K7rF>+g5{=pX%K_doWJ z{FI;kdwicPvn$v~)(>#`fdfJq)X|H5C+2OQ9$QfP^|_tx{7>Qou~g;>N9G6lpLz#((ldYwsW7@ctgHj?{k zy_R%=iOnEX%v4a2cV)%Vw__)ZK=jMj&5Vo;xxK=2X*!<7{z;DwR45Y$^jaD74TNR8}uE@Xpz2QBzyk0tp%yhyb~H_6C^gUYheX z(-X+CYqG>K{hCENSQi)Uqoy`2xo2Y$zSzh5!Bi8{oxN@HPprIdZ_vt!XTOfoiD4AK zx`Fx~_jo^7xz5PQY>Y2IHzMl+*7DOYLB*`?MpK!dyV1>cUwhs%vabA(* zSgll;_9!{+R4pTKnrTofOM}k#U0SEDBM{fITsU|iW8f_TD1%yMPgd(DDOdjkHsawp zWv|iYCL;D01y%r8Z%dWr6_120l@}VGCC!f-Ahq@sR*pC7?(=3XoK^T?KjJg-fBr*% z=%4xT{DjZ{iJ$$WeiT0S{21Dm%OI}@e@b|COPx+6y=p>G+~DW1&{$a1;Hl)rWZeT9`diyT>y-7aHcltlf&y172@!Be~{t@sSmelcj<=?sdGjvG}c{ z$s`PGmTdM)I%8)bDT z9o#TJVV`QrZ($7Uw7R;zNa1whO0G=FQ=J8S&JOZc`V#PY^u>xt7eb%)LMr}BWyf9c zQO8~E<3k!*?OaK)RT-*=DJE?l`#gYv8xf+HJCx>oA`VWD5MZDZ**XS#=|3+QndivM za-?z&{SNX^MS(4ES41I4c9%Rz`@Ep?sRFgMd!k?PFYHmNR znj;|#JbKVu(|i2ZE2`l7!H}jm7+vvHyaekK^W(x+701RP$==UzeL(HovcyL1S1j65 zaVlln)5522p5E#@?$63!gM}SB&5`**iJT^}KtB+k*5Yz8v#(Vtme17}r_dg2Id67^MGj=^e z2~&(tEVN$wIzIm9!1dF{FEhn@s`c_cz$FhIY**+0cr44>dgeavD@Q#E{p)!i_cixf zlvbI*r;TdnGO+b~gv{@=LbGX45=Yb0@4Y{NJM#{_pB;R?=)Uje)z`27&P;Z%zl=e@ zKZbMtTNMjs43_^j?g_=8p7++(6~yF_+7-jgPjTVa4dQeS{XUH^;B^rPWF6Ca_X2Hj zDPs>=k|?g3An|$wmnXz|S)U%l-9h?I!zbU?_kCT}22Fer$o#=j^j_b>?E2}9+V7Dp z{{Ntle;h>4$~eo26uqHbdN=AK>TVno+=m`=cBR}Swatuoom7a{5x` zuM25BI`i+FUjevdUr$({b(Ai+G-&C-#~M17)ifp?)+I&xpu^fEUyGcX2p@Z=#b4*C zH?h`O7}7yn4@@EE^}HAQT-VJ{q2IoFa~A5|&;NN}^7fm4&+q*c)eH4lsM>hcgZ53q z3uCJW>(%Am?3KTx^sjBJ*xaArt>CcSUvSgjPLQ-} zj`HSWFWGC`f3xn=-fRVo3Q}<@L7ROkCoTLL9SmhTWsTRqeB7pTsZ*%U41TM6xY8SP z1Y`P#RgO9?JByl2%1xTdij~u{3Pz>EFalv5IOnKDv3P~mT3d>>W_PR3I0p-BTj>^R z_DN5EvZTFh-siGp_qKA?Ew5L9C~(d1N|7!VAq0q+1gz1^B%KP15j-{p&zI25+KS~% z@qtdF@OR7Bv6(c={A+1jsCeNLwB>}S)WnH`aV$sHW@mqcS@t1a-B8yUES7+iYo!L< z-JM=^0Pv`0Pq^>_2gQB~w^HWM(RhJL(y_^Svsw9)ijn#|C6G>h*I-N+3*+z9z5nFI zGvyg*6KTG-Px@M@*46~3mDq*6>Q@xw=#=NVB?^kqJ`$43V|Ntm6|#n_pVzr-+Ee^)RfE{uyhC^cWMRs z>@X2~7|X5}SYj?Dk%FXvw|j~xtr(BCVzkweJs>q;6EgSmecA8fGYtg9w&SD|J&XOC z2({>I<^2>*jW9k>XU_KF%bT76_{>>%tnCtCG*Lj>oD&q$TcjWwGs~9J?UWJQV{Jd_aZWhLuI+3!=qYYnZ243Isi!rdP&RkhQKSjSV><-`Sh8< z8$><_v?`x&jC~qgXVyBzHOgLM%w!2V!~@IB4*5#kf#!@2@w3);DmE5FwzIYLQZ|PD zH5VsbyV=~xz<~K;s(KFO;0*!_D?P5q+qZA}9N+EHT0pFO+iJ~*=o-YB%&5iCrt^=CHrvxMHLX!m=0L;- zz4)Qp@CQn8jq7ng9~9RaN4R>N+gCc|8InF$mr)mvF%f;KC4C5&NN_#KL8G6qDuTMc zFS3BtDc}YOA%EHP+wmmeb@1HB1;4w3}qMW+EMrOqEe{_{Q`|HMD>-}|E9`Fno% zzxaudK0%wAGe4%V!-4YZ*;}m;6uY#Yu%QgJfb3XaZ%UVlDYmTdvX0*Z$xhT}Bl#%Yf-Q9y=KK(6;^~57Z%wOw?$uU7gb3$% z9?MrvC4qRps#KXh8D#UnkNVuV=2fz7If+-2eDk9FBD&hS{MhzT`DixB36?rs}u`MTlJ3GFYS@`(yk^VYDJTJyz96Ob8NLc zlowD(jX2004&pj9v3FpUBN`%#XZTK?F}o&}Q_9>f2lcYXE#kd(I6$6@c2_ zk7GT(#Gs9?4KL9&kl0g(-cnKpoJ}Nf85pV}LMEWY9caC$+!Yhhi4085YzB#KI2i#vgOK+j z%2=)*K5J0bMw-f0`Zl=Xp^@iJL$MyyX-UeT-!T;quzT+4C#DDk;zTofsu|N9J=R6vHW;=uh+edy`6%|n02|;q4Q}@oJ z@iH%tu@I_OrYxFA)QHbU*FrA_IkPI%UaP`iKinK;=ES04YbuERPi>0JZ zL$q-^S+KeeDU@2O?&mPXt4lRgFfY!`c-Us4}0+ukKiTELbv+bjkaaHJNk<0vi`*mJz zZ;`ah1n8Y@&G}qq0hwQWMS*iMcif#(2tD%F@^o&ZMeM``75=xEK6=Xw!S9w9<4Fd% z2oQE+o-4F^DQT~2Zel*~k)5V#UTyBPY}zd<0IS!LtI>0f-5@giJibSF+T4Z`Mk#Oo z#c>`p0_-sh7H?Twj6P|*G4_9O@gLh z#(?+ia9jcI*6!zsl!R|=b>;225L5WHnU zELMRoZ{-Bgi%08qtFhzNbejg@=vtis0KiSG_~ZrmsB!|eT!~UrUYaXCkOkm@tHXm4 zNtY``%Y;5qTw#XB^edy1b0Fbo3QZ3Dsm#3tiL#@sM69-q9v^q-y*)OPn1bX!Br3hVhVG$oz+)_;o^cGGouT(J&E!-!dQO9b zI@p`=?AluaJIg(Bs_HRr-Dz z&Xx+o2!;{Se(y6a0yDZxC`->6bZz=8m{H6$i^32_gwJY}T`TTysrL}oQlPmOF`%=Q zb+rKb{etT}?pjvDoIjP-2_SGYyeNhdjh}s2xYHAN*A*>tGJgd z%~)j6R`a{Ql(>5$K=j3$th5bcYm0(Q*wz-BxUBi4%tadOwh>oI;UFbf(EYs}mW%70 zcXByBoQ~A`9t)Y)Lf7I_Q zMQ~5~_y}<=D<|#cYR?%0dStM*orDJI>JkNe{1rNQU{#%MLGfsF-{z4@Rg%APP^t^- zu~Bc{;&1-7zp=mT&;R*v`tq;*QanG^&qlK?c=@muNkn14*>$?2HVPTDsGVFf&&SLv%jR}edLoB5|`Zc$eRe~;AmjKzG-tR9(9`B7U zWhf_1?BJaqM3lHp z)8uUZTGPMeHksu24|!H_a`dOU!FhHd;uGEvH_HMBtO|a32~u0P*@+$gXmy)<)ejvq zDDbXYCufO78}*tp3nR#PE~G{oF1ujK1zh}D0FPIf(!YvH@~pVU{`=q^tX>|Eb-w1S zzN)_A|M)xp@Vk#c#@jbwIjRgExLAjA{ejPx`3tVHJ&ZL^mZ@TH7&ULwXbp^2csxgB2RPScu(KIg6!gPh76; z6NZlqRqjBKD%7qRExSa#y5oGTMYZt6dq=ak6JC}Ka@&@9@(wX4>)byIg+x{jZ|5ct z<{HI;UC*54MwfC`yR)DDOIo4u%d*tkH!tASbN12O(rMFUWybGCn zT&Hqi#t{Shpj=rKr4+M(epuH03d*LdYSWi1Yi?^+=2G<5@$fONog}w=@KFX@O6)`* ztX`EJUF_s}V6=-&(Rd0=c9}m*v{km70P!+Sg{#i_gg=}H@WOW^J!Mtf#qSO#2Ot+_ zEn9BBfD1vzblhBUsqDX@@)9ORybh@4LjI)IsvGBdxDCQeq>~3Afscbbn=&m>6>xm! zW?9wRn8J#vG*o#(Fz{dk9p1N*rj(?qTpQ2nQ&le>Cv7l>;xr0RIP9jLo(6vyW zj-WeX!s9GP5iJ0FkHE5$go^uA$4J-$bPUL?TttsEl?CY55i}~(2^!3IYyeMm8g|=- zz0*jolrvK#C#Zc(0h6-~z=+4~1+GaG)mn*G!V72N9PQ;R-CLlm)Lqh*TLNpL-BVDdRctu9M5Oqu|q{Ng_(*!h*Zc(v>dky?}m) zZ~4Q2_*;JQFZm@u;tkdhV)fcs>-p|rgXi~E2(6&_L`UK-Y`d;QU!ix6lbn$?ww+Q# zoRM;JQJiQ41oKI>VZNV>=P&!GWiuQM z%?ox5hO7mz3*UI0ChD{gA-jdgF70%^swl_w*QBu$wrBeUO%kUVFQ=WMFn{x==e;1% znh(SgyH@r+O5R^}EK{O;&E24Y+L#%&7`7M)`YvZN6<>6KVx(~fb9uc-n_>65|EWb@ zXVNT}YMCXvKn{OmU86Z;Vv^K;U2k}AF4prN`h1J(a=-Tw0%K*`54AcMTfR&bbpd6t zzPtGE(?mK8#0JlM^~a~tpZnRL{qYxk{ug}Fzx6GD@QdF(KERv3HGzT~Zw&}kKpnv! z^hKOWKdAT+cJ-IVH`=n0bK>=^$)NLRd1U~s)}C;G(<%u+WBIUF1S<>QPx^YxN3b=L zh$%o05^PC*M6`X30|i-18dR}SovZ&CM;O~K(7&rFo};>z>0)4XM%5tixAlds4?kSy z#;lpoYgaR8IaR9{xBa$GYl8f4jm+pE!1#y0r60RfCO#-0X?mU~;DNWD`iME^krlt_ zS?BhhKwk_}w96_-^gB)aD|{jhdjR_6-S8G|YgOk5A`~7grBPIrN=2cc;=fXI2}K~I z3bjpFVcy+ZFRxz%{f>OMob;@m;#PH?NFYN(o>jd;74Yn!sSPxGkCGMckKA8y$KMlW zKwow{IIW#Hu&@|Es^Y+DjvgxlodM2~Ey-Rxx!7nwZ5hNtq@V^hn89 z+%fr|$qP<^s0ECv?wb}%>QX`1L8oWO_WZgg&m)Cvg<$-yk5AvJAhdihl?t`R93P@% z*|<6eNeZBpgC|SaR7(kR;4&)JsR%gsoT`DdvZg)!J7q;yx-zl`7lH;JDuu0ZuHZSg zO~x#V_PSZI6gtJ<{PRe`bow0}xoBSdgp)?d4RkFvpz+&0?%qPro9bwWn- z2YHP5?Rme}e{D9jd5|xw7Vu!Eq0kR}>$m=?pLCwjU-%O~?;k4c_2JvcThs>j-dbT} z`W#|&$BW$=y9`QUWU3nVP*qg30JrTaS!>RpGB=guHd}vPJLWmp#u4>rjTaUM(A;I^ zAqx`hi1jIjPc}`i7GvSlRlnpH#ZQ)Wnc-^Q-!;Y|CS~mGbk|lOmBdhMUQkWuoJK?PH}xjTezppQ{-VDYOI_4V8@L5UV9`=54tZgYvtWAMltsqe{n+H`hcBAiM4x!x-MExoVOtRD6Ev?P=p>{C1lz{_|S*dS9>40MNx2-FHUT)Zt};APPW^ zpN2}t4&?`idgmDp;RR_kK>xAmy_p{hy>x7B&-<&n*5xGx(A)fLnHSC6dn$8+EARbr zaGO{U(@s_#5S#GSZATZ@54bq`^{205p~zbehH1kE)8OJSL#Yd5qCz0RV?WFDzxdAc zw=TT(3kT#ph{xAsU3{FvKQexO$>VUq{;c- zvhf}-a06H+&#WGAcC811$1nS`FJFKD-}?`~;O7@b?F5=bAKnI{Gd6)+)a&RJcA+^uFN=4osHm~l8k zI;W_)oE)##pcD~kovNTji_6D3!((gal5>&V{n5JByxB)t<8VFEtQc+w@3L$@$KKNP zeB$&L4Lw&^Lhx8?gCh=bpSGC*p2u_X&jEb$!@|TGO}^>d5?T+*Jh|h){m*KG419%* z_2Hab??FGO`lCCCTim9s_1zM$9~t{}cWDWlVj~n|go860*vI*Gr{UniQR6i66H1$r zY)&m7r8ORX$x|<=P$7BWxzpcE^P}F@MO;kq)j&-8mY$1}U*Cw2p$-vb#$~BC+IPYbJ)Lke7nH9L5Nf4JR&x zf*+h5S!TzK(;fRtYkS+zRuI(6esxGxgR^4?X>?jb!K8(;mKermMH1MKS`tGqfgVPj z%L08x3NXRHj z@tq|fajmN+Pc&UJ9uSgD6`8FY9M3bVp~}Ki;ZHZd@H&+ZPu2_Y*=J5El?{s|oL-KF zrlY9KN+;)}ia(PVm+)DBARAn-1{C6tl>e)O}ot)%#QSUuo)}gT?%Qe}3O~&3``ioGR@j-9BO93fL>jY)YY1=IQx+#8&UH zaZp{0fjF?8n2TyWikpK)muOu8(<2$2d|3eGJQSbo_c~s*)~=g?S3r)NzABY6^4vC- zUS3j*fuZ2Qj8BTK%>*Ta_r)Q7oDcw1ZXS_I{=(mfzFi?^b8RLge~n)^1bzfc<}1L0 zNibTm0_ofBdf|_D7^io2-ed)eX(i?43+ zBed8Vr-gFOCIF8&yaB5Uf0YTC!(LhaCXnrNG^pNT$0%sYQ7t4Iaf75wBGCmpvFtya zrv+bm`SHW~t7?PKMH2{=u;a>?w`Z%rSe-Dv6)kriqIk79m;(y4S*1=kckv2)3mAd7 zafVdYc&MPPFU|BFTj)j$Z7LQcKmFdR8l`Q%dv|Q0t_)#)i=zW`o;4)h}ExTFU0~ z-$Yan+^m`!PBKyBH*v)$+0k&e-H?_7EeGkkcPVRY^f7*#S4|H6mWpzTyv+uU53gwj z)mFw)%1{Etim)1uC;F^e2Oz$br6fA`KwEqR{{oP-YpX2%no?b@fs10oMsXMEDgUg& zo(vehe{4Nt$TwE48K>&NT2_a>xfI{oM?Fl^(uftf52MklPN<_!{eE*vV!@0QcQX|( zCmZLuvU$J~dH2|B{OLktMR7O@xUrs|nL!ylDF(v`NVS>01;{){3!kI@W!*UHE64av zK$-T|SF4b|Krb;SLNTROHxrZesh0s2)FkCBU)`)^Y4*R`_U^ z;BE=2k;l?^z*1umc{+y^$?@RJ%_y1*?Wy@*X35fS%VM+O1zlD$W6+V+(eWiR@aSIf zPC;jbsoWdY8w>lgNDJOK1qo`~PGSZ1$=v-SDRwfJCZKHUIsy`Chs1b@w`cbuDFi5^ zs(n%9C1$LRS1$=V89=2)ReHA1%2a80@to6b^?t<$7BH8;b88vjv&G<5rG^&rLuyK; zl2#4UEMYp%xl*VYAgZaL6)IfNsO_5|xHYdBg0*$L#vSk~$So?1EympLstZiO41RBfxTWkpB(fS|9UeJ&_h-Ol z;JtaUIOnxX?s7~UmP@H#ay%>DKlywqH-mmEbt(ZD%eHRuK}!t}X+Qv1AsnN%yIC6M zFluSrgtt9ZTV(L=dFoMC=nHT5Lhm>Ai4Q)&H~hA5{H93@{2Cu9!c~YRIm-Kz@n3x-s$KOK`H8Q(c=&PD(kR1~>5&1-khu`7pkmi#A^rRTXX{@GvNv?#AXUskTE5EgYE!_?Q4<3W3F7oMMF5h%5%2bH`B=aT_E z_n|zwB5~9Gqp>zpBlh0+eoK&{%(M}9w-@Wn&HH!8=k;hw*tE{`6r!h(hLLg`*>l}? z&s_NUWF7q8m+*ecKpD=-Fv2fSV)S{igL8+^$8*et_>a@U9duh=M>e)h z*IK8c-SPg2tR?7T+zMfN5v9yVt0QaV{`b_v%g+ng5@7L$Ax5C_4#DDGMwThJNw z%%;V^;c;8}4u_jAg?p+3%MusdrBjY~YLfZZRM+!J20w5V++=M?;q7b`)zSNU!I zw4d?ct^M}RulYNF<9k2j&6`~=fUV_h_LG$XDEatqzz3{dY4nVC*}Nxa-;6^$wbEqS z3uPwU9bFB`&#Sd`0+W&)LRk&cy92ou=>z>bwo*pEb<7z*BBxxoujz?{yOd9 zdM;IwE-SiY|5pm+CDE5f%Ni%MF8{9V$1Y2<-TlgX7kbM4^Q@K^Um8~hhcp;a+8&X_ zsMc$(r-J*-Ym&Oca)J@Km39lTS&ki=V@l4WN>wTvuYn-*j^4r3w@&(1?bwW!o?NT! z^NWM~_TyQB;ZrOix&--!|LbdX9Dg}zSIEY-h7x~ zSiwmwN*g6nkhoSV2nx-AU*qGXi3vO4iyf(~fKpLnv>sv`Db+S^B_U48*I3ds1WHr~ zfwcH*Xj9dgaf^$cQAY$}jB}WY*NCW)3u%gnHC|#b9;@Q{8{w6*V8c7sYouXEUqV*H zjMg++g-h8!;BtAu&{in6fby8pI$voe481e)i| zFc^J=k5(j<&T{=Np42$(Dn5}?`YfxB{HKOLdXI9EoI-wF#BvpQSWCd|X;@Lv?RJxw ze~;@?aTrs_gs6I~?yK}?z7PUi$LD}!3e>cc*R``!*G?}#FWT+)_H7%if^gkib0h6?>?0n<#t(lmAq`*Cu>qz;FJolPii~Qo*781Zqd4?~j z1!j2OyxYf*S2Yh*f|3UUbgV*KYT&3#P`2@@m1SYi$saU!!1I!cVK{fX4n@5L6{Sm=sVF;o_{dC`I`W7kp)HCZ<3Uknr?iCH~7{j~= z904g6H7TE6abAO98GtmUbeI0wPF|Vc()hDCxPhvJr})+|t@+wAxyxUa!isWDm3RTO zwohuPS_0SB^skFj?uG5+!nM|_xrHmw%RTRw8o*0lgLJOOx#+cQVo{Na7L8pM!<}NT zSa09gS+GVi5P+}a#LsC7KUM!JW@t<2fTIiDC+O6%{xa$XF|Zc8kBQ7LX`u&P)#@YZ zIpN35xbo~vTRQLUw}q|cETf(yYZUiF3&#CB-kLD?C;0V!eN_0mI7eKC&MG=R5&#ax zXSFKWUJdk<3#S6IBZXZB?g=rHbes)$j@mX;45*#Iz3SS@c|HvRj5lBR#x^EF4wUh= z=hq9yHH~`u=;0JSmlX=KhP$UE{}?`)9Of`HynbY8C6NhCs5dpo?q-qP09in$zvB+H zaw_({o&sBB9O&!v{SB0k6l?=Wb`#*Lh9pFQ>g;v0ujsi^fiSy0EURD^>i7w#0&4ic zWlT0OSvwbWvNuI97cv=XKbA%IufWEs*;L#uBv^=YR|AjM*>>*!xpKWl$o%9K(}rx* z4Fa@{?!t1Z7P!8p%A272GU^5Ge{=Vnw%4VMgN@PB8tC4&#>e%BWf`oVs+U6TeSYBG zJN%As{GH$aKl>m5OCL9?*Q%;oN{*J*mv|4-K>qXE&;R0tc4%?t_W4p*AZkAm#IacA zxyeShKNnH2+|pzV=f3MuJOAxxdH#NWj}z^_rYn8NI5$y0@6VT=$M?(gJwXz%=hGR^ z`_DnYWRA;;?e@BR=Ho-(O6$VFL<}Xh;(E0$l5INEA#uoie3DBdt~t(1y2E|7 zdmWu#cpY4xZL{<$8~narCjTA}jH=7zsg{A+x0M$iV`deYJyW^WQ^f3*8d>}I<`H_> zVq`2Cuc7|3(ExDc(TN$?41aQhG0n8W*7F@AzZ~Ricm2|c_SxD7mg~;}=ri&B9r3?{cwrRal8G6!KZ$+0c=byF}bSbUoR=a zQo>783k8L}z?@%?i$s+nzt~AI`f2Pn$4u%r_oXS3IpxrkD>I7}UQ;URpV=o7+Pn}S zYu_qIjf6~VZihA}jQ?zKVDj37=!M(6v7Th*7TB?+31HyHj(YKEyOjkaLAo10-p@y} z+sC>Y*ql=qu`>)^x{gN&ieq@wKl@|9lR4iZhd*-!uws>n$RSKRKdhkNj953Kj3DT$WAY zDyX>4;j^2)0ScN$S7%}!=$F3Nbq%1Kd%}E8P9YE?SjyIC;zItO$NPjz`obkRoKd=z zo*l%POR&rXC~S1?Yi|7B?h&*`&bMsz>@~At&>ZF9^LMa8bFBfEI((K3g=AUL8L_h> zHyySb2&zAZ%HX?Q@3Jc=^8Q|?u5J4&CPymj7{h2U&X`g(CX<~N zL_86D0ubXS6I`!<+wBVUx&T6$h+3+Mt6Vs`NIB})Q1i*EYc36RXG6xU?UK{dt*)uL zFK&m#xs`|T3Rh^Hc}T^U8W)xIbOX<)+@h%axiLUP6cZ#!!!YMU6O0`@?y=+-E{)NJ zb-l`9KsNc!JFmAms7f9+k%;|U!@qDysf(UHm*+*9XJtXo#S4KEO4~P@w`GhK85m;g zkH)JD4ghJ+Fn1Uf>*;A_9PT=@^3w?6kQ2$AKQ&G$oquWq)bKyzS#zB{5wI7O=?4Q% z)^VlhuNSgq5mKSE?k`8GJZG0WX#O!|jGH+J*{SYUNF+uAZA{3gZ~ouxtB&~ zL~VZ53;vs!&FK;pPkNsj&ACHlggaeC9b7ly8r2)!!lz>7`S5`wMYzE?^=GZ?EQZF4 zelB!#v$3GaLL+7g=sGX@H0+ve3r;}TMo-a!*nqol@k;;b_nI$mo&D-jb3#>xC%W-v zzx0>>(7*d<|F6IAZ+`FJ#hdj&pJ%UC+?(xVdh9=;aOx?ovz6^t3R{T*w38@oc#Trf z(F^5XI^AfMmOG1#v*Q~S7WSgQ=VTbxN`UF?Wi^9Qpp>{sov5b#Owwmq zr}P)l&y!)%w<-FcUGSNr_CHETJNGt_Q0YHKM&f*&%!^l}`;+Xozl(}7;ex7@Q*9hu zWBc$n167xrs-bJQ?gw0YZpO~h$?}oBz100iC5}Lg7wk8A)XR#QSGo$cmDwq==~G~e zOehSZSV@d+(mFN->bOd@#c!?zA<)41O?|;u;kYvNZbohkhD=iqK#^CJd2VNbG$zLj zwFn$ADBUuKC&}Mwlz53;1NNHQ!Um3yu_+Txy$06JF_kjNXj~<9t}iSlr_YoFb_<_j zJnLG70(d0mYPkPhV=KWWZ>5}e9e@2u9$5LqchF$55@(6`IAsC~M}GZLjM8;dZdlE2 zx9hA~U%ijc?w)uG(igmnENv^Ak(wD|ws?vaS_+0gaLak@cy7!s&-rM79j6nr~ zbad6eH<=$PIVn?lfCq(MN6PgX`6ByPdQYF_pxG*k28$l9*1yO2)+)fK3vDW=CEu$> zOCI3|pjSYPX%6WendQp{LPn$twN<8H5U_QWWgsB9^d)2al@hbm#p*fEAlUeOOG8yFZy}?d_Io~hJsC-VaDwjBuJ-VS?7!WCUZIB zuTzpQ)fGudaAGwV9JXUN%vm_{>@y{*llB^mxV`+7Dae=-0rCG*L;Tx7w z%!LUC1EuxtrOcQDl>o8V$-YI|g&sA(3>|9EX|ACg2VGWSUD`~hb5=^OM(7I;x_o=9 zY^J!)-_Nl;cjk_qW1p>rsHQrOM_^Uk2Fm-QjjH8(?XJ+NhXB%CHU?$&)xJP%oxYhS zt%%gGmwDOHMbN^ij`x0F$lB!#4(>0+JnVEs_m;BRar}gX~6C5;8!Lz`jOU;eq!iO8V*)g#$VsgXKG|gVP?ZFUwU^!1DrHv{mo?OcU{%?@Czp#aE%3);F6Ca}O`P zwy21IDhjB}9IpWmj32%XW zqm-DdkWjybzE1KcwV@Ym5lhQ78GjZ0?KKZ9`(9`t3c6-SDAbvApI+ae)3!ZC148TO zM-BeWuR|5`cJ=e%`ITS(WuN(f`hDO0TOM!U;M9JDI(R%p&{A+GEP40J9_5CDeEAc} zHapTv3A&Ooy2f=dtDs&{l!PN+*j?SzQAPtTX*w zax2GrY0z@O-Juaw8PZGffr4c!(G~wVJ{GuUZd|x7(zZ0V1!AGfW&4rqwemgV^#-2J zo(!_YOGuKdOq>ZPndWFpYj_*$bEHsME#CGhZ$}D*X zDhJ~BE+awTm~CvTRAd1lQPbSPCh z#Kd(?RMZu#ZBRbIKu@aEo{H640a4%%BwI`xbWDn{t;D+r0hI8x1lLL?&x3%Fdsi+R zT?bkgxJe7j-i;Dl4r^pBs`(slxsZ}=9dOJ3l5{E)v=v|x50`NE|=D`RYDzS@fCb}=a~kb=_mQm7^x==-pB(qENV3Ypi}|G{NHJO-cKIjypuJ=yXZf1ju}kTM@X` zi;4v*BqpEK&@W1+qToXvxgx4bxVYxBJ(pWxVw)CIhV17u-ys88FP5^SO4?$jLFl=C z(P5r@O-+`dU~|*vf!r5b*?d(ra=xm36SMBH!Hm^*7WG=SgSCEOJjW@5?Di{|6Xh5wR@F2GflSrarAKz2@Je(^SdsXg~Vv`!Zx zHK&(ll^y@(+8vB`uuSxdgn)do^Vo$EI83JG0#P zy$u6d&iB+Sb0Yk&sCHZpSE>)O=Qeo^eLtn(hZJy1^?1}2Wphw4uyZU*D^Ki@0_c5&dVc(apZFO+?WceLfBk(w_$&VNzx1Df z@)I9^_;L5z2Mg82dD%#NW4UUPxmhvBihs&1ztB!e1QR*ri6tK~lGZD2GM7zcLH>_? z%4(F(omgiBctnF$1NAKQ^CU2XDtFOPX-%mt1xVB6Qj+L+WK$mR;KTv_0+^_ZWo~9n z8{Ezd0R1GRNoF~}CGC8GG;3p`VvL;CY{RABr^F?#`R$_rt>X;h6NfuztmVByE&tO8 z&W6ngpVz5&@vAJCwH3Ix;-Pa;NAZ`n_W>JaL%yRxAO`y#x^uz7a^U@5 ztU3zfMLF6Emi_t$QX1Dohxvz@$!M+eKW5RRSalWBc1Mu?5&hAG5Mbml4W;*>r=z_{1f zSvMkVYvL^i&5Mig zhnoc|Mhmhlvqodh-+ibWAOuL{s&qJAm?1Q9zDQe6z-4GcNZwd!EW zkQ9aJdv}aAx>*hqTj$@_8CRt&6Knb)C|LJeb1nsU-5@=+J;i)=3!cSiubfKJSaFHt zm4L>o3pksV66s>9XVAED-95iLOCXJ{7`yReo$S#;v#R14A@{wY;W~1)jz9!fA|MKz zI@e7z3$5Q5c&_Snr`zDZt_ojyd&YgRI`kX{_8g>h9c7TFVKqpoYD?yy5}Mi*246gP zH0b4KJPU9&01HS?6dyJX1NT;KRP9}ljsN1m`0Mpo{_^+y=AZnNf9l6o=Y#!tb6QX$ zb+-kZ&L`b-G(j%9}rMKHM60(Yn2XLPc&(GhRMSWy`W zUesYt8JI-FQZCX{!di;9xwQ<+Tj@bf+Z)p`UASYv3NSlmEQZXqV4ZBahv_5!{4+3^ zXLDLzyn-cMsN@=wXn-M)K{zkQMXa9LSF`uMxSe$EF`3oIHCCY*Od11i5n+YdT3NmL zLg^|(8gxJ5Ilv0E*EN4D#1_~rE28nYq1qUn?qK*G%9iH>l%WvB{qpaAU*FAu!{*1P zH+hnb&+Go8$-Qa}&`U1v07<493SbVwZ$BZT-74cZsf+?`$%|GQR<+W?ccFAr;zYx*$~-Nci&k{1nh`5^k9XSQU@;cyj=J z#;^REU-{wJef>B7(Z_z^@#ceP^@C&#a1N;aQ2w7Ygry_uYDNhKR`O|Sv6%*D&mQsl z2DcP0k=bB^e4Oq9@vE1Do@2nBzMZp-rj*NKf>mc^TvbKHJV~Ky+p<9jo;Y0bk$92104y5|4Ch0d z>{RxMcR|}5CBE)VzY05keUt90oM_h?)xi@7*ev5_<2c0INiC16Sy=|T`TlA({~J`b zcaXnN zljn~4ukZP`Gr8mEKJ9EoP0|p^(acF{FzhIWZ9bBfNE6Dw3|_2n&06~jtHkHr&eK*> zis@F&u1o9VQmhQbT@F{3b|x(NXfNSdGLJrIT>)AhqGob!??g(Sg-0R9y;SQ51`jBQ zT9r+OVtmb%rlO-Fm_~0E6_1EKLPWZmbYaS7)9$wrwWAL2JR=co7-3aocM8Dc1+eCU zCpj9frHI+=1$J={=%J6GdT)mAkw=gYq|Pc^QH$ZGUjpZXCPI49lRaN|-hR+Qn-J1a zpobKF#9!VDJO&w+u9GE0BLTR1<;2;!+z$&EsFV*5=?kQ$FA%G;NqZ2vAvW**tdjUqzIb zc27Rf(n|D$Gk#hTRgP~=Qlx-&xVBy3ks$(9pn06?bbamKSlf<*k5 zlD0=jmh4G42zxnZ?I6UJ@p3Lt8Jaqk3JaF~mGx85ZFflp*7L(RAK{z6`FH=jU-K)! zdhf@sgH;dW^W>zXxRCN^=T_68<>OM=Oxb&+@`*;!W5~`E5CA+P7Ho@JUYbk5jm2H= zn0YJdHF-q?xLb^DBO^KaR5^6rUC5FS_)w)&f%WIFvsB2f zTmic!whx%mp|Q%_f0ZNzBcs?~wG^UbBPDHXY^~)k5ePpUM_NJ<>gaR>+hF?@KB9}S zPSi&xbbQq)i~_5RFxAVN0dSdokMD5#3;qeEN}7W_md*yKAHa_+F_+-&3Z^mq167T=HX zxp9cov#?tDqO87;rT7JU;z0fe)+TEtV`>RyI9wC9aecdGx~z9v6-2yid0)XwZ`!h- zoOLct0ee=2hXzV_wq@DMo518b+@V*%5(Bn81MygCtPX=Q1;*%B)Z5WOsUi>;4x#-WFE9fsaYM z;XsQUw^BCu_Ol8#Ji2CE*!8^+6ott_*HNx3i`v;#Gzg99xx$T9E}*W^$k`$b6x7Ff_meE+NlmV zsHj%WyUbyyu4gy*%dhTKfYdo!dt295c9+lc-tiPcmX}9DRol)5yiM2|!bgT1HD68) zS}o90=omRQUn*0p9Z*0L;7 zSTUkbw4>>w`w*xk;tu;WKs4~WXWQiamq7TA>| ze;_`kg151;;-(y&)^2_&o3e62t627GdSZj8kd{IEn&q#N%P1=~D+w3YN*2QJsLRtU zsH(zH)OsMWO3M^~@CM)g-T&F=e&VAKe%j}M{!hTics|gDck;Pg?tVo$tPWvdEQ`1- zEkaKeB~{rnB|HS;T6UJ->R2D0wuFe8ttJyu+clH~SzYCaZ<=@yH77)R@mDlFuABSi zFg(yx)C^K6bD}BFmCKBj!fu>mTUIog6W8IUdI=;30tqI9w z^+jH~6%x&PGLZ)oM51eMWZAAIp*68>Tsr)xI=UZl-`)c7EJaXNMK7Nt z^<)gLApGfnb0*}Xr9*}kCvXxZcSj5_*tAttsPsfn_mo*4t66X- zafX;l0gZgdi}OZ5nXJCLSUA`@9m3)Df=m_$2a&gS@y&4uO}EC=drlyY7fsT!&VqF! z7#(0uu80kp+@rR49v!D2cObJsXReoaP4Tc@7oAM!H@bKtx>S6@zaw~wJxdFO!dizx zJX4kJXQG}k#v`SpCq(WmtW`kg{h63~dzk5_Fj`fdL*zsZ~f!n@ohi!{oltUkoS?@6(LtAu=O#!tXdpYS@9?t)qa8YT+8CIr$93EHIu6i ztfk&kv0(J2y6o0q1K8zv>K(!+-8{O}h;F*KXJ4}g;STTsyU1`bxic;+ecrih7UC2$ z-)-qW20Z3Hr{NZLaB|17qtUDca%-XLV7D@<#|tmjQ8aNaCNN!NytMZ^eGCp2wEa=2 zSzOB>_{>G?}Q$%+DZtWe9QSn*v?ZwZd!V7Fr27fv$|xte#16U(&p zs+b?7x;ygV_|PG23$U}{vm4Bl8X!LAKo(ZB@7QtQyiO-QAiz+|_}R$WejRQaDuBm) zY-vM2Itv!ol!9m}m6#<{NHq%4$0qWI zgRZTu^9>_LR#maS3n>u?kS^?n&N-b+onpI!U-KF<6~PKKzt*|%+b9+rQzP{>(4`XTE~G^sG~L@URT4u-3&l zmx$S0)7G(;Nz!%la%8(VW{&RaG?i0PY00HP@P~g)hU@z%Lh5K~B69)!$G8v4FkUvO zP?cyzx{?AP_mpnRZ8^WQUYa9BA$zcDwJNrnpWtGc*bxN?STJO(AkRDU z3qUvfSPl|H5{+|y>=$=kuWHl18tC0F?TX8N4~=BiH8+qiiAW1h+46dMLay>^`(ON% z+bS5LG0E`WPhCDyd1(bTX{_sbQzcx(#)~TjeAd!eG1kjFn(5TSxsIaU|Ko{zOoh;(!d*hU!43XN#-V zTKst}>@|iy4n}0dw|tqBzj+oG)Tj&#E@!t=a#F(E5E6O|vY~4z>K^(RyREcz@)#^^ z^oQ00o-Un7$!Dg}t4NFiIoNN3z8#aB^uH}ITx?ffAb>_e&Y=1bY#H-1fJ?=p_=rPc z;$qvsU(o%e*|N%9^v_@Dt#@+y7vLUkxImo(hzBdNBI(tZyTnagdY=@*-lhhb0MT1) z%Zd*%yti_ixeM&cAV|^DSC31Qu9FOU5ioLm6JuzcxAYoso-44@yH9#890382UD91> zAi~6ID(c2om3OMy)mH-zZii_K9) zvBk&$s?Rj!%M|b0CyVc~NrDkLyLR(BEt-iYjgO0(GUvZ%1)*A)Ov z(x?HISPv4!bc~q)#kDBcrCjr$fEd7ay$nI)MS0X=z-b4m?zuG3Z}h%3k{1=+Z=H0tZYZgAI7 zD1~ED?SLRyI++ng!>pFvTx z)b!AqXV>*snKSSx*$XMbd!w0EkC&RMuCWiX75egAkMR6UB74;$)+Oy_qIFl0O^exy z@``CJR4@03Z2KYIIy6TUGPG4QBdg*PROAJ-Xhg)3dA|foDjXeO;<|t6hY{OO4W2LT zN(t@dFJY31%k~N$zk|cwqTZNCpRY&asF}5!@u|D1R8D zOLQ-z?>+dl{K?hkli+}D-^S4)SJTV2UWp&}y@FK_*?sQ~c$mVqGiVn7wOU*mV)#Ie79idPd|?cb=bz*vyDPmHBlnEUK}TnDjaJ zb&dX2zy{1S}G9MBqB+$`W)uzpyH7X93!L#g3hb~jv|0=dOs)nNl7_YXGE$I@uwPD55n@TF!K-T5e)lCm?~C94$A9rZ{cV5qd;hEQsY_EF#DHiP$bqzi z24ljbM)XF~y<8nD$b+XGb*Pk9g;lT$+kZ@8w3HU1mTj@=mRlPTgRffEw~|i z``CA$cUzKK@)2b!eYwQLE9cEy=v1KEQV2P4*=g(=!6?~ymrRU{eh{>*S5QCM>Xb3j z5-%yRrHy<1zL)CmWI5s@p!2`9pXOkQCMg1Tmi5Gz*h^5?9iebJTx~7)|02`n9CINp z-s`TFauCe;Q)S(BO@d z5fgocG1UMy00961NklLtwNvT_J$ZQ@$?e93Hwvt(<3cVYB+->5RVVO(Z>?{SnjvcorZ>Y ze*!ibq};~4Dac`1ehUqyvhszkobG}x9D*qnJZp^y{-LmWPFKc5f~*x7j)EmyP)!SK4Tk zXr*<}h3GbZIAh>Cco{F~H0(R~U$~h~C_FyuJ!0SU($ke)EF$6S^V=~ODR~-QUF@T+ z3K(A zTQ=&_A6$CNK8=3sxWBIRqA>x3M0M!zX=%C01E}O6_YSS187`idAZ1HU?v58szg1Kd z>|6f4x&-;~+rh7!tP=>P++ z)lf4#?h;EggyI7;eHZ}IcyzQkLxU|B)fgEOnUmmn4S;ppZL4squC0kqgwbE)9_FQn zfP1bH{7nC7!T)Rf+5%R}Os+sUr!y&LX#wrED^5kB6o4D8a=f!daoJ*(${)ontk#sI z1wxvHA=Yv#L-bp!;*`(oeqDw5cmjmwes5&ckk`ZWUxn;HT~>ke{~Mrh>NGul$Sp=N zue*}li7`3_YznxRBtEq8mW2cF6!O;M(c1Jcc?2No%Z~I;hRQlr;lZpBX}LhOs6- zRALZ|6~zbG3uq^;!(&oO&F#vhi5r$HZd2>ovK-Li#obfy&>07`joh`C@#kZxTPG~o zTv2{iv!dXwyjNUAQlLc_%a)_3q}Vyn;dN>{v$6sU)&d2lm32bF=bR7sTItEsgQ#87V=bjG8+Ig{yutyH zyiZjXOmn;~)>HQ1k%Q=Wq^Qm74y3sy%>JSnLEo>HuotnvwvhIC4MNC*#nUa>-qoYH z;XY2JTTVBZGiUMfmI!UUp1|pH(Fz7D^n|kVKoDztMcy}Ko2#XOH#aLx!rne6NBqiW z0N^%Nm)~=|roR@GgIv+CGzbC(9N1EEk-v8p`ckelMoo5!_gZVA^0(D0Kf8_Z3w>tdsX&F?PYATqLs}X0Ry>MK^y#f*ujGHy6)mZCJa|3IsDPe<3K&|$o_!qZYJS_yP zkQB7mKpYM?w_I}TLgRvZ8c^A}>}*K}(N8?yzQrH?W8eC-f671dg+KbkPki#(wY56< znBg{_H6nO}`ji&OtEJ(k??atOk?{lE{KmUy|@7BMZ2j5|;l8 zFLY1Bqb5GA`J=L1XPov+l~mQkxF}<4O6X$1oo)ApcuQ<6=3w6W8C7d)NfUH5t0{fI zCF%fFPre`5rX~&TXzYT}g`xR%S5w<}<^ zAeETd{!3L(9-z`t2HZ(0ad9P4)97tqj$~U)V_PzUEZ<~2vuuQ;)FnyY9?0zx5;t(K zyslZT8JMvskV2@0p4N+T4W}#F)oOmt_2^`#Nq=#%tjPrW%ib1vBB+AHD!4JTv&nsX zM|IJDEs5c(Ug|dLEt<91kzNu=Cf+e7tpj5@Zwz z+6;om5eY>jG=n&$l2loVMyv8aRjE`Zl`?-~#Y$BYw5I{5j*-fK^ z34H%xlH4vFVvtGXPRs|J`goS5mBnSvxcVdHhKyQO94FwmC(45QE`$e6!W_k&6(rlv94W8_oXkAk5R9@<{ zRHb&vrJfC<$rPB35tEMU5K?CJu-wL;1`RvL{Hp`cEngqcu_q+8q5KC~cQErJ%{8~< zObM}-VS>8J#*qVFa*A0~`$7wWrXy!%;9I6A3dkciA)e*O{d}Ga_h{JyFfSRKvPld$ zr3bL`!P;ex8-|jetfCTsbaX&N0LUx;nrNae2q};EDAc*oD#4OG=$tA4eE7$wZ(?5O z<6r;%zxSW`+y0Kf<8r^;qg;*tx48F97S6!YSMo&w*@C%snS;pxdo+V8fPk-G=gkrpH#}H z(ON+4p`2)*cC`{(_W&`_BFnOxr6<}3elp-Brc&Oe$poqh@Ya+@NL6JX7$vqA6?Enq zkR%q-A6HZikK&7g(gWr}O>VyE(S0FBQH^j{Rqb`nGE#~0zEng53x2<(fFnON%G3B~ z`03hT-#G@#)K3`UVthZReYik7+rH9Xi`3w4PZyZ58eI!(9k>GK zy|S;OH1;Bb>C56`3EGlWXJS8`e0J#qy;AMcT>SU8Kb@8XQl0T}Z;8m9_d82l@sGVm zq;Z5au-{fUBh-G~bm`-a05XBPFSQ}kWnY%3wo&njREs4y3E-dmqVlfA8J?!SySXV1wTXW|;%x!BL1*9P?@nu~DiQbqjZ zt-J$NJl**GF@?{s*Czl!OybwKzVBCm@27w0AN=3^z_-5j!}$1kav1S#AZ1VmON#Fj z`^MGE>v^~B#muy`m{GdbAw+!MQrU8|I@1YAZz{lLis$%h^ z5iw3`k~b4Bwl6OKHQ8Bi$=(I)oooHCGABx)b!G)5@Jv^n0+>V_bCoJMCfa7w_lywK zWKZUDP9LigGCsAu5npkvMyY|EG}M{6B6&qvcMULbVUZp6GY@p1#j4w<3Uwq8I@gp~%FY=CayEXBb9?+?C2PiKu*54cB{h|MTa0GLN&ZT4 z(y=EipwKa3RC%V~(EwXt@;p9PIUffo34~P+R9FN~O+<1?cJ75RRQb^icx;kgN}dXV zB5+_d&p)Fb9rH~%C;&PZUD{x(%l5c$k0YtuS@wR;Egy9pD@vg5+S< zoi4bujQ(31z*i+V_#nJiZ)-8AAf@h-Ez>DNE;>ljxcmk!Z_LU>(+zyj=Xwg9`8IN^ z8$HW^Wo1*gZ(b#ws;YAO(XP-~(*Rg7z^NnhofLAZPn0;DYkp$fc5DnXHpAuZ);~6e zRMNW7R7lx75lRKIwTf`0AJ|p4qHwHCj*XRt7ZzgjXPv^R#Hee~FV6y9VV>f(mm`H| z+P0B(Bhxljd(2=7prXaBgMeDe7)-ILNLjw@K?@CcbBjVHpm*g<7BK6Zo^rK7n2{$X zy7_-)CiGar?)-KUYE?cwjh$xDcs_kq{XBT;1O4&zr%zwutA6l}Z+?y6^DqC4|E*v8 z3xCOv{p!>6qn??s@bNrsvCCT(GZ0*V%G?jxN6-EurH(=M1~fIRdTKnqb^43c?5SuI z0LbVx<0w)vW44K$!zLxhM;1dA_U9!#Ew&u?66qaToY-(FF%MVx=uBoAFDn#6_WW zODwZkOs*;CSVSKiF=0XFfgf@w{b<%OOC=eM}A)U91OrcoTzABlu-0i9bR=K!yF&t@hU;7N46ufD$V{#S2& zuj$(C^EtYE4H_TAlg9rzC(bPF_Xl(AxV(%#?iW+-%kuc#&_>v%qCwwQzD5i!9m{a$}}qhjy4eLYoFI@VlblzLK|us)=oArU4L@8*7PJV$D= zdhx>Om0@>Uaqu=BFcz@khAKOtMfUY8TF8P$+=a2Axj$aJGj8M-Ct<2@-##kMW z9^m(-gclO-}@ufftUnl6=Go!^U%-JT@tP0WJO}hfDd<(2#O(#>y%3fEHqDr|u&+nKO zAls$~5|3m&dr;^j;0ts{&m7Fl!T#zs84|n%(Z&$)x3H(^SySb{7*a5&2Dx`>cVS8u z)!6Ta5A=Q}E3O>pYQwSkIMXn%S}&u90i@lbT9amwfXO`}etrF1_HGQ|%)M?uVKpFMUfp(Qj zrjI3sf+;Ps*A8uC4)&6yo3AwwWs(<#Yt`!z(WF4WL_aA%qc`@pAc1U+**FYdftN&v zRPo&uCjzUG;GQHZxNiX;nLhKeZY6rMH^Za_Zt`GNs8_l^@tJC(fxS&futZ#Kr5-^L z!<=HO<$&vCRSDnIkYPaS4twR=Mrp4nq1C9Q(hXp&ic6nmH70{AnVA6!FE+7ztgJ6U z3c&PwyUvr8#(-$94xBUdn=ZeZd?1iwJ%|(jni(7+^1S`l0v-TYzjW5kMP>Y6DY85x z%ge@zTTm(^I|p<-Daa1&qmb=Szzq;%W-OSsjr`zIwdZ_% z;PYSjj34;H-}}G%b-(s+DpnKCi+2$Tf6hxR3OXbhOmEa{U2RFQy*O7mKv%{sjTZup z!xk&#jcz8&Pv3y}lzfUvV8HHK1!yIZ8#qyEQjq34zh-i}X0g#^QsTHXia%;7qA(sC z6JEm#g9E(bZh^6~#XU}E!nsy=gaUkddywyBcxb-2UfA!@z@t2879LBA!E99cPa(DZg^AT z)2Z6=K1-jCDbi?B2}4HbkpykEZddKgOn-V$ANZ)a;Mpd~~d^#mlOwf@MGHxtd$A)b;)xtu!SSZhAWF;}V5z#Udw znd?XUkCzYwX!^)-sQ<(knVIc*E}qFW>WS=T(uL*s0)q%%rM`?+<<=xzZfW|^4<9AI z6+fgCy`_D}CeEB~vIkI}+S=!(q$T%spo$d%M=k-yd3Jp<@y8wm;MhXyv_Jr?Mlqy- zsDge1qCQpOjv=Gu5>(Hf1W}JS^Z*47a|qjN+F_LvLrL;WJXsMUa6s@nXzPo*UoT% zGlLIZGoc}xJfHLxN|);y3v&4XEHDmIiawcy3*bB+DULaznZ^o-KIL%g+FBL&f|r!J z!Hxo^MQ2Fy3ruUPjcFjwRDgCj&k7m2R;7sWOr8V1_oxR1d8#QV(UOzkAN6A65evPL z^C~t{X&VR0OR5;!)p{jIgvM=jsg6KHMV<^6cHYgiM7eUPQ0FN;U2G3NQ}f`P-~0yt z>EHTW{<}Z#=l{H~>v^VTX50`0tSYZ3z^A>18JDgmV>&M2aigymEiF|k9F z0muKTN1Kd<FfnYB_m>Ngb@Aa2)t>=8&=RZf*Ol$4tkgOgzpxX_B6%Uq|3X}-7+#lREU%c(-W zlzh7sE2j|2`Nwb#hND{Vrh~>q82gz(>ctu;?6VC8IlxHp1<-#TYGTJVU5){se3cSH z$evOx(R~Wg)m)-Va^8K_;$`voI!aQZHpn%(+PhX?o;g`spCMa4L%+^~JmPpKqHMCF zips#iGX9*SDg(e749S$v=Rx7YqsEMw0bY!W45)2_2-(Nc^W4GN2FzEy7cwJw)do+R zmA27t1E84hsahp+>cJ@EeX7~1$-mG421dDZeYULfbpCzds|(c!m_ykqHP7}C!x{weXNl6S6>KBFYDlf1e{U)Eq_>k`?m zBt+NaXa`%5g%E>Da|lG$P1(hxi{erbB2Cnd{=Fs4*4f>k%y3y~t9R)DXI^~9i0Mmr zz3(z8CoD$Q3Q(5VF3F)OVF^b?$fIP+s#_)o1Pa&~q%h}E&;meH4Se^pW)$q42%$R= z1prYr@=*XQO_gmDows!qVEaQ3(;zY-e`a|kS7)d)`ygru(Uge{XfW$|$$%UP;KN0?p4u*D z;+nX!1V|xm_6i@J*6$?Qm*;GERt20@`Pz=_{V2->ny`eoXqu!i@nu z6BrdV_}Tzug@=cu;XO+l>8}WFt&?UyMK`)q)93)*rx5G}rq6QNp%+FDKX^YsW99r& z4yp5G0F~j?rS-i(qvGMz7sukmnVTk4Ph(EwZ~t5W){o!zJAeCcnJLUyh3EN7Hde>> zG`i%A!c=R;*|QCQ9|g8PmNfXYRZzmG=)ZMy1l%}~Ea1N5|LE&%%2)Vky|>`(AH9n< zzPXudh}nFKRge4uCzZSo`-!U%8DJ@~BrO>C?EnD#agA7N`k|fXE zRB|Tk+w1@ODsrkRttLc^BMsvOnNKMbkuuzpeqQ$nP^CuH>~G~O#7@t4sX|j1$y@#* z8B>|_VupV%NT12EzD6@+&)Jn1-Ed#B5M_8#!q;b8;#%N2ar&|?j&}L@?*-s>^&=w? zuooD;Y8Y3ZhV}@ z_vndX={@FcG~m%)5F=YA(b`j>ns-m0gaRr^=lLoPCdNf89dh!_C9AC$8J++H57SN0 zF3t-QvAyVd*vwGyKO@cmqa1FkLXX3Op1){7-d_DsqUV4xAm$H+W#L#qjNm#(| z#lP^nT2%ne|7l(jpZz*uwp~3dYZM9;s#yv~OoT$WbmKnOEK$jBou3BH3Y2Og#P%&~ zkg}k=9F3?j&+!;tcEZsHZkz}c25XnhfRYk&$M^b#0>@Lfp@5>stD*&SG-C5vMR%^m zQtettC0bP1{F!kuUZof*6*)sqGc<4CiStRx3(`!EIK}d)M+kTR>B0{mSM`2%3dB|Z z$>01>{q29vU-`?v`SsV|_ARKI35Lm%XtSj1Fern zO6&ef!nC;9wO(1nyszc53-_Zg=s>;;u;sDYPu^cq9ukBA@?t=K;glN;ui~9S8t{1~ z6)wp?SD-lnrrBKV4FEZ?y~6wd#Eq`uQeB)6nA@F7i7zmRgY1_3Fm{&9a9C8%ot9WP zS_WY5^;sYpht=)UVB9ie(C{#;_~r=#WCt3fJdew6Zc6Ah_qYn<295=sN*XxY*`K{Q zH+$0KkM~n+F|N=vvvil+sOik@E z7dE3h#*qTdR<~|G$jZFy3OgQNON3ZwX@E>Yk|2F*Ri3X;Y~J?mzuw%o*P8Q+HDmYZ zF{VirV#}u({Re*HH$HuWMmnKC3Lo>JTsdkcQBY5MC;})S zI#4Q1Z~N9^W^7=o-T2^~ut=7+Puv{O_^XkB7ppR75mxvWH0 zL*lIa&9DdJd5;WQi8puU5D+Bba-c-0HTYuo;wy`pd)o{xEi+mLrBno~ zEI)ADL@T^x=<5+Jq_)JEJIK5xS1QkUbw@GdKHWOJ2ksfx4!kYgLTw37u(|}JrgF;q zXAWCEWaxOzZT%oYej>b{4aypl*>W7gvBt&tLxBxl9FJG~Y-hLw(aXoj{J{v+6R&@=mnxqvkR> zL-Vfu=z-5XF^ko20E(^$6JW_8nRv|^27C)2A0Ovic%Fab_x;fS{#X5~Uj+=FBQ~en zJ1+(Iw2IhVwbZ!5=hzFD=K5m7jJ(0PUn~}N{l&OjxgUCGMzx0PuiL%fhvpl5xBDWt zn}UxEIR;EJgGg*s`Te3`j9{pUbp%6cp5ST#FES5md~Xgju2q3ep;qs;Y5hg!-Yku` z+WU9IK2nk~;c{^@QgFsjBM-<^-h{v!MfJf!@b%<$O(f%~QfDU+N;xYkMa$>Qo4@)m zjeu`irvosR49HK|Gbx~6Tk_}{yEz*Sm6JGO1L4<=XaTZ~*w)3#j+E#6YUn%bs~8M8 z%N|$M#KQ~#RdctlRT4`4v2QRCF(^$6vuX905O#;nhcOg+CXs==+veDs!Jvgz;4;6~ z4?6?URjYtL7bSgS7e1U+oPGlONPOto#^($rtdQr6!5n0N~tqQ;`RQJboA~Eu*-K3sH+pgmlrL{ z>nl1%x2}*XTThx(D*DKvHwMq<9`rL#OKK!( z;~J3W@14ZqKdtY`&(^v}*O)Q%KdtT4$a_Apvxmj%HW*bopDO#oB8+^tOz@Jwn;=?M zrdvuReAC)o!hZ(3zAArjtmrF3Lq*;3SCg$TUR4>zKYjM9uYE~Fp;H92XmtmrIK{zn z_)@}p#P!?s#QSsBKmq>%=>aF8V=RDBBC&K823~uCafqt9q0x`M{Iv$wJkd&+(M-7; zs*%cZp$0t4yU&`T6Kz(S;$|R) z=iBbrTrHr^)<7Q`w0mxGb3!a$zEGjym>Nvn(KZ4TJ>`|Me7oUbX>h^IKuWBSiEQje z@3GZZj!)~c4k(~bp3A}m{>*FP9gfnVYXxGuUf>d{=pDO6<5g$@Rk$L}YxVryQDzzd z!ITmM>Mpds5SOCw9q~?Au;X3|N7pIfV9efz!T#vsZ=i%k!j$~TY4GBc#i zrhs`iF+#zjaq1`=YLAifF5g?QH>#Y$kqgU`wCy`#kJUyD!-@~e#zq_)u{`j-_RR=!jPMMtaylOh5Sy>V$rJPx|!|^0_jL1s0`Brfe-2{MV ztV9toBnYsnZ^c?HDRP2r$^~RPU+D_B64szf5n*Z7_ z`}B>kzHMGLy>+jHA_~U;w|fPoK&MdZ14H*C8GU6{hOChS4N+UCXtik@m)Oeev5xer zsLroQoJVX>06bmAex6m^dVTztYi2z)N#9RW4dYT8j&Q~Ja|-;U0#Qw821YFhRjhjm zn6N>e|C~)BM2nlWyWAOTV+g`UxRCw43^_<+Ni=xdQ2M{ih_19=g$9L@S5SF$1l`-c zdnDxh+87@!5j1fxZM@dHBq);Zk@|#8G#n#{Q-dsETgSl9WD!7sAF{J!aa2CVL+QMK zJ;MYOgDr-0pkSH1(vZtX_VRN^*`NMv@&&$hr#^3t2oxsMTUWE#>H$b#Q@&QYCnVlW zGh+UzDQ=i8DlpC4^y52f<_(ZLHRS{$!cfksNs;GIfG;{#sMB(r#%M!6bDX>L{5`sKa7ne6GQ3hI)r77s^%4Vy3)pn}mF#%O-QaqTyQZkek%@o0KtNKZ|0SQPsp;l4 zc0ADNyYeY@_n%jdWws}t2cm(J6r?EIQzQ@qR3Ds&fX1sY9;QBcgOBJh6)XL&j_~H{ zGYUW(3klLILbRw8H^T)x$>pkHCs145=U5;UrEC?$2gPU7eyciSG#SLl!GEGLWs1j|pg22oPx^nhwl=(xA&I_nUiR3;jP_i}_%8ES4a2zGA??mQdm0TtUy53UKd&XlB&%Q2qfUhx zh2`M#^rD{iwvLuPMv(k^v&P>}W<>&CAoDBKr2I_}{Nrgck}Wcue->xxs$1e*)$1 zICV(=e2w`tl$IRu{4b@_=mh%fhZJnW>-ee%*K*`O-l+hY9_mxgxdlo-T;bSF5q~yz z@8m-@(31?bTJDr7O6XN@+KCdnJFz+6q0riqF@jk-MZe$sM!UR9ya}gIR~<}m_%f=v zDfRt88DrgLybg4aO{*6WaO;T*X^CW4vLM2ulc$M*&?_aNZ3<%}M*rpQ#OvedE~ghx zUI37fcD?=!*7Va__)-$L`y|^L*3FOj=~ll;2X|h6=}lkFz9%lkAg1KHo|Awm$iuFb z|GFeS-&rw${j|VTB~+@=I4zCm5`A22t8Bh**IDg9ct3K0no%Ss8d?c_YsOzWigh!S~#@w7UE?J(fW`EIi}6{aZ1S!Zy>XZ{dR-XOb-;JJDPJ0?XEM*}eBSi!_!sW}O-Fx>NN})54bFW2=*H(? z|9#(k{*iy|H~+)0^BG@%^$pZ(D;xn}96Dgjzv^VAo4xY}?2jU2~5(iruH_dW_InhnRRSz(;fgI+r z$I%3%@^x(~Zr4d7bKS!V;sW-ptIJPw1|NwJY-|8vUX?iT*!mBp5KqD5x-+yUR;W-t zcu|=XWH?7naun19>q9}ov=S&YsbUfMIQkIu0=#aw4^5YPOVq@DQGN%17ERV(VF5O?X#9UgF{jlnJ*xT9{=pHnc&)$W{Dpsw)H&Ci{r=)zRql1-ax&f}>D|c$b6-06x2@=z$ z>sap=V1&!%D&%B1%x?Kt7OXNNXpeTJSZB|Ihwnxze7Cc?IF&RfgU2?Z-$%nu5rm1` zW}D426K-QArsid0QbElKsF|>{YQkyxKHeouu?d|}F zBQ>YMQJLeiI$tl8G1m)hT*Gf_`uLV?Z6n%NF8qWWD!RS7Kki+IK3Ju(&v5UnN%C*@ zDZuoDe(>qzYkdFj`kjBz&;4mX>lPLf)U%2Kg?#k?<0lN6!2bf&7-PWdhjcXv< zzsx4ea1-Zj9>nk5j$C*6wN7LZPA2HGKEd z_mlf8#9NU5m46n=0Ty2NzujIn=YRbjbN^17M_m3kzSq3PKI>2h-hR9~`WzmO+#9Gx zw?J~9KvnayFtJ|8ZlhP|V)5>0Wbb??m&wZ6uE*_dOti1r*Dj0C={lD;$}RDR)e3;s zW1Xt>uJ+ofTGN`G^hxugJU6ML04P4GJqD`GGV+^Ve1+>>Ed*oFd(V(-nW*30U+A?* zU)T36;uV{)seo&*#y5F>=ZmLPKAeMu!iyBHmG|$I$-(jYdtruk*58&fH+teAKB87? z9~9V1Nb5Lc@~l%r<9i!T$0l{-y^cn2S}j}9NA#e(pcY(X8FugR8P`{1ix&3`-<#D{ z_KY!?Utz?7p6zoZ4ZUPSqR});vG_E09Oh<>I*R9PX9ThNS>i40#X2AT@xy24?cL%afSA!ygB`^P+K?X@{m`vF`G^mHCyg4 zT0aURea+P~F*6+rgPIL2=aiC^uMOUYXHF|5(UldOI%x{%0NL93p0YI!_D49Yr8m&k zpToWiJDgi*7EPi4Tq+>)I_)3F$Bf@R0XI-C&vBBDbZ@*=m5uniRti#zIP;R|2>w(o z#-`Y$$I1D6u{1GlaK}KAE*JF-GW;a}5X)kK+t@EijVLrUh0-!w7Qst*@Ib$iX z6gJPO$H6=4-NS3tHm7xXt!4(86X}*BR7{M%#z@Jm48bPSRPT};9DT+~cG8#C;G^4U z6j7rw7QWj!C>U|qPB*oS%6s|XUCdzL!S!;sz6XPsgd4idXtl{V_K}b~h?VOxgOsqx zZny5e&)6D)py#A4%Di0Jk!`M{3#(>ud_>toHeQfGs_(QrqXM2eFV0Euto!`kVeA?= z_d~!D+du}M&JB?IGe+`+6*e$OO&DV$_3p^;+SCyd>~>~UNL}zdZNo+w0|uT(EWwL% z4rZN+kVh(0R3lF$Spl)GYqp8{T2-S1JU75fIn@6D(|zD6BP#ihQ&fxaUX@`&sF z99a9bYoNxHi}bqOi0F7H%1bi0&Q`rp8sYAq0iUpUhb*Xdd&jkCFXS=3jqGcG+jd4E zjsOW7l}9O$iNd8qSHBJQM$qFDLMe$)-~4?)^gsP|zv0)=A*Euwx)_dcQJB#qyfuP9203X+R|6-AOWco|3in`M<0>#$tl?Ctx>)GTib5 zEDc;Q>3Pmq=w!DTy^sD^~J(HWQoTIsIfEh*5_oR9#zA#Yrvif$}NKEYll_Wh*Qb>d(Raz;N zuZb{4BGcdAu>qqRNs18xFLwB|;#?4n&mU=~Tf?N~QqgBuRqo(3zT@s8LzP#lIcTl5 z$)YZ@MmOBV)h3cw@uAs(*OhQJnJ4mL%Qgxn6ON!3^_#$mI{2I;k!8DB7jFdWu>nS< zmaP0bfR<9REtgUk?+s!^M+Ptvc^)$by6VTw;X)_j6!8rrrYkrYbf;FbW?B-|YJMfX zgQ@y*aBoz7VufRLa?mqx@^_!u>;cPPcwmX9B0qh@9QE|CJ_I$?j|Hxm@R-hyy+=S9WMdLK)uGmA=Fw3@V88AUwZ?;TcvBBzK^I712;T&vdp1 zY+kY5#bs(FIU9HTXTYOibkuX;U4$*f_AlwM*}dc4{Puf9Wb@SsrY z$+H2WU9SZP<`~Q<^($244dp~K>S79AFSsd+=e3oEg(r{r6Qa?P=yeuh{mvTk$tUgyi=! zjzh+1e6LN_6Gp>@gqt_ho||Pz(qoM0COkUfH7W?+)4yl=dI#hmDG}TGuF$({G+_Xg z<=p*8{wx`aZ+zP~@%w-OANZ^P;-CDJ|DqrLW4`Omycp?pD@ZV(j-A_e#}=2gUAD_} zoJ2anQ}Y0w>*X%HCxNMx4_JI!d!70#)m>7!`~oG@rKV;w`p`dRhOv z0^$v+ug%|Us~~h#q4}l5-GTb`xx-FCh24gQQz_iBUkL?4WgnrP*a^S$T{0GH=`X>xdwmskHhvxK{0xtOY?78vOAH&oO9Qyb(}Sw)_JTq)b`cVVXi-ho z?>GiBo?Jy?M`8GxlyKD2k^83eQr5UJGF16z0-AZ3NVgPRVh^i^E0b>*{r1kq_-T`dY3}uDwX(O@`q1y_sPG?ttaBADlV?4~qP?Gjfun+Jc5|Frw@}x`-CA?; z=h)hOkDd#K()qS*TBI&9%i`# z2N1Tj0PvzOE=+@5|ENv%(fcPy!Uo{TJ#T;pnr@yZF`clM7{-L%_+$~@F^oKj#wa*5B^roz`IcZ|IoJ~A<(i#JxE=lM z4nEW-R}+g5Y;e+Ij*;Nh03UkKfdX1(Ngr4zym6>8N~8Id-rT5lx0( z1)F{_7(O^~r~MBrIxqlsJ*z_<)7;*5?j+fZI76Tyl?K4LNR%Zr;;WKD9oLga36-V^ zux*h6pmp;DTI)Nb{Q`pz1@|ti3o;HH`C?Kcewo7~_0_G24CQT-^v@yT% zBNX3!E$u`uykf0`tyiYj#pOHu^!zKrzEYBnZdY|v%^|%%SNG?WlJlOuovEpEyZ*j{ zsmd9cvlyPCtRn%)wL)BhUEul_z7;yAJ7A6X=5%?{cFV5ikN3y*UrCLVc=SFBXs?A) z)g6-C?v|?^WD!% z_4@Ec-^;UG>Z5>iPJ8zGRs_on?Di^6?Ra>rZX*2eL>u@+#rNE*_6ortDGlfNRPb5* z45gsPrTiB}SaS#4Z&!m5Lw8rY%3C1)Cc8z9_>cHUTyfQHM%D56!DpA~xq|JNviAMF z5pJK(Fid9Nd{%l1LA8(%cP+pw75=YdoZsQgXdF@{?0cQqg^wKU+y+3vmMYR}SifHX`%|)Opjw)($ zZStH=`rjD^-U23FS_&x{W7FyoONxTks~vb6=IB}}327N*(6{SXybb#Q3!Ds@1Ji>? zNt*`PyFpE%-57ifMT;5SfZDk11Bix-mmK7mlZdX4rHbS!2nU}>B(#6KDx`{i5i;@* zR?;?iAd%18xiPR%sD){=tbVUf@6FrH|;%#6Jin4a0=R^)vD zs_;hTyb5+g>T&-&@p26|PXH-tf@|eKQ@6Z0!9bP1Npw%V7Z}8-u`mdSmFL0cJO~>KF=fO6JD=L=)Gyuj(ppgwIm81&O z!QR=hZkDHA4w@gkf;xuydm|mVcCONqhtGJ*ehHQyFY2u5lU|j&LN?s;j>O*6M+a_}WtDYU8;S=5R)(+oiKt zK%tE)&nUP?`Vma6fwA455Ao9%Grb7jniyJ__GP3^sz5wZ;X}u97W}$+N92+G@I|GT zNnJz>JtBo|+&nsxU`F-eU`Oru{?m=OTB>A$DPLB&Ia>}3@fR=n}>~-Id>gjKM%pdxL|M#EylYiWg`zhb` zlYYwU^*Rlp9~ok$kxJqBu_@p|+HZ#W(YHdZgK=6arMCz|QenxTb%!XfXK2SyEDH9j zr@m;FC#H&X4P0u2PkjR$AeAAO5ss#iD_jlSouq9Bm7EgmV?~zDrO6^2>Yn8 z*@7|s+F#{SumuW@mSYTV@GOm1X%Fh4KXIF+!1989ivKcuu-ALB8eL0X{*8q3pdDra z0^TRjK2(=4eHkcMtrEhNEMZyu6LgE)A;nAc3QYkXFJf6QG|G({u8jCl7kn~~t zvddvzSO7bO8&aQKhM%Pr+EC(r$s+-2uz-k{wwBDCOwKBQ8yM6MJ2llzoz5LKh3*GR& z{XKsf26ZIYMv_d1xrobO_&Xq^Y%o#;GQW$d=#af?KKf${^ZE%t{wIC@_8;~2f8&q- zk$?X?zWMdn3K+1Xw}l=mx69WkKuc20{#rp|r_8eg{~7ru$H@%yh)!Y@czODOSh6iq zWj*4PET{w+zN@X{%jex0NUnzrR;}g?8?$0D|DdOmq3{{=IxJ0`$H7fKdnN$msOrSHM~2@{ zU0seq*CriF5z-+3A&>wVr?#u=YnqK0U90slRC<|?>ZgQ-XE^$_o@#G8QPugI$W@TvI6?uB)- z_G1$Au4f@t;0X8S{*-aR(0ug$+>I*5Lose@Qh6kA&Fdd9AfUuHW)?{W2!@@3t4$Iz zSFP5CoAB!yWqKSX@a!JDtVA|GSfvif_r0AZErA3H#x)j=T^V%;LDq>u;owJr(6%;J z7!~nsB1@$r3KTQiZaUtHcMjkJpVp>Qf6?5x5HYN}{)%8LCG1{boIAz~94uk_^gH3Js@lxJGpS6aA zM>UdP#afe9e!^t5?+ z&xOH%tCFMh{z|t>WGyK7B6xImTl6=&zu0wvZ+#2@%y0i4|Je8a`d>dibDo~h102o; zUKJaiAfqExn$lxudGJzM>R2VVH%*eiPPqpioRHL%ys80S;w~G`LO#FOmaL&0(fxWZa;eDa( z%7*jTtDWn12tvk-%j&!VKZxxO-xqgGX)W!vYBYHysqY+-3Wx>+rKjx^|RkX%^-?+;=PeliL!OUfDp?La$O+urX-~YgVZ}F2&{8-IFQEi-5v# z(EIA(SW~@ca>x8~9ymBwrtw8Q2y?k7wck13Uv?MXRqz(h*}d)xLq2mKcXQSovdl1P zP_A9eE?;Bc)0+-VUO>&6z8pSsgp-#w_7-6j;~MOiFYJT)3wH0sV&ElAi9Fxxj=|G^ z$%M%d3oOht_C*c<$Ho65(44vhJj(l#UIGWhmKI|TAnA7}%P=YHiHLCr~ z2u$%}f@f>#2`|j>R-^Kt#9Y&vGJcah&Zz%4qKZyZYqiUeWL0t>VJpeEy$q<(N{FX2 z2BWLM14cC{f$}$qDC+7W$zqvr{~qMVw`&irO|?gybVSz=l1rm~$Ou6nAVWBAWMpn- zg0%{VtApZ&ZPaWUDlgZ~CH0fhqpva&?oVkBJ3Z8nqqWvLY&6-nN>3MF^TP+Pn#TEC zf5Wf){7?SofBJv)@ihARB-q2*D69KcV#Iz6X_e*`^2eEYa?nR3z&|@aGUnlThLwTS z2YovJ*x0wo90=|}UJ1d6^8uGa(nz{{dFeDf(xO$5K^BT|nkV%4N5OfIs#}W_!Frlg zyowR_D*)|vUFiyb;c=5$J zf^b~xqyL#MA5z2urRLIzY4Baw#4iXKE0D!EoqZRXA<`c-PlRTah4-_W)ypi%){2qkx)BCY0`~#jh(xTM{zIo!!6)ZM}^; z50cjna&g$%`A47!v|lewvK#kg_Q2=y0OF-R%V&JQhrR{(+Trll+}N5XmZc7@(G~4` z=Z}=n1nnXNKMMj?<(w4-cY5pI-H}MC#!5w`lx)pR^p9uCT)1 z69$FH=s)OAt%fIyH1D|4Kh6ycx~4UhHGsa7Mzoro5r@d{>pw z2OeA3#BL#Mj~fcKhoiN`z}mVU?Q0vXLt~UOI$XO*p~e)TFV5gjrVyoFv}^b~VS4j2*qCRXAh@(tFT^ z8~t0hg{Ak7&IQ^n5`CiejAbjXeAVmo3ph`GbUk$j&rkiS|K(4A;V=BT|NejS|M}0* z9}V;q=bl!iVb+M$xL~{rTkOibEQAK3Z?#E z)%A8QnBS-WBH&LdpMk%FK9Fp(%mOAL+7#k{Q*dgz*&3_FrPQYKr?LO?HB&QPF@Q@U zW)~Ag`N5s&)7lcScK`yp1aKO`;#OC@RnmHBRXlG1qQ{r$U!qgt5y+f4hC&WcLHOvc z7@ZN#eP6mW?T;awbj5mo(X&^tJ)BOY5gt`bni%^a2DX zIW{3(_}$iIHYX2>0gK-RAN!20gJ^O4(#Oi(XbdF#JXOlZ_^L-rcub3R5(*x%&(YmAgNh_);VS23oEZ^$J&KUFZAObHHE>b}tm5jo|p#k!$Y#0Ouv4 zw2(xAR>+jpZ9c>BB;R7ali$*Z)cfv*AZ_iPHHpb0aB$F9GHJ1*g%&UlQg zJa3Qf(A~5{e2Qf;he)#&7QM4$%P#v5jAqDVh>SW7>(Vhd%klbLx!_r$nNh3X*Ha3u zDUcp3!V!a1LalZAJ3&B>*C=Cv$-GY%y&m|kj&vIcT|wIDCApbY{J8aQ0jP5V6165p zH9QjJ3UeHr2xW$WwsY8z0qc_Zd@Va|r6=Vu`Ap@S2W`wr6fo|Kq^m$>AGUyx0?xef zyZ*U<@$dVUzw%dn-PNb6UXL4Y^Oc|Ity#)#*~+?1o|U;?JceS08R2;6hF9tvt2@H~ z6n2$qNkF!}ydE&v=hPBQ-`+Q@nz4}ZkIN4yqtE+CV)Fbh>uXh$PWV7uP(rpweb}6| z$$~crT}TldpQ(2VB+yc>4`}a~FL8E@@BRYd>+QS0o9^b6X~+;D`HmuIT+hA;7nM*b zhq-$BIC zU=*-y++nshG$&oc93iniTuW-p=yjZLXb0@mDYOUuLxTofhtF)Zy&&yh=rfqYVxZB)MvT*vhVp|gu7SKgnI8^jtUuM0IEWR@O!I>M>Q$N1{G zJ!OB4qAB0CW*1)b1q*^9SyKmFIMb7?p2E}kRQ{a5SO4(;@gM*DOH-gs zc<8KM`Nu1j$4U-TI`9=9;3%_d0AW}aFa=-1Ja5mYpW~znXH=X7y?~Xv)z-n(3*Cdq z$rt^TBu~8$aT#5kP0v=434e{ zoH31uFV_B}1UxjR1Ud`|78!24s)AW@t{X?1hmX)vy)!*JDw~W}_Z8d0E(<6dWu3u@{-clWnwhM-kb%M* z_>%s-$5p%b_5`||&(i#nFcS-HWrKq`h`3{*cxk$0Yuj6c^82pC8L^;ZflI zMwzm0T!Gf@O2>`$w$Jhc{Igv{`~8%)C@G|Udo8Lv#st>OrAXj zpYm?)BDH^c2f2<(9W~WKO{ZF6N(Egh1`$ZJOl8)(^3+MR(L1y!MV=mY*Y&vql#+t? z5obh0U+L@TDaKB)bKFf(~<)*4m?MP~RIIJ@ueoLA4 zn;g|8ATQ;!>>2Rj3Xo-YBGzkM{zC}~oK-n6+nSv%d#j6bj8^b{{n#y$`0tBZ7&H21 zX0@{0n=@kU7Xuz98$i?1mQFW|L*fHPk639Wd)Pn@`1JytR6<4=VVI- z=U4ruvY4-^>!YunU%29ke{B@ZCDzI2&(hMvgznw;NtdqKlm~|Gt(F|G$R^Wt?ork|SuMem z_7R{V!tV0M9G)j<$7Ylck@ibsuFMNrUlkcsz-Nm2IfwDn_}LeDX~iOd>4M;SSFPt= zL#bu&(oS>|aO2M$A`>)-s#xynTpiv_{$Nh0o4)}Ud#+=r69u;KNX2%L^^RDz# zd|_xZq8Q3nxX=DfVNK3BDpifH(j3!`07k0oFm7rm3bQnspwWU=`xvCA zKnoWkk!#~hJ~EAXsE9TWI93iqr$Qk=Q>yX7D^H;S^LZ&j0g(}3MBCu7;A>lIIRrzp zah&yTO)DiMceV^v1FwU!|7}4XDWdt2^1-%H$1;aL_P=#sJ^`m0f9dc<$fbTn!aC_4#B|k0? zGdc%;&S6IMk@iTTR`$3ayvkt3(xqut*bGA^dmE=n(3;+dFKFcHtzHKD?UA&wnTxuS z&gMxthm;w*ol&nSjQlBIhF7Xxd@Qfk2q$^y znf^G_=;x~{bmQRQ)7M|)`@jGDf9>~v@2~u}r`vTM$L7`wKqn}dO`Eea8V|IvAl1ce zFg&fciqVm+rGiOhORvAT(&0KyF?U+z>$AFS6VA5)!rEWc>e~1JM0sjN9c_F9gkH)1 zz!EAe!+>7p-@DI6n@%0<_KEH90^ik(6PAorRyp$lW>Tv)r{_zCd#>lSjp%q%Yt;5! zAq+L{R^O@whtEjBvK)%bTL>oN&_eAJQMf`j@4@zgS}B*i^vqh?R1SP5uSw>}Cl+mo zvBceLgMve12jPZYl+Q2C;1YY=!`uvcX>DTax98-1%x~J%a}MHweG2gwYyBOdeH26jwS+imj>4&2jKOCX>1 z1)G&O75Yo7jfq#l^3RCPujHfn$9m{GWjIpwi~Nr5?Ik7Vgybqj{}wI+W4d6ZUz}ah zi4j@YFG^sofw64u?$m?vMRdORO{lR$7VbmT+YHvN{N4whwR`CFm~FynM<1A}A8}hC zd~=};LO*jHV|u$vLb(cz8|0L?lINB2(XHRajGJn|F>ZvCEO09tWpU_Gd#>>?>RBGT zHD~RA%lW&0UuT}V_~u9?ymliX{y6vN7IT+S;Ub&v zi||@L24>*$XrNiSFva!RP~~p8lWf|_bCR7k4c!Q?0QtYK2&qv%MV?@%3_j~zrYA?J z@y&%{VogfIeF0FfHHP4tx4jJHSeQi_Z`iN9xFgl2C@=x1glg$>-}EaM=1Iy~fo7yz ztSz5CMN)Gyk(Jyq)4wxf6ilYjk}i{#o;$is_d;i2#TSWwk`U{8tUv>70E@DE`%V}E zlj7omdYD0Kxane#j4!Hm#l*=ZYj-Q+joWj{)trrsEF}9tGr+Lvzl> zHiwL*3}Gq;7&xM(0NTyUP7u6#Y%gIBM-78b9`Xy@gb-N!i>TzIW*5F6ejs#5_9QNU ztx%jPmWeblE@5v4*)VoYzBMA2>@{t1-ihnEqe_&!STnUr8AW99oF$xSU7IDe8NG zltxk5s)}I66y_SqS75ml3r`dPPJ?4FrC_Z7bP%Ul>g2Vz&}B9t=?F2Es1R-&&S-u$ zFKn8r=V(b&C{4)FeSoW+pR}~h@FQ;cj_YpTgl2AOP#~|gG{7wLI6*=!8;Y$&g+bcFHqk*MomK$5>y=-boAj?b>Y1OYBr1al)tT$j zGN#OOyHPN=w1ZIWJR1Y^A+N0ne5V~}AJgsaA!+*rsdfNPYg2Q;Cy*jI~XBL zHkH`FxMyZe#y*1SqS^BjuT8Dh$Nl^381mPAz6@A6^h4CWztFhIzFI#bTG)z@tTEDh zffHgU4+Nm0-FF!z$(~tuGPUbnu^EDVsVQv?)VgvJrTxcJx&IY5-R5#dKy;!4eV@wb zP+)ChUcNMlft(vFwPm%?&GgZqR!!F?`%(f} zl!U^NTFmUI-UxJ`kI_MXO~#3Cd9#i5FZ#v582|ns`(uCKfAOFHDSZ0$d_H*1pz$0g zuT;E;%rek5L%UmJe;1o13EmR>G*KuWoCbgcGYsp;FsMhtXwQDP)>ENy%|2C+;{E#8 zJ?PrNyO^yvw@fj)xOFIOv27s;YRSmUy_PCXLj znJxY;Srok+aEUq5oe~|&mQMleqH_!mc_yy$Jwf`ItgoMyf`mHrxhJ z3ciX+7i3Lml^js$3u^au#lH6Ad<%coaF)K*P=n=VHk1wBi3OTUP?4R*7esq5@;a%0d9lQ?tjl2MJgh6F8ts8pze`*FL)_mK&*n2cwhw7}eHQP(N zq%NcII#9J~PBC6G?Gx+)HNtkSn!P?yYwGv>@4CE8;7x~1b71On6)Rg{aNtMh^vnK% z-H~;V;uZ+pADin{-P;ai!xb;FuFo#BjnBw`f#Fm~Vs(Azdfo`csadyyx<%%y@J<_e z_TTkWT?g2$!Os0mw?-e{UbPl|hLEI-D*bkHz3WGeAZ`#JOnv;Qav&b;M39(0X2+8J z`?&j4$%snp#@r_8$eP!?-G#bvjr*7YI$nkjBf)w~>r2Fe`IwIz|CYXLKoS10_LO1o zl2Ok^+5Edu8Is}sU6ziJNA|Htr;ucOGGFW4eT#qcmwmMl$dwjS=H7?2QJ@Of@i;dX zw^<&-4hQoCU0um$hcsBrq~eWQ{)+Ls_NzC0Lt6&6zpve%2z1Yo2uO6lI;gKzAVwF_ zZfe!yl%Z=am=gG1$uOVS%)ZKlNW5H^vP}+|pN!h!CIagD&1a9;?@p)wnlqd#ed-=2 zS^K=DpDi9NXa&s7OW#ma)rh!+N19cnI8iBbQNqa(O?` zAQ_gKcX>atFW8D{ttT-A5)1X2Ij5dF-}60x*|+}-|NQUyyQuS0z>kV1 zSeEOQpOI9hRf;;2a(~~AtFg(k9k9{>l$y2jU=}N=Y<`rvHDEcs^Z2bYtqeBV)oHuB>t^@i#kF(Zh)w0p`_sTuJ zl>{x|W!#C`amEuBzZPR!t|(Rk?%Yct<6BZn24YGv47FrGqy7e}o{-s@Te{#z(xkz2 ztGW2|yVf{Kng}(gH_K3TtV@fXc_DcY@j_w<6#Ef?7vqf1GJz-`Q&Tz^=NCR`Ho}*6 z|2dT_zrJtJf1U-X3_2AwU0^~hmDk7++Vrx`pI1CehEaweU|3OZk#M6+vqG*WILOlF zNsa7K@V>k0M~*6n?R=i=_ovkEc1^u)wV~j#sk*AhG0`H4U42zO6Z)F_M;~|sUVww6 zSjh3BY3T_BW5>iD{c7FZwRTb%4e-v&S_2-DH5X^MKSDV0QQVAN1e>Um#;353%BhLw z$sB>5_eCAiuoJjS!3pK$oWzGhGDPw?UPF-b5yQ}O01nJR;tmuWYTq!Fbj0bgr(&)}_$U7`fQg!t2PKSuB8nYO9IBhK(^V+S+I5T2 z%HgWYmzbPg!t6>2he<~}>JqM4P&Y%n1NZ82B~vT^^Hcc+S@D=+OP>MqIzuj^-5|zL zv+En@s04&$g?zwVe_ZMB?A!lAZCu%UhVm48N_YH6QDtnH%n#QA7TNG^RxnagioPfB zJPHv@3?*)aE936Tdh%vm+#*U$u!bsB3BT={c%0;wGAsG~t0F+y`g;F0X5Q}?(X95a zXK{n4RAG@uWd^gdK7@C#cZsY05^2bmxh18fll7l>cwuucw0m!Atv&_5S=k^&&PF+Jbc{%(I6hIWf{tpPRBEPHk@mGJeElK;D zYav!ReQl*>{W9|BT{mR^itb*Xk$smvr-A!IDECVL9dn&mi9NlURc(ITJGG>dt>g5& zBh#f2&Ys}@p1|VDtYdOe(e=R;v^UVrF2?CaQ}^4(`LhYF+2*0*Kdsm1x1lRsH@ zHq+xOqga?(eO2#APC!#1NO@lIg09bB-3Uc+$$*3;#RjO0FoBrX1BANKzQvIKXzkFs z*~3}+;M3aajnHIbful!s$E&QrLfX`H@&CH2{-*E!)!+L4KlBIw*RR*X^E|J93jLby zdhnsq!6U|FP_WIJQ%*fj)_B-VmMbkt|I~ri98NDn^)k2LWB=f@M5&a)_U*VT`hP=z zE-eOU{|5liYjMw67DGYJ?>tcV3J^8K#6@uF|Ixt!G>y8xtx0`HHoFaJnqA$$Ok-&n3n7#cpsIxnMv4JMS}yO0+y zdLqD}2day~vH$)N73-KYTuVK^^FQ~boDQ4tbCI(9CDtNyQakP;r>5+MEUGyAd?`sMZPNp@kC9vXS zGoJM2mBC}&x1H7`MSO!Ls^!?mhLy-qvhi-N%gXE0x(AJy9D{}p=~RdROW%RdaFpt} zuBp__PK#jEGvub*EX|B>pdPf*7CQIbq?=8Vaf=JVb{GU{qt83*yW7DqE6rH%StQEH z7CH#Vgn(_ej+|EwUeidySe`E?oR_gG5`yz7xWQIbsS@>u=7-*(dRe*iFwU_+6@h?S z^zW4Hov8PAg^-f9XlWtHBOpcGA7bVS)7f34(qn^@L9!G7Osaw0>8WdNLTm5ORBT->ci*$gp85fTNN$ zDVw>@Zsw%PW8s76Bc$1Ph73y7e4y}|+;lc202w^4+{`l=l`@sk=z|9~QRY`jNqZzB zx9WIWXERXowi@WX$rn^6B>A*wZGAAKhCVI+sh!bUlwm=mq}=nu^n?HGKmE_Y?RlPG z{9WJmV@|1&R4pfbpM1vVD2>@%jB+qD!2J8?7&?5D?AtsmgSgUgjPsQ$j#T2Yk6UO5$e z`L=g??Z=HgDuHptAA|Xl_q=R-*GO&1*mw#PfRL{OzVwg2Z+&u6J*q-HdmRvL5&WYQwNdY9ME-OJ+s~c}4$mw| z=qvR;WBH(;<0Pi&nMc(m1bN>Vf0uLj&BhU8r?{GuqC(cPW+g=B$UnTZi@ zt<`hCn~)k8QQYd64;R4}EbtvhL=RBF@^I6)F7FY_6vf$<6re2p zpzxF{T#41RnopyBXP&#A9u-l_mnmn?^^vxU70y%sbC5|>_8-AOr_B7yy1EA$*HKPY zgMi4gTDLg2B%)SI3L&W8$ijWm4o=#+0s&(5^8LlZ9Nwcpxf*ITly)*qEMP^gO1b_i z`faJ9S;fxFc7+!joT=KIe2J|6{wOk;_KXb1CXo5 z@-bgvlBv9pdGy?44T!22FrF>DU`!Po!n-d)iN8mQ;oKk6bDTLVKevk|@E|77GwfX7 z$OuHk&ZFma5M>70(0$Aqy}J@X=e}R!NytOz&j>`QS_TESF0oiFKt(=i3?Af)M7g#@ z%GmVPIRw!7sCXpiky11EQQyw-8D3?xHo`qGgOPWq$VVE@!IWk8%3DxdaU8^`*+Vlx zoRAh-g z2pwKJaL%yck0y|ZeHz_limsIm0tEEkf{A1B3dqn7OB7sMRZKh%6(xaZFi-OHRsLT4BjxFJ zIww|;HEn}ynh{+5n|Ehw25(RFioTn{)tnNtJH}r4Uw*A&V4F%$EwXmM_P2!2j~VSt z#G^Ll30ev3J5|iky1Ah4>1&U-uCbUK=gwH>iTVwBs~i}M=rwO^zQCMyIm}vfgMH9w z0a>-#y{neK((O)Gh#MU!2d-ANrEPgcpC#J)fr=-Ak6fQL;u$H;SNQlVajGs}kLAl;$Jt5OQIZW6)@c3>pX0n=NdUEnP(qI2^pR=@- z5uRj4@(x6Ul~cZD$4o=NAoDLTzCI5j z*PQSF-QWKYv1>WLMOV(6rk98<;2!d2x&hq<-+~iY2%Ok*&Vi@D!zN{yUxCf$00-w0Z^ZT20%GUwW+y zK}K*U9rKgS*z_jN-5ER;e|`_3K-Z#V`qW->3G26>l%~&1N-Wl#d5QLy{QOeJGy=1e ziG3&u#>H@l<`YIeO>pj+-*kV0kusw7;NjvZ2RI~f1H9^R=(#k&ndxUeGH#M{+AEft zRTBs7sO}=*8sL@mqv(XjdPy}zDr9f3a&_bY$HwDu zYK8el9@PZxEB?Oi5paL!O6?W~_8TCje}b|~c1z_w68x9=DTp}jlg`oS%sWIc1n+~p zy}jB>z~sZI4`)KWCyaEbPW3Gvs2*p&&ke zw2)lj)2IIY`qu0DlmFo#`yc<(U-F9t4j)dC@0)dM(QV;7b@uJzNXc5w zHRoCZty@U1qpIAt>ng}g6nwj8B4c3_;gh}DebvV7J^)+Yr*rbpT^mJLu=LvT7MaY_ zdk1jK8PTW~FhK}w4J4T%e772W?j?swqPll!@w)tB{+Vv8s1Bbf@+`VZ=C%_}m26!8 z83$YZ=d3Y2rmr%$zCXM?c|E2!kPCB&h*v&w2tC*P5}crzEKuq;Ch~q)I3o6u2_b!? zXG#r*oO4PI$sL^x6jLG<;6_Eh?_qopX6V+II3Gqx&UV2ajXiN0=K~>X|_DI z4HX3kqBbxe>-*@cSqcfB^ZdP+9saZMhxr9P%`%Q=m6!y(6rm zE0N`k8PtfE(4&Akk4iZt0V8fdERC-MJ_8E43ROp$RX(V2m4ej4Iiow^4LVMf8l@>< zjsh(_aE~)xHip%E>O_jYs18m9XMA1ski(6rmnspQRO(|(Uv5lqTei%j4PV%I{4W*< z2=K6vh=M0ic0@;|Y}WM$O!{~)mWn8gk>XWTb6nOBe5Y>*l|YJ@lYb!n%wMe8($xaR z8J>H}YuBSjbpjtW?vSy*&D>)D(FE!VQG>Tw)DPxp@RP(XD;FG#+>=sP#k9bXQbCt} z-&JlC%T_Zuws1ao*ho!~@e!|TMyjfs`OsSByq2;7PqsZ0NRM|kh_OBAhM%=zAIR5I z3gbW(7HZ~4=EfOK*@KgJe!qbp(eXg}C7&1N3htJVasbI=N_nyMb1mzXT@PW}=jybN zo+y?+%4lLziD+8=>1=O- z5IRLB)nWlnXn@v-oy8`se{$x9`9PT0Pqntjh5or27{u# zmH{amWXVP!YYo?cck26rtF`>e7nm0wmBKM^D+6m4?==biwGnP9xV;|3<}0nA)gWGJ zp>%C!#2?DoKLRt54;Na^o_6a&;E12 zw8?%`!E}as?mp_lG$!H6Vd>hsD zO3YPR(2CpFTRUY3UsT*>;r{ytUm9k?Yz1ptDP*Wc*uIu?^y*POe3pB{VUA2ws$8sd z)aLxyvLia6Ikv>gjf&=#o3Qy4P+J}dg%F74-z_k{v^e9;W2Ae%b$UOj0Qp+mpvsLD zl&*7NyRp!S``agWiS%{8IMY`UH`o76C-=L`Kc{LdEv`Ow`wZXl!6?@wGQH!(T(>iW zxI7bTh;d3suir2>1w#EMyJgy(Vy#r|(aNBr9DTQi%cg9Ee8?W98pTM3Hs${)9iF2Y zomT75sbNdMnQ8Xc*23Kv^kR)`_8#R&APDT1&Q=_d3XFAkI!5Rae%a?=zO_z5%B@yBuV`~$4E)cGbX*-r^j@NJ%pgOtnQ&&NoK zWPE^8u;Q}C)EjX7W&X#GD!V~SX^<7U(TSHxF@^I)^3t8DBni$qUFY&9A9d>;>5MwH z!$VlpxLt`QhCA7xia}VCTBbhiN&sMB=;AgZb*y$LnC~$NiWe z|D*rVANixd^y$W{idWKJ^efiuYV>)(WE1w9+sJ73iY@r!4oEIJ-gwn#o)C9L8x68RN>_XRIvg#_Pw+y zRXDg|w%0t+Rb>06#U22@@6*z(KGVX3>Q;ejAzED4qCV_bNmG>Tx=8<1zBYU*Y^{?O zl3bs*h3z~q=Ig5h)PI+Hq!(1WZR@0nKW|p9{NLMXxm&0be{i97wWsXs=FMYiStGT# ztSjQFcM4W`_`R4Hadr}p@_$y{@Enm z;M8@UxCmgy$cmcxSo2L)$r$TOwl(Z@=h{pidZ(Y!wTzjBop82>h+4hN~6AfRz9?*n&-#1Rtmg*%Xd$F^iv<&*@I41G{_ zbPeFyuJ%>ya*D%wI4^{S%w+bpMsq>csG(}Oz8*I4(2@p779Th`2e}hXB1yq0y+2@( z|M=(9RB7xBJM|6^3p&$mK5v&e42jb)s-IBD{+p+DWG2fU78MPTLa__DYq2J52FNG7I= z;KL2B)u`r}O;9)`;-EZJQSo3mpX&=6>amQinyA%IS8@1ArfGj^_`d%14ZQyRU%(Ii z;P3tWf8%fb_2=_>eV%FACz!mTeHq_@4uS42TvdU+wLzvb7gY? z)bNttz78RD)h5u)Ww-Na!M{8?3>N1tO8Eg-UpL+kj>F;uxs{>+0Gm1~Rk9+vr{@pK?FETyuVc~q&Gs%=Zr?sGSg5g&)RG%4 z6bdIK*gL}bSqOtQd?B2*1bwB%k=a-N^mGCL%y4|hb>slGNrRBHHzV@2^7jxEE+2m6 zrReJPTvn}oY9`&K)65AI%B_5|pk+d>=nUjxVBPQWVIxn-QIb(@>JZm|xkMzNbW4MW z{9J@7>4h7tbOzotwai~*mqP#U72%1diF_EDaM)HZzII1n*&Opcws5Yul8E|m-smfS znHa}OL}^M?yshIeo{#exukZV{--{pkfq(UXOItRWdT{z4na8iN zL9&!Zt*?<)0P=fEB-gz3KD@XcF;JjzutiogIiYi)K#wHU&2E;?>$7PbeGDAEvW5$= zBGJ2|stcXumu98u{dY6+0^vt1f{_a}FeH94m;QMvCT;Le_*Gq7pnv4%g0*@ejn)o1 z`=kLlO0kLZF>O&q>=g9IEW1!8sBz*)-^k2bE@*~0lTlz{z*GbIN_wn zgMw}wc41|6UUIeRYcp%GGiV18Kw$dTg^HTL3w;h7TNjhQocv~lm*>|OuEZ{)IV${7 zikk=lkRBiH(6*9@DshNGE{X{0V`Tg|rQ_I2-z*t`HH8lL8@zqTHN5t%;T)^juE|x2 znwK>I$}LHzz$}~986=Ob{tH&E3kF%#*;8XuGUld_kztP$R4iGtGz7Hx{KakRihC8m z46CWcK;0rfiqdQEGV#0=6vibg)u+e%HVYKpJUm$|DJtPz*#dj)=XN+O6 z!qLw<%q_#KQmGml9W{Va_dIwags?kZ_Yp^zP-j;TTn3zehLO(Vs7y#9bkS%V1<$BI z!neMf5*hPZKm`nHX!B^&!kwQW{dktoLGG~4Ir#L|2ma`P@E`o7@A|Iq`isBwM}2$O z;H!_84{Pj)TGsvrB;7VVWQ-C;(w4R=l%`G?!J}3!_q}xpz|oiHdNnk4cX?l02oAxd zmc}keo?Gzi23wd!bCuy_aWrg&i_+*{WZr_@#jB}GWdV*@p5o;{50+NY;5JOS1i^qo zD%r8%1>X0|8n;7ZtuL3gNci? z2R^LH=R$pLr}O=+G3m(+Z1Fxs_e*HZj;0e|Ghc4)Z6#$hJrvl0702t(^T|LvD$HP& zUI_be@zbnHRQ%|-PsnEz>-7gT%F*d)Dx+AEcpj~3xD=d=N6k0L5l0{s8wpOOdFL};ol<3(~z9tLxS}#6asf2kW4K^te6W1Mnzl|L~WUq-Q`zm{IKKwm} z!W$c!&Z|{QYRuZ`IykEm@<>_uyet8dTBE+>OT81W5Z$PEQczvj>iie&i2I0m?Z>Td z>V55y##mLVZJuC5sBrz}J?nm#LqXp7bp9G;Jv>m468*$WG+aGZb+IdEoZ!!bDs=YI z*zxBc(gnyQTiKY-l%qUjk;uf><=ud0E3}9@o^o0wL z@dKZN$3U0rVvGqjfchXU?7tdP!hly~(dd%5|GYSMypZ~zDFnY^f~>7okOw{#v73K0!}GcK{bO~t;*StVlGT6-fEA0()+A_95olwqp376qK0TSDl8@9 zCRy{8=)m!;u4f#LOK+cX0cxJTP@7^p10t|5J+_@9_YuvV%qDPR$S^wSqBv%-O?mjU zgCZET!i;F$CLWKn{7C(j<>G24S(F2IqI&Ehu*wALac~sNO$;Y&4R)>k+1)_%8C8)$ z@E?W7#Cd^8-9!z!+{T#5BcT%{f_e%Am8K*7m24%;wL6D7FCQ>{e&41n5u@jctHTC^ z+DsLuUh9%+-CW1Sb6VdTyjxQ42+D&!O#!}vW!JDmw)`G4?GSAZ1xuGs*;4TD^obZy za}ZtWIfqjZarqgo?5z%m35XJNuOD10;c^phu!>4C5Cveo9~6wAp^%brpy06+5n%7WA=>kNr79UH@onr&eKJBPn zJawT&{VZq!A3eO6n)u=$X+Ondb{97kyCTo{8je5{z=#FRQ3=6iG#~)2>`L5t zM7?hd7wLJbK)IR~<+AOC67TOH(^*d~08c9Lq8iL~e8&uW*l?xzpBr6%At|>jbT86o zS41#(

WJ8YxoRQ?P>I0c=&qiny3|Y^|3+>iYW9|NUA55bL-9DR1z7-3QpxAW?gD zA|KOjXmQU&^baaTIWs90#$aM==Iv{jex60_q&{kwf;wW6!FbYX2qZDFWsBzF3kx-8 zYZF~s7vDKM9yexxQ>gq;rSK-ePSrJ;VFtMqEOF*JMb(F4$CpgsIbbDpr1Bed<7VdL zZ~Pnnx^MiBf8k&FAAEeMqtvMxO?MhK1FQym9>qkJ;MVFJP`C1?_D;drBh{D zt_A3<2H2UJ3D`@%5_ixt(Ong=;J7LmP%y?IkV%_T7FY+VV{3rlHxKB_VyP6~-Reut z19d+)0g>*^%Evu8H8?nLS8J^#xUpW-boGOT0#hUZKr^3Kg%2yTD$ap7swuy>pQ}bo zXWViyEd@TJ3zFwERn6>x@0>?h0PL;ubM!Hn?n=eNlNTH#k+PrRk$RAQeiRVDq-m+W z6^!OZvuo1-O+i;mSDnn9Ow+RKFxq4+phm4fH3FN|C`LYwjU*)hIszuC2S|gvE+oMt z+!^!2BjC-+V$y(3&OP?f%`&^_BM)rc27OvR!Fsb5*9HG5<{}`)JyQET@-FDFRpOWU z+&H3B!IS3L6jC6V=v+`-@K6TAB;wCed|i;j^m+|i*oZrsJC+BixqV@Dr3NU@XtdkM z>?m|Ugod*K7oS8CScRNpwY`5DaBstPsvU2S&49rrp*Uyj!yGNtQEP((lyq;KiQ)^6 z)iw_&0k1ZEYLyLC0|;g?F^^=oQVk%o@fpQn%N{uIsZT!AF#`&^rxMsRi!W|y=ix7N zGWK8A3>pNX`>?_RTt2*n(Ta5B_oQF%ZUU~UXJm13>WdN|nIcJ#&eayMX~hY#r?nmziZD(AzK&z>rL=3IFE@aG@KfAXLH zfB$=b$xr|31Drm!Y7)>b1;cv0uWKofH(2tF3pLYIbomp7$kcEC0LG4#WpfpBV+)C7~+!#$(RmDd*-~3)Z3<2YGI~R7zwfEOG7ei?jHqT*mqV>>uCn$HU9#tK%hG5J6nw36Fa{&!?o%;XgVb&SaUrDL zfWk}^cIbEgcZvF{%-7f96CpJVQS}c5|c0iCoX`?wkMm7K)wAH zCM|fV@M#?=bgnfn!ZpnT@Tc!tT_){uf^NO-Rfj%X{;^ILaxkQ?qOO*{mwGI$ez@3Z z&m$GG1%ps!rc5tee^a2|KBPiFlbjEpdwuU6fXHuzRf<-Q^FS5R}$9{aq;hEC<%JvpA{S3r!Hk$Gz(;=6v z<#Qx=BGrq?Xd&e@qtp!snQaJEyXf*C;$SIrmj?H| zz+louYMBA>=fbsZb!!?`&j$6+o@RnBWD}};1?FkQL^_B*W)-(q+joWVM^`1s#}j>l zcq(IbiThsfZqPj%@o>2$Dk9-$UM#6@gihl_o?$RLG@M84J{FIzbYme{A+zCBg1KL+ z=(QkiDvLR_Y_C*zF4o!N7T7AOmDCB>CU)xV*Tpd-SD!N1sw~m$I92TO8w}wFs<+v_ zgK?uJzI$o7&_6J`9+H54khS+~R8?Tl&sz4&m}jiSvEy3ANk^ul0Q96$)86=p_KhSt z_n!`9CigZVM~-`PQ2{J}uVO~6KMwJvuZyq<>b3io%R5!n$eXw!Nnb3W}5nyB89D;5-xng?n5(gj16b$S({m?NXnhI{h& zFLMga;3fL?W1(2)_F_LU+(#IWr5XeFD=@BpsaX_=7O5uLi3fdFOnsSdo~{6BGvRUm zR_C@FM*_}n^bV2UtqbWxnP|0fIu=*Bl%MCRZ`N+Rh6rAIjB8CVm8+u|&YrPQ>T1I% z(vmGcF8wJ`5G=?VMMF8}-_17`R@WYuYyV5Y1E@q2P8W?0V_NA z7~L(EeT~0A($iqfQy^-}s(=<+*KbmzWB>pssLYaCe2=ODU`2fa2~DJDB3()^#}E6bnxcOWorgiTeAL2_L>dW2;JR1C8J)Fjg3-$ zHBlHHxe*Wty3&%Wj1V0aInGq31AIZhL5^Hz(&n%vd?Zwc=bZJgx%3*l0~D=6)hL%* z@TK=?xFfr?)o~U(dR0ePKV;s-d?FWge7E~<#lt1X6XV)pRlmMq!@+p%`B_@?TpeUx zp6SBK0U4dHu)tM}(^@9TT4?=(7u%enC+gh?s48`Fk&7TjeW5CI9TqW=>wLr1NE7mO zfsqCuwW?TD?rd!>V(rVUL;Y-vHSWw@*BaulXE&)>mjDQfs~OB|;A;ey&z+)CH5uMx z2x{Y-!r;2JJGrV86;$SrkaZ)AR?)R)p|jy6#_GSs4325Yg>+=h0tIAG#`UxZpXlm` za%j!fKk94MKld;F+28%|{LsJk^S|-wLA7F~LaE+)WT^R2 z12{E{cJ?

j;6c~cd&!xken5e#%G9BseevZ*RUGZay63gV#yg%*_o`Ee2W{5wdC+2gwgrV<+cHN$Kg(XT3rwX)0sT zoou%S(n{tPN!|Uv0MbJ-D`Ucz!e1!c0%@TvXc-ut=ba8IRRt`_<^&;uuE&h6_1oi; zGP}w!snif!mdXT5;1Q2gL~@n04cUOoS^YnbPj2}B(Rq50d23Kqe=1o`F3MQCl!p|P306fx^K47IHfn7{1?ju`m>{nu^HlN; z*D&&6kF%>?n|W_{a^#u^qDLX6OF`vYL7QDmCKf0}Q2jG_rQq(O;E6Ev%=u7_zrh%6_lDlFE2)I zgOvaeLcK0phh9=_{*F(_R|{;O*-3U29jQ@lA=iZj<4$)`ml=s~)vN+j&3pn7*Dg)Y zBsW%0>?(V7eL%~D`-JN|1K>F_rmCzF-wiY#M<2H_OFx0~lN#;{8$73wGl198RwQ;5 z9@O*T^XG$q;L*^cst@LM*lO!FLgYg^6FC~quWjPQ4PHb?KnT=(gUMm zW@hZB$a54W%ePB4-y(tSW8>mCD&KWv1g`oXOH@oYaQO#THdvBe&Q=XFVHU5!jAt&{%(pp~D~aN~%*guG72e3l^mkoaEJ;fD)x~6H z_G}p#4-6ywU*!b{h7hMwka<2=3e-6`bC92J^Wq2h&1B((JxEi@*FhL{Zf4BwCWfA-H& zd{Gjhkfx}u$MO+iCE-zMz_$)gPp}0t{!va#q9`5V_j{i*)mBq=9gVaoVnr%)Hz@CP z_QOSemR*}s2-Y!JDS1bFX7rQTBN!M91j*A^Cmy`z-blB-2v)>`CSZg?<(0!81;u%7z`%lCeXmNfnu zfbacP-;00#U-~`&6C40fOJS8HrJ%1+WyPA^+DrDre(t}ypfuVwJ6q9?Pr7{o@lvsHdE3h~FRYS%3bc}%8<(cN~@Ag5x z1ae4m4oZ#hEKDV99@eHWY<{z;Z+bnv2J5PGc`|msqcOR+6R{GFpx8^_iz$sT@{Ean zJo48GytKdzMIq|`UvNB>wxT_fAO0B%)`C8vvj73UM^<$VBzQR!8JlCrViHp;7Aq&# z(h<8VSHN4u$tZ6%%Q%?ye{*xdfv^3%tO7mImjP6bgQW?Rj2X)*4uJ%1rqTC|64k{x zFkyxVJkpxy9F+j+pfy8sEKW|7hv^=nR3v80f6V2%aa17`a&)i0{d|>s3iWe)A)BF* zmw{oI`I)0}X?6Km{O7S!=tAbl9|iY;vfZcJ3TA204`>37+DfYFx;FPLXxUxN>UBv! zT=>LCZZ~-<{(%!IY9vuWCM(F3V!&t$ln_k<8QstvRlDq}rT|F49!+bozl8`&dM{|U zYwN1SC@^RLh(Qv=F9STw3QQ}?kPOXmqt!0GQxtB+UCzDwfa0n#lO7eIb1a9CWuEZ& zDcBqxdx#`kp`wJM+7w(ysN_qUqx|b*mqBQ=-v3mKKP_wW!3OXhCndVB{Eg|qC}|Bi zgIB)n8DqG`^OJm-C@H~eMsP`8$S8Yn_w3l*o6~iWk8XVQQ~%FD`6uxiufO)k{rI2o z7v}RhEQ51*k$JSt1F1lp7dsOphMnX<|l_bJYrrx)hg!kN83E&devea0A6u>cPGt)*sy`5I_>j28i)GdU1&K<#& zVay5z2D8C}^*FPkTEzG?_}Tb#}O<(S~eB$e+U*lh*&X`qy#itZZ!Ijy#B) z=hKa)0(3XVBPi{G<*09F5krMv$G(sPad*VL*mKM~21{Vg=@&#Tc!S3!0LDEkDx__Z zZb%ClFwsEyV=!jt+6p4!tKB2hjJ(DSv`Dg%C{VckAT1;cwPYpw+RxmZ1`IO6$6uAd z8~zMuxaMh$4a~IpqO0n0>5p4_Vjovt;$Yd;z6n;x@IRIT+lub~r<_ze0X)hqssr}^ zxe-!2kyiQG8a?A0wTw}`7gGag%L89)D!2QVNbce!D8CYLyE<$vry z{Nq2ibsdkkoEiA|V)DNT6TPg%!)2~@bE2;@52u0SY$=kQbArUi@VGj)z<}lsc8b7) zE(qfsu(|U@{|l8A{53;gQDR==WM0jEa?2_Hk4pLL*h9FB#fHAL@x?0Cd{%x|5-Zl( zd9%9SI0H`tV}G&_&=*2Vo3^RBtnGmGxpZa~@R|X-k(cNF8f9r%b@4)m)~MQ;TW4aZ4$w~_S8Pv(_N;(bv8CiQdp*3;!FlmIkFxb#5%evf zbfveYGyNraY?_X1;Ej~HniU2Jc(F0Glg|SvpmnvxHTc-xggv0K*4#fKI=4T2{Lzvu zFrn-whjVnQzl4WO{?}-g7U{%0z*-xdi08O(Q zS53;#U%>-23mIT|4?pgFzc}t=;RixC+X$W*WPmX|4ge27 z(`AvB;%H9-gPNZ}zzyZO(fUqPW<5Df{M}KkYQU(l?dL4BU04Frngk75U|Bnj+?sJX(lXegK8Q)!5V)-uM| zy|tWvi3>!>&HS?jB7+4`!<9*_aLNmO3n z4Cr5Bzb4BWw1S?v9ti}hw%>VnDeKd~*LbJSrD=dtMLj8X_!T6BZ9e|%!uqxd@QE>r;GjU;KS*Vo#q&e97rMo*@XaQ~72X^%(ut>Ov zTufv`VgT<-X9#)sKr@&>OA1=j>h%=H2-6wJjkQ??82H&WDj?TsBtPE2H)WpPfY9f( z>p)D4m_z4UZW%YV?s4ZG7uup2g{xOO@~2Y%cJ%H`EW)XHsncJ+_}hvDtiVM{>mB5n1x^*V~X!eh;?n2m88M% zlv>~?&TSO+mqQe>2J~<$2PpgvaX~UO$Sg6^U9;OhTA#mtqItTzpO2>jobUbR zf9<#Zw%_@?{;rP?pz8x35oiDFl4d*~g|0?76Au6wl=|jS+n&?6p`gpZdzLBRt~cHl zlcL-PVtz+a8)OQpT!6bOpg7js$;0VFKl0n*%rrTp#XSYb5R*Luh3BsEdCV~q95-FF zb`7@;FwPzyCfIaUnT@Sg-vPO6O6r>R-B6k;GsRkD2BrC(iFaJVuO8Gh5(KkJ!Ph8q zt1IkM7su&59pIH)UV*mASYqs$QRW?p zW2wsb)JwuDdOZrn1r(hjE_bZaAjN7}9*CBw(Vu9jf*qa9fUo35wABYqY3LW2?HfJ=iMNs(NOF6wqjL4xB5lc zP)xovM>SYfdl&}FaGGOV#PILD&UAoz^<`U4G&lH@nR52L5f8kuXd}kA9Eh{Me10&_ zrJ}oY6#VTgw=yts9$qz9d3DJ=M|ZM7>cL{*hp` zTzuxVBs4ND?u`H#XO4#i;K65<_HFSI?T+r+pI~M@<8r7^rD%K?$MzS%94a%RIwc#( z{T)>;nk-WKkYLS12}bj(kpQ39Ku$un2!#b!NH`p83Jn^}3RD{W5`gFu7@(jw-S5>@ zrF~CEbQ>X+Eke%Y0@)6a+R%}Y*XMgl5Y-)OTZ4sfc@ECJ�k{ygs7)Kzx+)l@zm> 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

a&A~DA=WI&hu$3-BVuJ&E;(9cbGZwt~(J_J^ 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

YpGq*BNdlrL^VYkom6^ARXz-}SHN(M@qws%EO^ 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�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%0D7U@eyG1)orM{d^cyOdFF3pkt`rFPdcm0% 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�Qk_Ak}yI^?4?71B?l`V{Q6XU!rE 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

92Ub(M5! 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

Gvr}ZYv3T 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%0AWxYMVf9W+zNYwF?yryS6lOkE}*l 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

y4}Dg)1zOXK;dodm+*i0UAFC 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ℑ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�ZW7(Y*wCpFr1v-Ak%rfbGvL45f$Z_}PT`^xcl~#vuiy=D+^y zsS|`izk`GUrASziqXPopL zIjGglNx}yC7VH?8GyG1*(h@wzxsnMW09QP43}Pw6;Gm9SxrVLI_q4{^E*bPn036^U zu}6%0dGcpS4SKH<{F5G18R8Q@XBPCpJN3k;S|Kt#jir6S zj?Sj$tn#qw1zW-19p#RmEe94#ajW=GhRxS08 zOH(&ksuFsDQ&u1g=&g)cB9bu19FsWV?GsuB*`#~r9tyLixv0FsiqX$91?_a4lT#yc z=(*9gX-Bt|l()7yB5a)GXDXOdv{gnbR2l+~b+&ydM z2{qL~W37%k0yyUkm<*_H60aWH$9(GR*LC&Z`@4Vl#h3oc|NI9&?{h!rQ{L~bAAnaa z(y2m>ZTqu^ZHOPAeHiNy)SWl)Tb0Z$*rk2w_Br$td?5h3y^`=VdeX`_5bvw1J@ko3Z&96>YHBq9Niau z5R?-dp~Qh;bqI715kSt6ci`n zy?W(i451#o3M%l)v&<7}Icx)|Qx;743P*WJo8QfONy(43 z?;HV5J+p}ztcAv7vfZ6_&cUF@9cayeS#GEFbJY@lj75ks(6dOswuAj43Cca$DcQDN zUXf3DPvcCVRmvRyIFsvRoH_xZ%f&(9(g{>um`w0#W1-sq{H*9xjfUQ_K0aP~&3cTZ zw?hia9QldC3&VKc<7PRHwc2nuN0bcGkNv}1}X9NeqG_A~d z^dw-PTe?Qcp8PQ9*g^*Xc!o|zR*At=lZbs#RhBGF>j;CJ;#HDJ@|q#Q7h^Ld3jW!& z7!%Bt{Wflum+_WF&dO_fh*>=kX3v7n<`~L(s`C2wEI0eKepd7-dtl6<>WqEL^|HRf zKSR0_s$4c+gb63xhBm$nNeRmBJ5gtQ-e}L~7+rImq;cVv&DM2doeDikS($>?&_(ZO zd|T`rvgUpXxoKjl|9B0!%~2d-H3YNYK2(SXmq_dN)o@k)$U zv(d1d6_b(n0SJ()%IB~fRWXbb+!aHK#!T`iM_EszK@88jA&sCE(AO*CazNm!73e(g z_X?1R05Q)%n+KCH8Wn{pngLuv-;N%Na*nwDxSUF_o`Zy!mNLe!808MY39CZulz2<_jp@Ub*BMvg}BFl$3#3O3gQ zw+GEQs#mfmg3NZf&;7?o=7tHOdli7cznQJoR886!V)vsfi4b~r90lMqLtSxF3%H)@ zRtkPIigtY$Fj*ih&iT?Osdp1L8_RAvleF9<>zjY;@7tGpVdKA$qpb>(eWGrRDqOiC zrRc5ok)cc#l7TS-ZnQ2S&pY!t>mFT5sW_0MfM|J~N!M{Wu^$6i{7jYKIag)j9Zj%f zLT5J$Gk|c@6&5wXsr`9thIGlF?OFHO(;h-1N0Y^=gd56<4>XC&t><5Mq8U`CAx)`c z0`7n5Wv6D{ZIig=K{qtH2s_8c}S7RyY`Si^Ufst(bz9v^?P1_ef^Fv`27Fir~mYi`t5(< z5B;J2=F3NHM`4RdUsVL2kO4x6A&Mm-5(iqyf|nAH=8+-Qs~Yk|N^ug4)>vBuz2Cta zcDVYj>DG3{lf!E2RB4slf-?rpHe{|P1M9t*qmk|93g!8?v*ZM6mWX+kgy;0wzDhiL zcP~B2!kA5q;YV@`^H@0mrg<4H$i>`gS5$o;hzm`uYwfmmEdt;ESHA7V|K&^nH|OUgIbw&%SBVwCxy}1Y*e!PDJu=3DNNzu(Re|ix`BOc)wCocdthCJs{m`Z zPrL6-x3b(P*gOO70Cc;QJHZW2u3P7m*pLyH{LxQhyA2bavLCn18$tqv^pm~M5JX+j zzSUVc3BB!a@Rl*+nOx3j28zU^WNU*z8o=%u8#cBrgA#Lc&Nc~Jb|@`v+CPHJnLPWC42{nBYqZDq&(;BiXOe8k z?0K$ey~wNG+RL*2i>C<=5JX!J*yS&J`^DXs3INp$sj}6VWd9F-brp!;srH6qt2%+= z&ZjvTDV3%cT;r+Dr< zXIzJ@h^qsv8?+9VKq*vHp{O8d!_Ls?_)Fuqh7)!3Uclx;h7~4wzpUdLlnvBX*kP1c zf?LcpM`tJh?&Z`+S^zT`uk!9CTQ`Iy3_{CEw?i8WBFF#p=;9sM_p%LZ#SP8b|1w#a znFI?xa}iF9fn~@41(FzX?b@zUPx*IOA64SG?2mQQk9G(OB8`Ezg7KhOjwP3qkm1hh z{@D4(I11tlLE?YMe-@TwEw|BoeLME2pwmZ!t&C3=ABDzNu(Wb~8Vsl`kXDYIF`6Lr zREv5sSO-|6=Ey%ST4!ZP>`=q$oVVyshYlwdB-R@&hn{79n( zfHevtffrt?lv9u6)li?+BBCQtt2FVZp&k zj?4klcSm3X>AqsEBDff5O6Z8S$-p<%r-Q<_VmB8ufNU76@;fqBrBRSfBFA7sGh;^EOczj#*l14*F-yA(Y>_o9WlJ|_qy&_~N6E3D3Y4DU z8p)g>pMeJr6G*tzgNfB!@_Q39E#d1vVYQB zj<zi|;xY)69YA3lL2XR*vn)&EcirBFRHY<1uMr z#E`A&@x=r(2GVqv;uhdfP~ez@-a^`5>;5YH$%I^*AVsPKEDv;fHVpok$-0daAyNdp zvA3e?$IDm!^3}#?eb%r06QBO`e%@#P+h6?ez5ZiA_TxaIxr%GeY?$tHZ&sQz{f3K? z;$!PhPyDn0dV_hhKnoSV4l`?cK3u9(PG4IvhrW4#3TV$TxTPjJtf2f@boma{h_CG1 zDfD4rhYs}MO3_-?Od2fablS?3_SE)|>v-mXwMuiy>&*t&dExW*C~ZOGO@f#0hgC7V ziLSM{f@2H#+u!+}_=-ROm4E9G{QlqnAs_f*AHp|Yf4z3ALI*o-L8S@*Y4IZ+3|iXR zKyQ&wezZ*oV$uIbrMTVMMm zrc|FXnaK`5C{g(Zur6r0uo58$A6Ryl*Fg)e=MJXuJ`a{+5;8uGYP6mvdn~WUz^9HV zULe&Tak>krC;M4r6JoI6QL#Qjs`va@whI$L_&#*=^JVgZAgWty5~e#DX`ErvESILQ zGj8!jcfnb4kELxcO~6MgWne<9E@5N)uVr69rzFG#RZbp=H`c-)UcyG3m`rgG(WDs) zwCPXlTP&QCnuP?b3q8l^W{4<3lW)fqCm7T4@Ik14KJOtMXbOZxOK?uXuxSQn zwJzXt#$M;IHmpZ3%K+BW-P-tJCJvPl&2^jNUMQ@vt5qJzx4a0>>b82H#yCPfNjXx+ z134|Ifzp^#iiUdFRbpXwD~g3iatL9i=~)IjAGCscNDZhU(HBPXN4S)f(Z(*HA8L4( ztYc0^NPs!ztuau9yCY-R591S|OhA$;{GvSloR^J_*}#)gmsbff0-GGNu_)#ap4TQL z^aKDy3}`RvI9e$9S@Tp{gf(E{IOVIuq~aD+r*B+Qf6AavFoUDaQ6%@mOu25C4db$t zF04zz&T%?Cq;447TV`EiSR-n44r^s%G5wy6X=8w4Zj^=e(#WV-mBirMd1vXO1L&Tz zw*}7G@!51Hd&2YcWnzGN7eeK3oUx3UAY>#@}lZaN+VT@hL=xehFwr)`6UHy~^d6sr9$umx&K0#gf(Pd8T-%b8>*~ zTS^vhIw22wNDR!ovI2GD(>zzi7k`_csD%p_@L8r%CYlk|3+TnP1^bqH^Ql05C09%MSQciS@0 zY7Izqi8BE`@G=px3IJh1p1+UY_44H#>tFe0zvP>L-DmykPx!Zf|G)F*-}q~P9jovL zfLB$k1aSf)5wt_v_~g-#(rC31^%=N^IJu);Oua<3`CI_?{N!vL4la6>NltV)a?F|E zSbnmi^|)cLn1pw+zxu23fBUj8`?}xryMNEWv|f9Gm+yJw+SPQt4qO+&zS%IdH8hmQvJdNI z5VVN_l#lFaQjez+{Z8sDR%(RiH^SWtNv6!&;@pVwm6f$D!*+ zc#Qk@{6>1(n9~|8F%3F?E)*JT>D|t334J#4#OY}jzDO)2`SpefsQY^yTqpUi@u6u~ znW_`i8PeOq7qJV4GZGt^s4#nF-#oCBPSiw~3lnR_PRRe7;0s+{&SKMc$Lm_YpNV77 zw9c8--k#Ty-&VQ`t$&ZAOuW`z~pF*3ZoiTzx$oK1_^wc>|q+7GZF?f7wDp z8!YdCUf>`_Iw&=|<2kM&OfL(#$}YAGnNFhcHVmE>!fq0~cT-xB<^92}RMoCox^j;1G(3p>>{w|htjWU!S@TvqQ$w0*GY^=T{#Kv`DIhP$+ z0_(8b(ed1W3}yfd39f~w((yjmQZ~P_-H($oF{J9e=n{t=JS5?Dl$bJ9>A}izCdkB5nDwnf zB@1FBDAn@1*hxhfC*_!cxqMF>7DA+uUi-q3S&M$Y*b1nJ&3hRvyYiQt$C=Sw0B~Jc zy`3dlHkeuO^^D+)O_f}`sfRM`Ygr%afC3GMkpW#RrEZZspxW25Y-L1wz;Ll{jt!{N z^%n#Xht!Kx0Swr4R!Ggn+6;M_h&bQ3algjC9Kxw^Arq`q#;^TcE_|-(=f6T~j*Lov z$A0MHkh(0%y{^A_7l#-_07Uq32T1 zW+Y>cd}mVdguLsV(ooC4H(9(qysEslSZ2-A~Bx$VwZI40(lNP!|a%moZQ8 zht1$*eO|BGRQZl6urX=MgKz-4xFd8;&dC=DR(WO5F57zmZ4YZ|}6C9&!BiL2HIUrgZbAioS?XV~&- zJ8C3Q)vh<_A!R;tu~Npnj~#OBQ&6g^3}2ym0c)!gqTo!ha9+3UK$o)7XWehoovMYR z{=uL%3`qlIO>?3ZVTuOyQsykj|e^WAIgf$tG7WSN`yk6Sx^VS0vmau()uGJCOG*gcm^Z2?>T-GK#*gDi6En0oWx#72{UgPIw51$W-4nAUd0uJ}R>)mBm;F z^#wb$8x+BE^&?PRlfO#vUn5SiR=A8MtZSL9sF>551(6eyvC3erT_OnDPIU#`mgfVw z)Feh*L4+Z^H@{uXIc2{w=7m90mESYZWcHdsCYs;$R{=`iV67o#bmfy3Ms|{|^0m_c zRhY&E3YC&Yz(wk}Z2B5lh_X(`IK~(k=h(4Y;RRipQC{I&k4%D{d)f)}bM5y9l_6fP zc1^p1o}VWhSTfusXAsOV*_G5#S^)fHRVs(0)=vgqXL`o)s<&8cDukJ>xeR6u>gqhc ztiIRJhD?AX0jdmtj(kgI_;!WkpgFm6%kX9WtZ^s;Ot6)sNt|^9Esb@RhGmZRJFKBv z=~U{tVlBCF8^ec`tj?ra%i{PX2o$!0O(g;985<8|y|*)OM9Ldg zK;ZoYXULVNqRV%n3thJXNLl0bK*#2nf}ae~*;{i#b}w+Zl1Y2N`K}-RvHHk={v%%h zga7^?_}S~Vw|wCr`~!dZde^&OUytVF!EGB2w&}aON#lVVffD&Gqx}P>^!}WG9X*I< zhOV-;(1KdDvbr{)(n9AU7;@AGC>AAm7wqOaENJZXP>wix{{~dL98TGidj3m4U0IJnXh3jXeEI5uKmN!5*vlXLvDbgm=YP)U z{LupNp4VRo7O=PN5enQ7ZOncgcEM%LnvdfE2AsZpm6-s!4}D%d9hk?CqHRBXIty(p+V50!#er=D(b8U=U;wq z0`509u5%efBf+KCVtO1W>BaCwvqdC?X4Y_WwWuJs=;+<_Q68of?XEz*_HmPN0iv0NdYn}~2 zKVgO!dfiVlqky{R#Ek^e!FGhOSsBB@_M%+UpVp+p!C<{Yu9kN)Iz@0To0Fb6can2< zY$Ea+cqj&5?V4X=ViBekvM~BBGp5)m@F~gM84e!gaJLckx*`MQ^+)5J)Bmbv9l>Ut zg6s!!=IvU^Am*iP)HF@3l08dM7BDRwC<#(I4d>_gKkB2gIV-(jo-W7P%*Iz4Q5jp@ z`iTykqs8g-)=t4Vag)rfH-7opnezOBFgHZa5r;9#kyrZu1ggkX@OaZS_dxd2wARg_ zYe520p{(Wo`YHGF{&;aP0I!m%@Z-ictwL4Cc*JOGXiMfNBohd;yJFQj)>Uzxly7?L zAyR0?l+~ppN5=IYBR7l`Fu`y-h8mW^4)ztFE{y zz@=(B*6kL|B8F3IxC~IV7_@_TlWJc4(bvK9mvtO2d1bs*40+u0VeeyP zD}(*YYr3FJlU341LCD;I9Ca0guBq?k_87f?GhF;Wed=Y1UI&Z@;vFVdGUm!z5gVvy zsZz;t9bkdjg9Ch(y7YQE0`oY^lhs1NyKF--r#~bR#~FBf)2ow|76@I|`Ru?s63O!S z44fWYTmWkAb3MX3Wixuql}h!37I_E?5E=4xJ{+=4&z!jefhs!bohBb#hQAGZA&@y! z&#MP)$SxIj1~?%UY>zI<)DVx(yYzrOZ!KoZIM>O}4xx5@0_|9*{R?*V+oix+4j-3z z?*9Pmit{Zl)EjTSiGFN+{^$Ls|KwNx>R<7h|K{)geXqX#TfXfHOLNg5k6k*MqcRly z-jM_UGnk3vmHi0Tm39zLm`Xm1X8Pn{mYW6wwwA}cp6>nf?UCQ5YS67ldA?y6ZVyIF zQOv61ONwf>&8%F#Vxth&Yeb5~>RAuHL!waqxmAAW-TZRv_QM!H83INjwYt5zxr+bf zhra*izx}`d_kQ@Jf7-`<@^Ac2zu_-kSK-yGji_@K&duqA|j=x z{WWAzCs`6DAN!cWdFe&hnk_)>9RB{c+Em&z#GoR>x{`S5>&zAE%XZS041nJCa;^Wk z0Q;pbfLFQdwC1mxei0vY!-BV7 z>fpSzJ(d#?8Z7N~rG2EL`h)*bP7Ue1s$eV2{)neZ90h_iShs-;AxZ%ZpupZK1H1l2 z5PHu7W`6)o)h+C!Pd-N`K7Ie=?_vb$xit6r1HOiI1Kx{}*oCwL&)V&iH70s61gyt3 zfk(|y{kDCP1DMEAZA%sX08*)wnN*0r7m$hoAJ}4iu$1Vu-=)c-aWG|^h(bRp<^W5T z@#Mr*iF1n}5O$k_R=*)!>lW15YX2 z@DK#2&7!f~kGgD^%kA$ONX@p>MFBq5;S6ca>>}?_9eGbXPhu*@2-qR!NatnZOg1Fi zoi&%NB_rWCpZMHKy1FVUHllv_JKtf!j@ll6Dhe>j({eu#2$dbk{i#5rE&R`}6JzO; zYZ9o-Miw#Za(d0Of|Z5Y-fYzJ_V-c=O z9VP<8kJCC1DumlQFMggQ`wls^Ri^(Em|uSLltPs=qUEfz8B8g)?9bgejiD|Dmof_F zdem|ih+G_VO*7F7Y#a{fnQ}oa_qPR^L=A51$1)(agw`qVp1 zz&^xR5m!xKLQcQJ1h41IJk>I~ejIRmpHv3FRmx0e5ykpZAnpqLm(~P}#j_HnIf4e5 zs)})B0b%}I9hnc$JN3#YTq@*Q@d3lt!xw&F~dzMt~GJd_+AhP?+QySQTUmV5fny|brbrBP|y@IEaO zRO*FWmf!1U14ZOY?-kQ6=ZHnr^JuL3HSUB33p zQjGT|7w+qN+~$-?KkMrOpfbj{z4^(T@2*jDk-H>g;wt;lE6A zX4NQ=g)$!u_jkK1ZQ#T`SANy2ulu^c`2YQ)-}OZw`H>&_5qP|MS+y4TE6tT) zyNq*(kTP+;eU#swaQ57i)Q||&xuV9zSHPmGlN8+FkKeXqrN{~F)M>U|v1i65t)DJ0?HPoq zVEd7KB*-Rj6$z0uVsE$Dbci}v962bNtL_n5nDRgYs(4$Nfr%1yvL-$#a-u)ckMo<%dB~pXu{89&Jj@W#zLfP# z+gY9~pofwS956GP^>{>{=V?D{-s-GN_T8jdh-kAot8NL(@?u1^PzriG7~8VVlX#e! z8O722VTLV#WxE%gbMBQk$&#%L7RuukJugXL&^8Lt%YjVVT%iF%vr-ALzG0v!^1X5bqle@gRhSahE#I#o5>+xFv;yi zzwyRpmfHIm(r}(UYGiEJ)Z8Pr&g4n`KaP7dbXV=LT8{=M?i%RCV?k-LRWkcFw*$uP z1nQl?`<*uQYwkKW3^LNmJ`kn2Nh<+jVXa2*HG}TNsJQHC*%|C|&>3;{XRsV3^yD(` z6UHa5cUnz<&oE@3K*`6SRCMfSh?db0E2ToW06UEP4RvME$_ziNvBowg7KkWi6>8gv z*Mdr>_6ZKNn26BZWV$4u=p+x$uC1GFnHZ@{EEk#6vQn+ z_9N*6iA_0}%kO#uXZdaQe;sBE9ws6qT4ohQR+#81&jn!EL8HF~kAtXLV`WxH^Du2B zSs?#C?;5fwq{w-$<(}Y1rGGs4kNHn}*LylWP-((i>nPwTSOX47Ri38Yv)0HN14ozy zaJls%%%ghX&;F*1wqo(TPE4R^@tlD2Iz|sQh}zfkel5$wDjz)r)MZAJHSd>t;RKqV zLOSb_Ha!q-^RS+$JPL(0qs`n6&{>-roe^)b4lm?qj7+WM8Yh@EAagI|LWwP~vx+gz zfM7XITzcm8!Vn2<%M7W-k$9<(UkQ z$Wm&jlEh1y4V$we9M(c{qt@jprp{V9)wN>#OnR0{1s$iO1zMLW76n+hnc0-B9WH3qkNr5l_}~A-Z~lT$|CG=E1)uS0|L^yC z-?#G3H(#Hif^6&!bU!e2JJ@c|Z?G3wK7+YK()R2^GP)uPq>GzdM?+OCltDA$7u%}N zG8@kodBxD5?#53y6s@2Z3D15CLmC~Fh@J;z@<7}*NM z;~(13A4$x)?Gx4!1Jc0|Jx1{3aH+W-yA?8d*OHX_YWx`kejT#hVtGN4@=OkBt3~k3MTpQyAa(Ft8+&9I)p`+pn@YC3mEk-e9F*Xh?2REP zg8N`05`Kmh=+q7W+WTk8O~fAgE*k(MB~iUFt%%x@taa3Z2M?f-U~+=~$RBb0H>09w z29G~OBZIq1B|I0RZaEKIy3x;AC~BQkT~@x&37&$(TCKtfekmD+D-WcfCee2iB8NbV zA(YzFuO0l7Sqe_ZIA)<^fnd^9M^9n|vR?^`1F7a+?b~Yz#KLkwN8^=xc-cy}PpuS@ zF%PT+u)0-6a~p8UCH%7deG&IHM+%FiJos`*)jqUeWTR zfw76t04GyH6BQBW8^Oc^E}BOrfXn2D>`{ogP*sgor@$3d3-!)-yc1_=-i@IIk45M< zh09@A=J$t^l`u9vsP0UFUN-J+ja5Lk&VU0G06stk<$hCTJ^knwey#@kGB~a=G?PW0 z1;PQdI3GDBXZC0HUMw02P(XVs8lqu0)++y&V=m~Vfg@BprxkERtAau+V@m3W6;ovl zCPD63(xl#_$T@yX%U@<;WpE?(MS7S^sV|7zk6Z&-jG%{2oQ%D*(J4bh-o30gCD5^W zDYy!f%i0j0VkxkF6{igB8R|;_yzWC}R`5F`;?5q49h)?uno4jSIqwKzRd zavs}jub<`l;QJ~!-~gecTz2-6H2~>HTIYdpYtYCDF8$McR)s0US_;H32kCJ*j_Yu> z+06^8_ndf>b8jOX9Ma5dV@VGC8V6pKJmvHVfSSPENIx@D`j(?P(( z{wNYyD}kjnzd``quC*m%k@G$=$Y)i%m`h{ z>O2)ZIt&>ENZW-FJqcQKjN^KC{oDR}%ga>>>HS+XL&L#xw#V0j*Ec-V5HKO-nEMNY7#Z#o0~u6-++G23|u%*>?GXLU1H5EZ?6j z$;{^H`Uby&vhvaD7|pM3WjeX;xjxG&6#@SR>HHaaDs*!k_#E5&l>i{6v3FjQu?6F<4tYM_TWR{R=nv z7`tj0bYKSSoMM$?tlNE07NwFasH*YAWGst&kP=%4M)+d)Iy4nF0pR8g@<6bOC~q=t3CFr6DWcnR_)+o zrwreN`v{~%y-5vxv%ssGfKH;9j}z4IHN4LLDYQ!y3gB*mnl~s*&+Z}CiA$|eE&rt8 zx~KI(+vMj@q9B>=wC32Sh8$pSHSh0tchIImyUv1B!Fbo6so~86!va6x_b>2j=tNAB z4zgJacysjWesFd*!LzC##A^|)1j-W3EJv4ii&JHRBJfD5c6L!e?k0F8=p=M11E#_< zNvvhOb00{Df|4qZnyj|QMk(2~-(cP5i&#$*Tomz4m4Zuw^TZyWNW$bNbqpec?_dJz za#aJzcQb9bTY;tK9r>Qdk4YYom@MaHnyHn~yrd8c3z9W@_*fr@4UtE;go)SlR40m1 zi@hH0qMy$})M_ZB-oO-0+2@!AiotQUhKc$LUcarkpRU=M5!+)!nEyZii5Y^C8-&#J zj~W~%q1+ayDw~ra)HQjqOa3Rmt6e#eoBP+jFn%PgN#7o>tcSXkSCo-$2g(!N34?6M zTf^w)yNoZlK{lREf-%@x#QTNCPF==%V8jSl;JlMlVXJ9f2+jC@j&Tx)m>263`HGAv4MWyt`Wk#LU8FpT^))tQqqBg&9IN zh<>gYACnA1C8|Vn*Rpd4-Jxy$j6)te`lG*@+-m||G-g^lEf?TP)}b!YCPUYeg_WJ` z+281gVXpgdD!pxg2^e2>OSBzFO;3p<1XtbQ%# zJZ1eO>pArWKu%-Hmsa$paCFfXN1X(H-PQ@#tCdjHHZ?c1a;LRoItXsyWR30d=j{CN zr#q~W5!T1LO7@YBx>PZN(q3jzSEc2w3Tx>^;W>Gu7hb&fgxB8k7QFk%-?hH{%fIa3 z`kVjlKltB#&S(F6e(I-vY60K_1a{-;T9XUQ?aMUlAdwo27U-zgIX}lu7;Znr{Rj7e z|NoDl-lzZ^GyLA4^9_y62U?tO= z@alX1(f7UOPyT0r>dp6k-`9TczxLaI=l}Wr-tVn=*BkFa?Z#s}2Tee!1O=ev1MTyz zs|Y@vQEdUXUBuNV<4%{_3$Pp9)2|2&XlwSZ=!2L|#A!UA5YRtga5eSMG~wkKyE@abFohJ3 z-v7E~)j9qtkwUOLOseRD?axZ3h7>Y67E;0Sp#zv?-(d{qgavUDVS|Y!r_q@*iI9Lj ziNWZM%`AWmO{JM#XrQef5)FfI*61><2=Wk9mF(wKTF1{C3yT7-jU22BI1#`S(ROL= zPGA1PWiz-r>x6^PnrUT}#LWDttPFFnVWVJ@87AMiKS2LP)%hB!INY> zQX`#>-4VF|tNB$KF!e>WN*1O{U}sP!h4urDE60wkki%@8u_sOm)0& z79cI$-Kcl|?stIOY?I|q6fU2fkh)!UTh>@t}HZ#@AmC0H$0kdEbyL2Jk!zSSitA z$p8k9^Cs8#lvEULZhDMBGUjv4-FxZCzRnPmvhj`0TgY)23POk&K|3`Fuy?h}lW)%= zo8klXLc5J-$B_kYTbXR+a&+KAK%H7w&L5j3S2!%j4(q*%((sRP0Aq-J`vl}wW)x4j z!^xXz9qO_Y2P#;$W@w3*dLoAMF(nSzFA!-Xi7bX$4VkiB26Tt{_tOMq1a7(?y+8t{ z8t)UvSgck*lWQ*o)1hSSfUQqeVb$eWe(Nemh!50 z#6%52R<|{aEEfv(1fScSnIPW1Sz7&N**VerI_kJe&#^H2J+l>>;M2l&GfQFrN4`tP zfaRRSb-|ABQby`Y*IDZc&5h;bRaX|NM8v4|K=)ssgHRxNpw>2vYXAM?{*+C03dG3FB^MI`4Bg#ujVn#jy+=+-uv1=!KKCYngTE!7>7cfa0rMylZx&I;N7pk zdHvZx^A)eY^Y47uw|>rV_;tVH<3H&W-u=dV-o%4jb>*nqw6pF4ZM64(r6AA1`b^p9 z#Bdtm7FTk{^<^^u3B-s(S_gWvTyO@R2GMj&WhWw}2C}7UtD7#p_uDt&k>##_H!x@L z3Qy2X2fX0P=<6kh2;e3r+hJljL!_#mF<-|X>SQMPT`{0ip@mxskYvC{?FFv3KzgoI zC+Kev4CDlVEm_~k|NVbQ#x-25oYjl(rmxu3B(VQ(i|!J zxJOh@0OG_b)jk2@y#ml3^>Wf`H|@XO`dYqrX`hqune`$O0I4l-0&LQTC*TwmJ2~$f z>BJBNnO=a9@HAoX`$cX!zKb3}c-{jEZlDC60&0#?IwvID0{94;iLz-I@a6P6^mR^< zE^K_>Z<0`Oumegj3qvdg3l1{NU+C!##Mt;?*UZ<*$|V!4>xbB>S@(NzHhPOZgA#ts zgnS5#g2u2Yq(W#WpfT6cvxR`Wm!=5^hYYZ{h(A=1Dt8`Pt6Qh&8?Gkw>4snzXv`!?QN=yocr3ULvF^Wde=efi#9Z zulz91kA7;fhpRGVe{r>jrB)4Y6gGBJZ`~FHwl^N)y()2}ejw+r@rNvidH@gYW%c-eP#`oMPbiM=$6|P6B;D7JhaSsj~~6PA=3XyR@2G)>y=L zT(xwvU%m4k?~qOIV5MYT&IQLTC0iM`0~fB9#rL3^fs`BpK>8jO&5jVxsw2#|H(}Y( zgl(&mgK-*2)ef}GPY9LR(Sx!al$b?E*h?$Rs>XIE^f^xQ?|DE&Fp-|LtT=H%A+=}$ zvgTH4fiKQ^lm)n&;RJKXWAwCHWZhG)F5&Z|t+PMJ!CCtTVBgjYaA1uQmb&fdWYxpL z`28dM%b)2H+&X&)DQo(~6?Z!j4Ey}FMKHkxgbhzfoOLIElC_K6`DmJ4Z@!PZ4SjQ~ z2CNu9u21+TilDbj$=Km5O#pP{9!M~@M07hqnQPgkmoVh=7?OIWXx^~jtFi{#4AuIz=^;aX#Y1T@F$c|S0~ z0iH0K@?&Y?maDi>lCPlB)unb1 zm*K4f+QTMgV-{-3Z(Wmykj!b&?1go?I>gT=-nLe4g*>l4y?F8BdV00q_JqIwSO4b6 z{^_syim&{T5C5>A@%f+g8}UJJ|DeZJS8;EwYZbfPoj}$}2|f$TZ@wr_)QWX}d;8Rw z)BCT-6zgX^p2C}aL^cpO&hd_7^py$4@0Yj*{C*OLA@+!8Y*(slpE=oFQxEnMz>{w) zTCg`zYm>$O^3{uP{>HDzpZyE}uOIm3zwDQP_AmOypYbiP9>klkzr=OP@$_zhZeN7w zOxY$sdaECtp8O8k!UPfutZ@Q=o&j+)@t?#Dx`Oms<^Fd-n5daZ`L`fSf}-@Kx6Z9k zM&Es<8Jd>WJ>Q_peuXl@F*ZCye-mW(feD(5eHk$O+5x-;%>iJbP5T*qdJGaAYLk`W zKO{lcAgMWaBqCT)f8Xt@nUE;(Jt@e)BQ0B9GMjYIa5AigS=ru0u%AJF@?wHVJ-)^p zITaMzQ~8G(3qr|L|kMu+V4dJBgW2}0bq3XZO(B{*1G{#h^Beeexn8FQ4J)}DAOI+&%g zZsfC2=oJJAzY`}AAHt>ljKQ}dmby&ldu0WXy_O~kbz$V3`iX_gGcigP2N#`f&-Z!5 zpE0hCov_Y1nywN1>@UvHJ@@i0nt*icYFbzhu@u56tg^|A!~^tSt>%V*a~jtLARz|x zidSr=+L!iN{0#MWaO=4N?F8He-2_-k4&I0&6Q?r@r!1ehX*lgTY9)?ykNc{g?-vU@ zZGUK(Qjj|`0X9yp>Xi0}xHO14IwZWWIVU0>c!@ws6OZg0rcK-8edIXyKG!!VUR918 z{oJ7ERM}81fUP(sGd)83%pATj8fPPhkkLTa3-!(~dPg3^j@-)P4D~EdAz)y9d04yv z=(D7Gmqbx#;c|RJP|Jxs2ag;EkOJD1&_&p>8z}XH>CPLA3wx>mTs>nN!j{3Tx`nfi zFc4lgX6biv;4UY=Zb8x!j7r9L|DvhD#Vhs#klNCJsE=A6ew{q!P44C4AFs_oP74Wq}{A53GrR5bveJ46Lr zd9W%FFZ;NxgX2^QR0he-o?zbXl&r%fr)*DZ74$ii1tFTYNQY@j$^V7qF88e`nYy5W z_lX9YYmDt3U|g4g%}T_czRfCRa-F1Wxu-wRCByQx#sd5LH8P#~A7!w!?rk$Ds3qa7 znRw1=I5?!mjcluBCT1zx|aLgbUSSo$UFOg)+O*R(y?&) z8nqe?4@3m8Uc0Vp?!t@ft#5nlW;OoO*MHM5`l_$~um03e`M|e-RV`W^s&a7h2@jTqX58}aUw03T+b71<7sdhSe<%JoK3x+*jWFdZ?uHIXfX!Vz za4_sKgh)#2`2#$EXg&8qYyMcCe?CU?RiQ2v_8V`$j&J>@zw+X1zVsltBYaH^n8*tXi$Lsc66V8z6cVnNb{jEw$qB~;bjR@cpD;z znt@wRkRcP?^Gp$va0c5)@SU<`EwJ8#zW^Q%v}*$Uh!H{WyTLGYKRAdI!O+ME^}+^u z7e1H-(e>#hmx30rHyc=H>D!3U5K**oY-9tv>caVF_nOQO(LzEiHODgjj-~b@new*W3GKQ#E`s zWGnFO!;8SKG)I6boH>IcccJ69h)=$pl_<&OTPEs(7CnVE}v+#an$+ z`cCc>PEi_8c0~c#4UL%v1mb-RUYE@_flOS2Gmb!^t8v+R=_#D~<{Gd7aC*Rla0pDP zH-h_YTm#moU%ds^r?|;1aM*)&SF$*e1qV%9(`-w!@@G2xyl<5$cg3trY@!LJTk|~P zM@JivjL-hhy(}x4VR13cr2}*vS(?f7Hrfd`f|h|CKW3m4_8aH^QzErQ5_udAR+Wlj zt^*vZBKba!iI=y4Cy2Fth1obKI#`|z>ek8x(>$ENVgouvgBe2U>KemYuIoEf)K6xs zyv~!|sEBjT?2n{)<+#1)@M#MsDu$T`ZcI}S-Z9NqVdwXm)iI|M+|;Kej5t+9xxb#V>?lHX+~2sNfH%68GWvNY z=$Oldb-0vmZ?wzgy}pzFjT~a01E|xM;OlE}Wd0syE0GkJ<>F3M0S0Q8sf8i;uue6| zvj0lZ4yY2sXM>p2xb4I`t!!5n%EL-EGXPb!WbUf5){E+knsu;Wy|%7}x4!jlFS(mv z``>)sFZ=p$`1&vXsUP)GAM$yh_qpq>@B4nNs*I9h6W3GOD4)cP5!Q(Mjor{L-tx^H~*ecu1AU-}#W=fCcEe#8fV1m6AXJ=iZFRhO(_k^N#3FSn=6 zCtL6Aye>@a-Sc<3C};XN=Xny;k`KSy$zUoweM-y=@v9u0@~reenMRk{7Yq*jA$gtu z0VcBtC~(V8&;#fuYmuzD#Gz-fFZ}`v4mSPqR2#qpIG{dsdhit8`_Me61GK9Q@_Y%} zLx8ILK*t15?}S4hJ>dqr(0O@?6i61evAyON?DMb)IIr9K5Xx|0)H-R6IBQhS>_zxR z0GKdg8*~FjwJ0<>CqhNy2d5H)Z{cKDGRMhEYgt8*1dtyx#z!h9Bifc7T^Lx-Hfw@k zIem@6zjyowXG_bnI^3J?&-h0IIdvgQRFO$WJW`b#S#|qL_SQ+BF8lWjo{0clJst6W z{P}v*`g_b?!f6ct2ZF=>_d52zQw{?l zrQo9*o<+2rnoA&fPP*{*X6@vMW*=|m|0otU7IiSM=7{y+rXknh#EEst)8dOw23IEe zwqlp6Di!;*acYz4?2YB&He@P9V$fwn<~T@XZO7@|5}=%fZk~^R`N{*@=H1bO_TreG z1M)`l4hihm+aSTQlRaaHwJr_xWtBL-OM4Q%E2EMoxK@FiNz|xhUN~`8gL~;@gAfF_ z`0VO4&m>2w4#o#D!w-{8f#urv$esa3yAg3r0uyUFhd&<(7Z585AqBkEPbXfJAgzIQ8dFIz!_|u$>33L z5-vr~S5MHvfLzz4=tKfPYl#*0JfR^xz*d&Kt*n6hf+^N z>c*`UIO}|j4+ctMG6L3{@zqCH3&(h7Z)KEg*R8)DXHV&)wXzPW%7_r+uw@D6cs*go z3)IA_63B*1-I;KxCb(+%ODEx%AZ^)Td4uMhR>4}1B+~wM&bhUtTkv9}?=HN{u)XCf z2u}vRbiGk-jabK3OMQTvQLVMHe2R%rMlx^fV(TQ_o&`2$>B`j8eMrhmfVk!=Y-gq| zqccnfWs=XtQ5oHjF;=h@tzD&Bon-09=n`{c2M7mdM|M?}VQF5P&p`bGDpgtzDr0Dp zRpn%1&w5}CU_tj9{SFZsBLoNb&d6Zpl|v%&=rhhcWUdDs@Gn}1LQEE+zVn)@0XfGy z!K#Ccx-icI=(=LrYaX+XkOKy~?V$F;Z4SzafuHlNl-mY~x>A;xGI`s&4&h^IpZg!B zFVH7|z(kgg2`rrBZ!+|!k@ol-?ot z^VaJ3YB#8-1#HT0JiWl`^@5LAulQ7W-}ikV{KyagDE`Yo`{#e_cmIRG`+xZ9AN^79 z_lrOC7p;%|xQ|`y#nZ9LpGh8P%|~aotwzs1`_16n$rNuNfA)d7e*;)# zxxy>geEcVV+&}rTA9)?UU%gm`SGY9LbQQn{@$~cpZ@zp9PA&XaN&jSMH+BnnuI4@F zaF$Vl%4F193z-aMssl1wKo6aMUj=k+8FYMuy?K%&$14M zSqk?+^pFX{dMUv_*fN36l+q&sc|uUkFv9y_wp@KA&s7S*^V6H;leb#tg2QxC5gHS0 zBuFLJqI;jqNdv9nbYrAVHOUQltu5TsS52?6A3CMwjLb0!9sRqLF0Hg_Vr+J*Kw=gr zyGyF_d&fj)!h4i66)zmjhmf8;RT%7Gfdst5UctH=mPemJaSIR$Vd963LZNhc(S}ZE zD9hArI;PKDqM^VQL)HbKc@76;cvia&#a8A%w9hbL0?^T0cqm&H&}zW}h^lM!I~4}g zPFN|9;IN-4jLreL5lQ3lCEo-x^?i(Eo_YdW4F1o0+}|S)F+{>xY`qVd?`Qkz_7R@7 zVNTKVEy^Y6(V8+&uE>cVerA_c;hX@_2fsNh7s6mUOB(mn?$zJ%U5ReX_>=`r+4k%r z^uaFl=dIUDa0V+n7zaeQlujR+AlGn_*+9!z0$)^2I8nTinJWaQy>Ky=B7lh2Oxibg zAdjxBGU@CGj{x2i=+UDivL?m|;xd7Do>kA2-Oyf&3m|H7;-Ms@<46mf=cDa(<`cwq z<+LWX?*}&n^^Pxkhc(CJEXorQ0n8wjTd(YxUN3Y?iFy&&l*8H(m7HCkpgNiPOcqZD z)XpTxzUV$1I8cmsJFD!4oMX*KOFi`MHhsLJAO`i!RD41`TJwD*;f5LB72z)o8k^4` zJiG2a{Z0oj9V0PpL2{8{mTHI<0EYyrgN~$M-srj+Y%|Yc78jP+T+etX7s5hk2ah#V zDS9RWr(a0#&2EQXsmuzUjEEGhe4oOpf<_S)t#aYLt=WCy2PJ7-dP_PxLDT(9TSxHg&FH(-?}DK zQeteZGu9K!L&4-;Cit&4dstS5TAqJE%$x;1B)n5xbe5Y>mU5{-}v=k`v-sI$KU-MKH=j&?rp#H7k>tR(%V1uCA37{&DX%j!qs)5 zy4jc3>Oz4JplTD>Rd06VEwwfa*F%CZ@q)I1_A(VOGIlK12v*|f^;KW>)%f^O&(LeR4 z{>Q)YGe7+g{tN%YPkUGQ1IE&Ow9R6Ku8Hf4U!-vzH6!SThGn zveP~OswWf00a?l{OMWs?FW7yi&rcPi&*yVSUwMyIFFNDuXH1XjJt+4pB16UmY861} zTyWdh?wKQ$fp!SVQ3B%Q8Fpu@jXu;b-wjpP>;r~%0T1h z^dn>D0X(ZT@PzECc&O4bd=XlB??%^v5D^*XS4{S<KETR>V}+9%9P+%|1!B=)7^@ zs!~gg{l<_D2uA1hulExa+m`m{G(_WGXzBMOaxzvwR1mm}a1o%Ai7>?UjEV`=OSZ0W z_)jE|l2Kz*Cg4e?yN;|TKEPZumpQTrV?hr!l||*)iXm=NaTQtf19XxC-po0D3?eWR zOiAv7JLT*$bTJ7Y7zd@2JjdC|Jm{S!2p-Ndvlu3=fCvj`!%wAE$j&5{gJaXWk>Q3_ zNXpxKM;|)pBmWxH>Wz>=#!9J*l&!xcFfkQFulp;hWWAA26+(qT*M+OLiLW~39d4wi z)i*P#s(NONunP5#cf4a1=vcRN&L_fTJE@bSKoSEX1_wN#%tSy~;gdZXm*n1XUwcrC z+4zBdHc}Rn0LU1E%7)pz9X2aQtNi0k2 zejswpp!pX&tAz|Pfel@|N&>L+SxMG43NP+8!9KY_dbnp@)t-^zFXId<(Zcfc8X&tg z`?`qc1;^0>R!kptmc#m!pxN)A1bOa1M3G;(5TSg=Ryjbw8ByD(D{-1hrKvWupTBWnhab1J|EWy1;NC^?|g`tJ2v12|WyO5(JkYaz;IEEV?HHfC8*NGM2* z?e&z`Y0>dAsPO03R#Bz?_a^E|7N%_2%cMfSpJI^=4xH!oIxYZbD5GV-NM+5#;&U#pgUH1Nu8oCj2rQ!!+{^#jYOhkY zlnItmST6_*6-Qkms?L-<*>Mv^H?ja_0;&KVS*bO+LXzBkVtNj*CrF5T@hvYdJiT~< zr`LcV`k^0wpFi_w{`BX3@Av%R?|Z+W^wy94HNX5<;b;D=pZW4>T^oh>Y4AbR3lZjc z#f>K=X6iycQ0oGaj8O%NC}z|8#x?7NtfQH@nVY-2soRg|GZ@Lhe0s+FK|X1-_S53E zckSzmP4KDQ^TzU&h7GQzLRzpE@xXWgy?=mj_@-~>cm2J8_ zfs|#--@x|=J;R_)eA879@!Lc{{O(JH1fBgZx|Qzs9;rRii9Cge?ew#%h341|Jwsvh zp>wo1C&7^{I13ugIHZUGf1Y1%JUF6&-RSE^>Tj!V?XKdNG9~%z@H&}7Jyxzoe%}IQf{N=MSq4v zekn+Y$;k#%Ezkqk$K(OYMSAF7^aHljBx>7#B5UJ7(TN(#Tmp725@$kWg2SUz@a87aZ^UO4U@ACy$t*jflarQ1xId597eSZ024jO3hCsVK}Z9+odoOhcIN z$F)r<4Gv31P{I6@rO6gSJ;DQ@Z@YOuM;uTSR!TvqhtGsJ7@_mOZ-QCRE??)_G9<-6 zKiQUJt!B+{tXQ5WnFV3vn?hHeeOhp-Kj$QYVEL5uAiy*MmnCOfChF|fFpXOI;WKL0 zltwwFZs6_AqlaDG74ir_Jf9%J0uIRbH{=hp(8=9qk3%HYIH@&8DH+Xj5HMv3uJo|6 zx@}USCUb+V|F{4}Uez{OYoS)V#ly3N!?vQt^xhcDiX0)qFP+(6{yV3#W!^Dm>q(=< zfz5i|f|0zzYRpihGU)J>+OVPOyzleSg(j@kA|uhGukam@e=C zjl3YC4Dw`;qXR4UIbv{_0YBHBNYp^Rbl7W3^cXi2WXU+H4dV!2F)l6vD2H^=`_+;E z3F7^^q`QZxi%yTX(wqr+{(-Z?*&T5blEECU$4g`q3GgZ(V|OEi0*D;2ht)kluA`l~ zeo5A*V%wh&y|la*!-4122G5en$q@JKLP)Y@9mm4xkvIYGp?E3lrYlqu(5(0gh1V4l z!o*VL7^As2j(!_}C&A__zDph94Tgae3$tF9rZsxSnH2;MaS zcv=vaGy0{qT}#Pt^9JV~{C{b!s7q`3biKff*DgH0cne;C<8^%FH+;*_|F*ySmw(R> z{geOCulTSJ`{1|z(qHl`@rj@G@o&D*`@WC6j5MW^kAA8raPO|`T41}+dzqMra4)BJ#}v$@-_h>Rl>R2!5;;1XNdWRf<_WF(@Lhtok?wL zRXPojC*uu2g9v~GHyBK!VBX-_!397EZam}#32Z&9k}vJxd9H{I+C&JD1qY9X8G7X; zi@u`b}uU8vfaI)-+(*6&Ve zPDE`E7MX7Sb|yIR)|hAp9*$qh@ViTU?j%>%&rdxAy^nfoog{OEDuWcKx^reM1&^DQ zlDt(6h3sQW?F%z2BLVcBkWhsj{df$;32J5o{F5e607Z;^Pk@p&bMnv*XJ9Ek+Asmy z=$G{Z5T1#mwaNdoPMyH04!&la6k7I?&I;k|dXH((5MW+tGX z?d}A+T1pFFs{3q=R^Bb2^|xhvEMLH_g#oI%u|5^xmgYe)IQe%yJhSlgbPe#b>~Pj@ z=A-!}24(c^e$V1RhwJt)=mg~_k~swW&Df2x(mwY(z9?Iq zeJYc^NG#`fn>OBp9g%4aR~)F~8crCWXt>_-MPD@1DrTc@{n1u_-k7Ny^LYgcSC}mL zCw6jhU$nn_A#MDiyk zENfZ|>gWYEKfwo3G^o1kMZC+pFZcmuLP{e3r+Bz08b>3jb3CchI#^7 zj77Ig7`oGtV{Lsh-<2G-@3=NlCYo-5RKQO{D?Of5(3w7X*_9XPBbxOPAg; zb}BMu0#iC_t5H5Npw{eXd4CZ#oI9SM-TVF5?IXH&;r>V$R_1_>4r_h2<;$cTGuow& zPBnn1$o+~QO}=0HHP2$w(Q8(I*t!W^&gY$Tdg&Pe#~rD~BIImH1>b`!^rAH^&m?PvM|P!-WZ0a(ifnOoFQg{yRY8oIPTCOEnq>!}P7w2tdqA{`fxZ815} zfZ734C1JqI>ZfZxJ+5n2ttY&A?c$HW`^TUD>bL#1pZAU5^3A{V)$8y1Wp8`mx4r!n zKK_&VOMb~O;0L_z0~fB$M#paK7i??*`&w6d==>8sP;kwpO$d3r&w(x&yJ_@ID#pv& zljEj9K)x@~=D~Pfjq9mZf zcArc;u+h}OnZYLx#+ioRTL3X|b|*A%5pONRuFkkKoI73#(c}3gzU)-fT|Et#$x_1Xe^yU&AsKUP4*b59&!h$RiK9$Z1dvu8RC8`)TRe zXp`%8Wajq!{4IkZRo#+g(|JAvaJNDqUpEu<1wz2yh6pN+PKC_GN?yd2mydc=I+Y~q z3G9s_*;tp41~q1y)_y{(X$ZtFTqO1te2kmTaNe>~5k%{aW!CB5U zL=!!~VC>hCmBdt}Cn@;e_xtOen4-tq1^~dqvOlbMP78o962Mmwvⅈ(GAQj963(o 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�EI9KhZ$27ej9 zC(tfIxhR5E+ZusHb^?!V*+}<1e-h(eS|heZ2ltVmNe`Kn05Mq?7RsMz)zXvs1w<=U zGy!GMRPo3#-NQMk3%1b4rKjk%;VLOfMGHr=2o-}Uevd&(w!QN(OH3hq64osS)oSIR z|8Ha4dgYTNsqWrN5ycvesq$GoBZ0`lm>HZ|KT7l=Qymk%k6}`eehlrgTq7gNYUbQ3 z&mQHfvbq9Pj*3|vs@j2GT7pO>>jYcLF&zMy^wU)7zkM%WJ94vs=b8OA{$AhSnxOb9 zJNU{u018ZY&2(0xD2dvg)zzfzc3c_*&nHtJK-mAdAln#Q z6#b>btO%O?41cDxJOm>*_S^bComm?=_G@7-ob}wHJ-|POD~DwdAhv%z-5aE*8pB80 z?pqfiu(u9HF+IiW+j;x>3)n;#>!g*&|G4@enVHf2B}k6^3u3S-$!A`QYHhB6*Colx z)H%p`;_*%%3$YWYdiSLFLWo_P_Jd(D0)%M<%nDwSH?)v`Z9vV zw$9ZB)9ybcgerdAc4OP6V!PyX_{jmr@OiesW8~hJufp~$N=#6nSy9-+*x!(AxDYTv zVOzn>V7tm332f&i2t^7jA_QS?wHOmZD1loSpBGgE&52#Z7qso+W_a(k2Mb4)zEV`aXk!-Ky=#?buo6v3aLj9JV-TW_|LW zb4!~P0}uGxHx55P%6TriQhIn;exE~C?o0m6`s|2Eu)EJXIw7SB+V-a$C$q0W2i6+@ z>hr&3ts`?M#C*;t_C>w1$O~&?Z2S+8T@5f%;IC}DwMLzGAAS6O@W*ozw9EK>Gtl%! zd%m{S;@gY6$Y>}G`9ufMKhIIM41{0aG~f^uD(!R6gn2(MxC`m(_nsRI%06j+rQ9+v zl}TcK;w{u_*G46=LZcgN_ia!1)JWq;4WM(G|9dD%sUZJZAX^rBX9 z`A@HX#$(q{>S3ga>3#NWRX>cgO4QboRi~_^g%WmtH?ZvNXPJ4wN@6PSrlB+}M!s-Z z+f+p-B7s7LWQX_2_xAcqHUDg#zhFrmL)fgaOu1+KY!+SeS&QSy|5f&Hqd(bE#u$&K zeu54IgcWA(e~_g(DF4$?*SX_UAyW=LMr(xs$Lr>Og_GNWSU=7;AmF2bl14v+?W zC5VsD&ljK&B4O_c&V98)*K)K{~!BN zs#oqF$Cp7#cc~4XbkxeWsWDYY!aV z3b3{8$65MpXU}hC2w8LgD;;%Ib6rJV$+oHGnr->|sfpahFCMw&izalcX-qcYQ&bD2 zzWTZeW4*reZDCu|EK&p+6r+RxF_^&E#|I{$RqU57*HP>Rb|^wH8N&Da$-Un$SvN7j zB>4;^fZWdX<5>sGm38WWi1K9MC7a>5zx;j$FmXN#@pYv6A;$zF-AZ4+--X+CK63GH zYUtgc9Y&gMA3@A;&SD!wlJ4=?H;Tb|US;dzd$$@R#1A|2sy+O~y=QxDA9QJ{A`w%X z5AM16e6M+D3iYfF%i#BJYBv(RCrG4UyFWg+qXYOuoBK}(YV6A)$Rsepswvs=5%Lhw z8;R?p_`!D3K8~x{YF>|e@JLCys;tiW^m!w*HB$f9uLdiv6)aa7R2(h)<0_A@&r0Z4wX`?95gNJSx~X2Mg^Y)DRsL-Mn$Ms5u4?-<6C76AvBjf)rirFUsz2vyN1z8LP)=uOYMs>n z!*p5889EKI^JKU^Xo=wP$JY`(hjN`xc3SH&^#c&-w|%o>jfEA?wSjnjk{^H%Ecx)I z$(fL9@VtgU)CVVpx%U1gM7K@KO&l*vLRg*kEI?xK4rFOE-C?x(Oxs5?cppHl1nMc7 zE3N$*9PtgwN!H9PHa=vX58z?mp2X?+W&~eRRYHH39UpX?;K1+f?HNF*On^leM3zO_ zBNXT76>2~jd*A)fRa*_2nygLMTHP*Xk1&Z$J8d3I5-QaKe$h;U-zL93GXND;aaM!+ z?014s0@#3v_{Ye%46;Jj?acM8dC8Yii1RLy?6ogy4rS|{#=2rV!n}_{zN+!7$qB>* zv_}6CYbWe2$cyV&-j-|=rlpeQk@avJ@OJQhLWEP1fAB#ObBt4q=& z91<8(qE5f#_E&dv%`S`a)D#xzejYG8=rwRa-l#Hndy7`h*NZPGd*& zCg7n^N<1GObM90pH8*QKwqM zSDNf8Zy9a0$H7{_2S5wfpb2`i72n|)jS#3x)ql%Cc(C$Wp|bMNGLEkGf660(Q4Dtu z+nblmRR_>_JVa*_{F%4u3|*Rc4OsVCA*;%0C&;A?a`FE9J@?(p-{0(04q_JYiUddk zALX9Rz276u4|@vBR)7kN6pT)59ncC=7%!Bkk-r$tT=cU{Aa~3PqhN5>TYfY3CQv&0 z)XO$H2te3At#K}4LTqEMr~nrj1#+OzftrY|`qhI*w}J+CT3 zIMQUqHOe(4xu>_w_jHbW`(4tPC&98ZILb0cKmHzb9M^NQ-pI2u+Hzrs7jf2DXN8fM z)LUh`K9eKtkk={2+*$@3uFMwf?4Yp1lBE-&@=R}KG|DjJu-3#T7&H$znvX$22j_EE zg4lq4mLy+|mUjZjZ19V*#h5kkAHrpL|K$;u)t%=QmnDyEjfcC*vaU~XWUvtOF#3ek zM!<8QLuPQW3G5Zd7*Cit@=snZXj<^*=f?*(z|(r-4W7O}YQRllgDhi5-M;E!{*Mi{ zED3P=rVw{*PFaNZ>YI4*uZY!wrANO%8Rf+*=;G>6WMW%%02lmD$W0-^O-3rVWp93o zJ-dx8Szm=8iH#oaNf)Ku{_@}I!t`a|ETv89ZAy2WY3~K`(%pO4z4zh1pO;^-Mtl9- z)n)Hun;z(QC5_i-zagIa0o1uC{pdYYjqUXMU$(i(kT__$66eoxm2Z3KetOs|-`b3< zi3C5Z+kvqt+in7v`jCh3#t6$#r@XpG_rBn!*%KG#jQ%<@aco~84=%Ra1F|lkiT#|| zNBP<~$N{Z{)f?6{UjNI0d!MhKOn3k3o5h(KuJyIfH~j=Pc~>Dz;`%+e_k{9F_zaP|P=C(ZT>{vjv9aioQ^~+ZN4N%b;*uukL$yopfnQSIghMj%F-LQPlOBm1 ztdXFgzy{OBz$%k($5^*(HGy6~x4bAe`q?kPZms`x3)KB!*uZB(5Goro z|A5WiJzTaCd4(!l;(HF28T8Vgk$Y^h^C+--+QDpl4Lc?NpmUi$mDSZh%fWu*zu_S& z@|qIsWI5M?n-uB{HVWPi0jFTPhYuCm$>%HfLY2?PS!O;c-IbaN$Xe5Y~C z=d-ajebe5n9nzcuv6xnAUw)+DZ}sF*LD$l_r7gsMfv;U*oCf!iwyd42q!tU@UU7T) zbRIsNac14^zEm4ksy4ucxp724WhkU&8Yh&XFY+e#);lPt;5d_9nWs_p%)cZ(x>(vX z7JFS9%{h>he)W>|qn@RMs6{twHcu1K5b`BDlUpY&2wD9KQVlUrpNJ zVxy+CpA}%2q1*Q9v&lcrySL~l2O`y6R|23V&0M3^ub*}>V#3R3mwK_^aN|K_!vr4# zQOCEPRg?*Rp2jx$$qvDYRgc*Z3^*+yWLe>4HsobFUE{q%Rjr|6TTq*TR2}bJ^wDa$t!U8Jp_5U8l~Vq&AH4?3U&SzPwl{_^3NtDVg{yov)bRjL3-qd zENxu80JbUzFz<6sS~*bl`nr#UzsdVWJ^@%wAi{r-O7-_-Xdr@8IzL$JDwunXeAO30 z6d<~0d}SMU$skEM3y4dAPJiSy8S*vEk=>j>PU)P2czNL3R^Uj7el)zO#jkvY9W4%$ z(($Vp{e5II+V!wv}HD}zN zg`>B~b$-4XE|{|I4FVHLkzG+~GqJHyZ)>)hWi{%^#%iqCJCYx=)4mRkwHV^`* zT@GDomK|b^ex@?h-kqv!H0jD=hWfZKQ zL$yk|dYD1N#gPOjnUEoa-DQxa=!*M;s~K1}ron8#;yRmu$3OXCTC|H?@XffY%`mIi z{|&N|Qt8-X+6Wa=WMq7B7L?bsbARb3#U@N=NH&^kKvO-g7Pui{Op1MVWzY#@-J;#% zJuiE+#B%37a?euh6xc1;jQ9MluGltgwcvz&wVY?DT-Bep6nH`Fyr8qSe#v@h!|T}* z=;E*^Ils-a;RhqXO5ns+%)H8sIgqh(sc-fcJ8IYKb)Qt5oq`-GZQEl@MXQv+-rciA zmRd`CbOv}Gy`X<_h~L-M76)+5`ah-UGucZvz7&J2ErY-KqD|HiTt<50Dq|fv-L{QxS{a(F-9oq4l!>{yBtDqZ0Ukao^?$s5&+{?ot#63t`tRf0 zKQf!7x>Qt3AdMpv7tAo~Ex7^D^Pc9)o}ZmDxsf=j47JwJH=mni#$x7m?yUQ~R$in@ znX&VUFO}IT8gw(ebV5uOc8%|?kF&jnX4=41@#8u3wL3n1V>`zWM_0Kb+CP6Cn~mi# zwko*l(Xo$6^2eq^&DJL~3MfPqF2SDzZV73*zGM@V(L=d94xPQD#c&fsKk;R~R6u0o zV&8UbX@sIjx8365Sr)zTX!|$V|KUBfH~2^mnXXp_J2GA*Q!pdx`uT?5IF|~3H6dn~{WCa@%#3 zE@d??d(D*928QR5o~aK^hbuXnBUp zUr6>x`|o+5A0ubq>FSji>#Nmox+S1eccFqE{j$c=-r=m2`ppCB^`0#SoHDd#=Kb#H zde)xbI$OW@$4CqglXu!I0E*pdjX3qlCS^}1)DM+5xQ)SBeKHEcn+dc~;dNedNSVf& zDC&WY`Ue20YDp`TPk9A<{)$rTl>(Q38@F%9k26j7B$>>@2&VN$B9o|ralJ~ZqgB{t zPX{N!Z;+Aj#@BdwR!E&VRvT0Lk{xrwf+llHONt0C2IF0}eIrar-4({8(fm&Ut;~4# ztZXOaJKV>viHUxoc#6_!W(p`|R^8{v^fJ|ZHZtZ67E z4JfQQ1RRfv(x=Owu5X`E4M@iUos$d)IuJ1Sv3(=1Kb;RapFacl`VgZMPSF7u#Teeh z0$eT-bAYq;9Zb#1ee_@bsNL)jXt;5#KJy)Tsr&k`N7}44B67L^rrh#l&XBneQ^OAMEx?f3x2-fP06y^&o$>#BPA#jw;Uc z}#o@C4;cU7G@-OGY{{PL&e2P%{r6sTm1368`tB|;E zU+)Ea>{OWn=ziJQ5`mqt0m4D1XPnQoasaT$+2-j*FX*pZf z7|53Q=}>v`5(~3gpOsyEFy;Ds`y0N0K61O6!iP0bM(^%*HOFD^0`*3;sZkMOKn|l) z;J>o3>0%&q+m>W!?wIW8n`8*GkSdx9kc|?7GY+hh0IALG7@gpGq(8rpO+&uN84{hp z%9Ec$T)Lh0$Q9@md&lna-YF|@6ks&ld%l4%_%0Zrkmv2r#XmKHiN+w?8q!a{#`ZbG z;Y!Srfn#n0+x2;CJ9|O6=am*jtCVYbjpMb+0iWz|ad)UHuiFG)=6=OhHY9HfVnhpq zlYI%uW+(I4G!$G*R6Q-Z8vj%#$a_po7a7pQ?4zSYP6Az!Tj7S{x@0w*Y@W|d8yM#f zcp;;j(P4srs+KbRdxFap#EJCRtHhZZ)ek~z>^D&khjUG8Eh_j-Hzd`~K>gpJ{|vj?t>bD~%^#HSHJAazMjmLYeY8<>hA(a)YJRZP>H z+#B^995N5H+K)lf6I<8r>nsr;9qB;iDv}rZ#yduAc!B;Q?c-1KU?wf8U^c?{I7XSmTEvkwLXx3c-rP%%>ByOYUKTF` zs)~XX2MdeSjYDP#&F}T+`|!LE##0P*BZ#aO7_UA~`Ai0{u&=@?=I8VgzVY4gDb3S2 zE1iJjEX>#W^k|-W!na-?<$U1Ezj@>ej;Y*PTc6BpWoz_;@+1-tRUwt?TeQFbCpk z9kL0HfYQ~!bvD{EuH1_olGN{veHqF|MTG!>_YFb(I2?r(E5OQSJj&HJCH_jeXWGdU zNTqf~#sOFt0c$gNV|mIGTlppl=N#35ivyVefxR-Dss>}&4K#LWTZlgVIsVyl*j7Eq(1g#&*0HMC|8AZA+fhQL(S@ z28^^<_+vh0?Lf4mJ_}syy%{pxMv~_d4%g66miCx$-PT&R#&ixPgJK4Vj6YANK7M2A8zwlM1B+ zzHqfWEqUy_ZuZr7*5pZXQw;&JvX7;`;$UtpGF8ObENqasDMloA0DB5KsD837jqVtmxn@y!KB zRlM6GB?xz+CwS)#yXMJZNU>Jh7O2kANPRXCunXT>WhAR;lL04Asq~BS@ilc*QvJ|7 zV^`0{ncdvR=I&L8pM6S|k;w(`<&S_N!SNfk`OYB9G#wlc=8iKUvlM?4;N+7>;Ma{yL|7Mq+!ZK(gt~S)a}@4-qoa&M)tzyY$Z(W ztA9=*r9wpcaf?>;n-CU*jMSceO=1{*&g8c^;UAJ3#@Bm^+%L)W-l_CkQX^SWnu>z(jtGNV@u}S?oZWHfG(}6xBwoNF_@)0lPq9_yxp+^U z{T-a~c0KU!|LF8Ohtw?tuZQwMt}+r%94KN7_kh?(ZUgoNa`yA*-IJ5RqTC> zuv<7?1E#F=1*0jGwD~{!t-uTBI!AkzVIsD#{!F(tVC1l8KbDlt++Nc+xl(JC-kBlO znE^p34BiS$blhx_Z5M%MOKBbWAv9T6=Wxye)(*7mt)=Cx&>pH@x!fA8 z%bpC1!p|1$VZ})J-?>9|+)TH7{KVhicSTv88M47ys7ARit>pd9gyIBa(3&gbkxp)X zTEOPjJ{GTjrM;pmjvraysj#K}jhU1%u^&4MbjS$Ig7)g#+bf3PIAlTcC8v%-V+K!> z-7`=0d$)V6jruUc3rK|gl#;PjnJqPF$Pas7#10=>ryMfNU$;AHRPcn*byjCYt4!m7 z;xA^6nggM>?K!h0!{SJvq_WG4C*le5K=mBeT*;TnCL4!wn=KiTS8ph;Bx^sKZXaO-hv9;7L()euk< zES6j%?Q>7os^6NeGTPu>KEs~`z{_x_S@JV8O*HK+Ageq%n{G;c^}d61bQyVq2HUuA z+u2Rj(jj-t12!w4ZTliBVb;#Z{-~IjfGt2(LwHPQEhAMwd|n}WvlH}mPizN)^_Mt~ z1zEBRKe%WwfeE(!^rvJoc1#O|*is)%ySzDbB#!*GR1VqM^FL42vi)N&j-p>-N%YDH z&7D8Lda6{1DiES*PojsMuA^kQHt?5LK3fyh%KRRxJoK`Zg}&t1ONCgx%%$57Xbs{S zA@WLZ-wpVhnK@p;0%7)V2PC44`e_GX+CQ7{%h|kg&-g6louH^C%^wwx0QQ_?Rcv+d znV$LJo8MPHy}u@$KQGHh11jsNOG@ z1tY$)hAy0u4l|6pmY}jdFpf23|B#2Hr{4i1>$IHRuk%{uGQTOwC38$L5%p|nN$T|C z^5>9H3TsN<1supeaJA(L01k{2~LWVe)-MX8R372{U2esHX%7mi%?~D67rY1C%-C4 z5PxL-*i7v6nOO{HF*zBUT(yKOnx@^W28k5f9UENoR);AW*Tz(lAKx4a^%Jele%Hw=j-X(=bnA zUksWLPB)WDRim>PpZizO=jEi_`5K+K7Q5WMq{*buSZA+edzIqk$2}_vsQt$7DXNLn zk7KRunU!KvuE9p8woJBgMcH&K7N(_i-4zGyh}KT8pRlT=Gk4eRtCYru66cROdTi9P zUB&Nv|2{KW46Z8sQof-;<4ANtwKz*0`D(1s&3-i?sW71Zk$cV_gYv0je0<4mWK+qd%X298F}UxdWPVv$TcSp3E}VuN2mD zbrf4tWKjJsy+T@#(eJ0}BG7U+76XFuEbP+2NG9uVMWVde(WdQ42*vg=l?B#cpd4@k zuuj049ZTSKLKH;gXGAJ{pI1COJ%YaNGrh83(gb<`f~p>6h?206#vD!GMVvU#FIisO z#Dakb=Qm_su$f$fswpmgi~Pm0a|FN-z4F_k@BHahCdp$WY}Z1@#AG*eg1@axP^t&k zeXM{1y`iqo4$iurA}IP!&#w-dY)@wg|8$&sB|d%_BxKU5nVdd)+5SRtTSvoP->X+m zrhYOrV>VEok@vF=o^3CSIF6Q(LC3MX0g4l6oRVNjZvzYLQcIWq=Cx;=5i6zC+V&%D zaA()HnYhfn&}ag((8;++aYX<0Uegx9_CKN`K;oi5>;KZ6#rFYN(_Uwi4laXq&i2Ol z-7D4I8$uF5Hz%HV_kZoZ2U{g{;!p6|2YvKah}Zr%L0bPgGqWV9FwWM$PHPkcxTgAp zQ`6c3ECaU`x_suf`NYOqEwIX!IW}nt@aDQtd)~Q5`6vF-`qatLPGp+|f<}F_`<_LR z8A!tZrd>mls_tr|XD|-IO|eJ#GJfA;XBqYTrX>qsapMaddwyJ6}mJmGx)+(+|Ay?Bgob-86Zesku(+3G(pkd4v{=S{clGx>m+qVwYK8rOw=;;Imqy9?h~$w>E*83QDbim%i;d zd!xpbT$x*|GfE3r4xjzC8U>@G8`zfzVxlWlZ*}mmb{~@-uzhK%Je1Gp9eopogl%bS zu&ue}#cNBIvA`6z7qjwqsP;^I>4|)U@s^0H5SQ4A^Y*!>9LB^x#Q9_reVz0zBM=1; z-3gxG4ftsarH&9G5N2zCALIF0MdOOFJsje1FQU-~LiHo)@kgm%84xPD#@-5&&;L&| z$M=NYeDK_ubE1+Hx~` zptS7=CDrF<0fl(b8t+n{gsD|%_OkXBNTFX)+ z)&D5Wm8RW)BQjnC!NYznNJWkshh^Z<^m)1TrTSCev+pt+gxbym3hMPOO@-hY_aOiH zycp~{o0TxF``h!`I4WSnDyMM64tmcTmJtt&QC1X9FPpz*=K4H?;0m2cS)ljlJd*1; z=-6zC9OO73hH%zzOUaDsQP#AKB;u|O;rztOnr-ubB0IHL+qtqPaxz;W99W#{9)f$=feL7oRn)6#HFlD0A&!?nMrEGDpo-2t~eKV=jJJ$pa zQID#NcF|ikAv+G_p&fK?z)MN&Xgh~5 z96rH*>46B7kp~ij@oY2dxt?+j2$^xo;v2m8A>*6uE`gZO;%X{0a6;HchAw_w3r2@E zg`ihSzck^;eC2m0I>No`gUvcW+DvTZa$mPD@`WAq?_X^|t=ZO>v|RXxpU-tf`e_u~ zHEM3gkxKIyaQT1rtE9fpn!2|RFz@{d@C}i;)gcRr%MzNv3466#Hrix>1Lq_Fq?8hG zlz9N2(QkT0EuZ}E{RAI%z2u|qMIK7)^Mo1hffso1oR~GgCxJ(&%x&p6f;9c!S(fEy zgAzvW*L`iY(v>=An z$k4F$MfjEAwgk5g{>eVwwSiLa)Py|iNcwN>zh$D3#DQc8c1_Etwwfe~p%7`0T=r+p zuReQYdB}v*cc!t4A)HDZ%+rEZI^GX3Q{oEu=Q~L9ws}ZpK4;ERS|bvup1e1eTRXEJ zTex^0d=RE!2q(@bZMcLuY)9u2N_IXr8q`TU66bUtZW?S^#&RaH zbb`5Jv-R*rYAL3WUorC``Wye1wu8#$dOziKk~mC-L#k}O$|hD}U)ZPk&*1Kk-tRA!}(CbI{sYSxYUqSg|YyZ{w#3oObte)(m50kRw)4`p2ll}OmaKGkLy<= z=%Zzt*XWXIX8%^uDH%EmtR?Lv>T#CF)5qfo?NI8o)?z440}QfHgTw9x8V_`phAFgb ztzU0$IXJ##N38P=z>#(sN}Mv_`*q|2BFV~)j@wqIb>p;TW8f`*j>Ib9`{z^Yq^xOX z$%|@|b%(xZB}jk2(s{QCwLU>Yd3aRm&+Dsz*7Lyk4(Q3^yf4NKqZ#)})ds_}EWY=E zeq!Y1Ie}h+Ey2@|=&W6@x6bqcLYBrk0g|w*N1FMuckF*`+1f1nuNy!nDbsJ)USWje zw`*q#AY*8KhiI+kNdTt$>k=V9V$%mXJYF&YeT7B-1!j|H$5!rzKzg0=3w)ztsUJ-Vw?>FUVA> ze@S2#HoSQEZBMVJdEvrlByJFv>vT`L%+GqZdf8DqC z^>6uy>|V&}Mn6gbMaf2dz*fJM!K_04Jnnu4<|>vGV5# zMMG7d6V|~|N5g!Cl}`h&wb#q;%HVQmZw%|9g-&B~WkwJK0M%b`L{LZpUVUyM-JcGv zB~!Ae{&m_OJD~fWhamWVHLBfLQZyAmwkm_XKp-*5(_L_{ZKJ?nTP4t=^J`838Th%% zX27an`w4(`KACrQlE5ImL;Oh)KgkFN^CiXP*45jkEuW6eQ@U?V%6s@lwFS= znNWVdz*U-5AlyZBr2)(I60iei_8;notL*b|Cskz#VsI!>J3-&*{h^6 zq+05!x!o_wmedUULBci;3j?x_^r3VG*@9g6`3;9y)Pj~L0jxSl~D)oR{z1F%V zw_Lqevd9)k$({^g$F_LOKI;RvXmjY%PQi{0b*CiMAnjsz58oGM=zEAGmvK*8IFJ_` zbZt3v8@v^RW-nJ3-NVSRfbe(NHK)` z1r5xW$Ub%){M8-qq^SsR?5NKzS3kP)YQ(h^UhBmVlPO4BQKuB!Ke_$QT2c`i%dIla zjSV#pL>l>PZE2*Kn{e84*Ae1+nP-KxZYlE`{7!#<|75e$JE`Hv@|8R?kf7WxBuoncc0lli;HjjcFE6sw`_}{$XbSxsr zlk)m?r)0&-i>yl<1^RsI5YS>PxXLO)Naq$0!&mk7^PBhI85*%0>|+6XJH*BQH{>}! zWP{r1i~8F|srqvZ!o=P8t7*+=5P2xW@B-Lt&V*b{Ye?=G4@t5uREyKzRl&jc`slNI zARWPcZTwkrXZVk@Vr@Vc41Pz(*c;P;%@b)PNkUu8_qxto1JNX<(#wQK$!)M;&x-Fu z-C-iFYPkZkR5h8I#@>75OC>6{R+?Qo$eJfQTN@}kXvPlP5v#+f&iTyOdi!M`XyM81 ztN0NQE%$j^#f*0}1am&Dna|Kh4+qu5{_$yd{iR0NST43GgkptQzsmuRvcB^w43x^- z*|=aawi28}y3~X-Bt~I0HW(1o@J!X>tZc-?MmyIFc=JdlDp4%liXgsrs8XL)oYd!? zv|&L(A{PEJc}qVpgGdXX6ZS-^s);}MlF_jvQGH}<6nz>JB-jh*6@t4WhrKgMf}=tZ z?5rQn`kFM4*9bD;+HT*`oSu{5Yr=OtyJbGO93zei$+VlP9cre9%yDt-sjIQht(!MG^N7mQ|6SHt`pWE_Qycrdw3Eoi2ze!b`TLOOMqsrL)vYR zGRMFdj{DXc4GX&ikPKCL>~e;(B*a<8WG#35-2ME1Yb+^)3!QsN2I492$rT$#9ZB#5 zWJt=JEkZk>BQ8afFJ)P9yI(KO&(#tC!FcB5lP#RQJDVLNUMO7#{+w#*cU;<}QTjl{ zduKq0vqp{68~O0%?toKDRZ~5$!+L77Def67g;7Y!OsW*Rq>yV3irn&}fG`Bs6Dm=> zRle~(%JTcgDU0*5bI7io|07?Ywe4?Z_4(Q?8Dfx~{?He18k^a*gl$}dv@G(dT+66b z`zVze=?5UlH_$~AKKeI5fU4FPW7LxqMQiHpD zlQT*XKqmd#RhAc`1PUyp-#b?R31b*m$G-fJ< z&9d(?(JB>#A#F#5gFjPTJtT#&+Cf#64aRJ_`V%{{0s<(`s=s?q^~+RN0+BQ(SGNE3 zL_S~HVGOGsz-*=#kL3-HKCAYY0Rf!VN52yXkiZumN=0&M$)nl##(8N}{}sU~S;KvX zY%h4|Apo%X^at-0b6J)SH#3;oW_T(r-{C!;%@FqAtqe&`cv$4H(-u+(2=%HqcqPxc zE6$nytCv}?JZ|@u7lVM&I?G26ZHDuZ(LGd`)FZ@W!lGRx=5wSBbnS1g+??+^$c9an zvZVSC`I=|Dr^h9X(vsM=UA>dkH?XOr3@KBs)zG#o6uq%Luq9Qy>=+OwefmA?FMwjj zp7s*2|6=dcMv!Z<{+R&Rt(P6r3m&NuNw$78d%ft4UOUBvsPvxv$TEp@asnGKU8!QK zEjjT17BeuMCBGV~8%~r)nu`rgVno_L{5sP7c>=qn(cQC)zg`@Wp=RKt0M?g&=&*Ea zl>4}>*1+=QgtJ*o{6{uza-I&j>=~Up4tib%Nq|akKob(NYt+{UZ-c5S-GJ-p;mEeO z(rZ6UETu|B^D4*eCkfdIW)(8%85ZQKKEX=@PVs#!SykkFsXw1HC9>xyw0*+cd(Zi- z=IYoC`U;7a{q%aN43V^D-d)P=JEue@xzN9yOM#bbiIev|2y0osPW1xtl!1FMU-mGV z%h|spDFn&K|3#wq^G`&OWD*01LuAlK`%4?jn8?mRWc_0I?Eway29vaYD*@rfx4HO$ z3J@AXRzy(lQ6-#`Y&?e;61Z^LSiX4BsMkvdh)E% z4}V@&mieF7hq`5co_z6{77Y?_F>56)N}Hyu7c3R$yh9ahUBr>+jnsK0_pUF_1RT0k z@D)PAdRV_iC(#$}8E9JdpM_w+WRa8j+DAs_<>qDFIh38}Bk9eyIeJ4p@sEyj`8Z~? z)fb1YcKe34pc>9K`kLQI%ly>{a}o!f?+&RB(MLN=vVdjhk@#~E9sf`) zONuErA-m8oiRC}9hu`YtjXj_J?Fy9yd-$u65H|lI=^{?Gf3wb3sp@Lm&gpwCu>M|; z%JZ^SiKA){5VGeNSxel0bUIGorvfAO*swT{u36Uk3-+{GS-qhg>3^%3(jXhJGKDpz zaW6HFpr{N0^l_5_O#$n5YNjcCj}6ix5}H*E)zsTq3ztYm4$khIk!L+P2B@_FFs5RjsXA+*CnVpTWe(bL;^6PmQh zBEN*3K$10nV)TT1!*20?sgMNxOJg;ABlpn4fO(*(a}F(>CI zJCOe(t9oZJ8f6esswTF&rw4gF`nFkM<2he#V0r4uBxGSw;*4jrE_Ys^QlVRY`vtpG zOvP>wOVp_U{E*)~BYjTxI@hGug4-~vah6iR$Mb1PDKzK5RVK5$qP-`b{Z|9x+L`Pq z=mG)uzd7$*xs}0s0sg|_C&N3z{&6<5O6hfn=^yW@@Een7V~cG+OJUi&@4DR#pLyk& z$WH7l*;0iemz26}zf)wc_3Ke9X_;1+d-Kj2&jz74J<(eHM27fi1CazP>23S$G5b89 zGdVTbQhHRM`O*eP97b%#M0fdm5>S>;GEE5x)#~)JlU*;#a|}9hQ15y4XMH<$do!cs z!F5CFcmpOD{0JF1qpKrs9Qt>N@)I`)SWP?0dI$ChtG!?B*Ir$WN_rs}h7(j z{Bgm#1-?AqhlNr3H)MM%O!B&E<@NrRt8a6YLd6GrosVcE*k#b@5)U0h6$0u!U$;Zk z12|=J!+DdVH}`9{&3B^91YNHVMN7Wrwy^#a1KPEJLZB{(9g_YbBKvbH3p8?~>u^P~ zq`juKr%dR)_Y>zh75&kZ$Rtszdb1DsBpg}b5L+sCww&->{4trabd|xddRypggQbVP zzZFZ(S&_84i>p6-G_%)>X87pyWyU(n{weq+Z|(;*WZN{id4MI1KPpA+dv-fCwW2Gr z0^j!CXCKWBd@#kHV?JQEGPGblLRfh|(p{Xe;J36ZDq`@Nl76G}L*w@z!Lw#lVcT`c zU2fzVI*(bn?y(s5$sqBqv(wLwQf%oQ!Qqs)d@mn&B5)WWhUPx5 zq@Ey6QRZAOze>e-MR4k;H)lXzJ6T$a@BD1e$kb{KUIHGe8IB|XfdOvqXXf+R;dMrK zYQ^GzYfNqAYct=T#ZX8WWs9sW06vOZyS6ZxoN48dZOW-+`%0oed@?NQFwAIwFB!+|b2io4Tvz7JGKU!k z$A35*d!%2#{VMw1w$B#exA_AAWGddj{lYtK-HaZfB#iJ;NJre7L_giP%0ZFYa<<32 zoT`P=dwW1!<#%jMkJE^N(eubhK*R>t$O^4T9~PJZ6vz3bVE{9qGmHF;48|cZCE1=a zL?~JMuHSw?BuUtIJ3I^^@=+QX^{lNJ>O@&T6&#s^q3AKrvzQiq5|DTF-?AQ^x7<=@ zU!y9@&SGCcO$nN+?$-JRD9!Gb9ff(`Q^Eg>OkbZj5{;}9z) z!N?SPwEc|jgY3;b)DqLuPwD5&`94Qy`CZai+p!4=@b-AyQkXNb=ZicZh}ARK8!j<4ju^D=Z1y-L0Ii2Dwn$^@4ckOzGqt%ql{@|TyQj^Xs>o`|gwy@B zcg+J91|sM~-6awI4BVKuIzX8^xFlvnPYItB-_D*MGGeSC*(NolN$M&VxOy+Vzg}o}bli*ijhG-F zz+SIko%4x}+yH)qX1|Neb6@4j&O*XJ?~9`{if`?+Z8xvNL%Ubj-=c#FADZDt``Ao@i37<2;NFvzGJ4U5v&!%tv`$}Ye9VEXD@Mna-Z;fJ2B$j>)+V!$p-UMM~o+f%6oaj4%9tbF`lsq|a?7xE~ zW1b9=VK`~0t?rW`n)8O@c?d4?&H3mGWm~75JW1sW(ThAm9a)R1R&?*T+wCJ7cABMt zFMpeGx!{-ck^o?VRJQuvv26r5i4is5m?csNsTNs5MxGu0t-Ft;#w=ME8A4eL1#R!e z@)mz+Z4Dyysj75xHrgeA*DYpF^QUEEBa^&Dd`*dJ6C1q&_ltzeZ~k%PtSl`~ZcOx?jd)Fk8k z;M37IXlseV@tG~rIHjEmZ6pdvKnAXVx7Qw$QEHKCS1QQWhmJDWxcglS9R#GOgu2=5 zoTAq?^8He+5Ef@@gNj^{+RSv&g5Z+o`~wo)%Wvb9((TAp%Hg-%G`|2=KK zAJN;Z=t}C%x@gVFg!PsS#uw>&!du42x&qzdKM|yR>I5uYYY$YP4lrmr%YHVw>tF^l)dKw1b{5?9R{= zk6+si(bxYS$R&n-oex;A>Q80^TtIKRWIcU>&t?4vCz*1-&qGITCsMK;TL4*#HM}i( z{#A2Hi*+*oF*jR;d$c{{^U<*#fcyXB>RcCVw{aZ|%9;0n;Kt(2gxAMj(E($ACIJ!;)g^lW7^281ZxlgcfD&pbN- zc!wDu<(Kh!zq)f&@a0s_7`Mfz`m18MEW3jbV4rwBw{xF+7s3Y9$QTpmS(#EeAKT(D zLVqF`Pi$py)7REUCmXwHc{!ODUfRl0n0Dx4~(Ks^^@G`MK@ezRj28U#~p zFbi1jdKahVjc4DHHS0bjlkN9iB0{>@Ww1Lt^esdwCH-w+oSe@l$@14G*g{;6lorZ- zV=z`A*pQf;hYF#?>=PRi){s+e>?c8JWgbeZy{I5LjUDwfl0mK$UsUc#|M85H6 z{KFA1OvOJ1H_lhCPFi10f@Wq^J(paraSKB9H2x(1*T2WSo%|o~I3>#0AuQulnRVch z=Oabsbrj^!!cRz*%|DOvk(dMK?v{&PstSyc2vZ(iJT-CZ2FuuS8x#8p=DL$Qwk) z$hWd#5iBp>ed77oU}HYcFB#)C7EI}GvL{EQdHL@VfqT>cNXDmX4+9&T)BZqT$`xfM zfA9F@c|NGG(p(l*6P7om9luHG@G3$E=G3d^ior7(NI@Vo2ul40t^O{T)|z$mUjc@} z0%BwNHzjZg9OplFPrn)PFwv5k$pGntQSc zkR-?D{V-dGAxENbIr1fN1`XJOVK3k#hB`z;jMg4M04#zl*?Dxzm9>Czkb|QBXXL=# zJ_=wzT9PN(hh@l1yZ_s=v5sUk%fwyd<#pm1A9-CkG{4g!>vUy18$MwxI&wK|46>Zh z!~y2G5Tw%mXK{ts9WsQ6!y$i%8Rg#WKes(B;7pb_dx4Wc>~_yiBpO|_g;cI*EDgYW zw3*T99Y!nJr_y!sNSW_L{{p(QLz*|TV^fs<06D5P$rR!| zQhw+%ZHR+8TNdw$$;nVqu8v)qNiY0ar{o_~zLuD8?wz{P%PD;ZJ6fI&dvMylr=>$c zGtxY`^oD77@Vl9N)dn&%<7{>YFvWgNaa)AQw6Ldd>-P1Ao5!iHuJJt=j}==OPK%OYbbBSZJkck6BW;V? zOHk01np;n)gL#`WU=wO@jXdx&4tch9+tq3W%*ev)1J+J1@3t&-uejX5jEX~CsikfR zt?6wy^IegC_XK5_GD>s_+#=)Hgmn$EPf<^Z#WBv&aklka<0jwTUmK;~~{y4U+H7jSdLOPEaTu)jBca9et$ zZLb^7CFH!Y4_n2e5X64wdC8bYKQ%Y_80)T7pLQ$j%*lTbOPtvER!C7Pfz;9p(imLF z<{2M>sb0=5zg-CcB0#Bx;N-@>yYPOEd@i9^kPy=9#}ewnTy4D#Zn)J51k($gIERBl zfK4n~iOw6r5dP2lb$AcyK_{&%cC9$EBwBb!eww<7b6KdjlIkj{Ds!y&WmZ~SuB4ir$TLAI^=Znpp4 zxe$GEZquR~d>sf~x6R{5n zonwY`_jKJVGIVb%x@%8DupwS!8bfd_yoD^v&^hHCw>`47f}I#by5Gg`c1XXTCEfK| z&n|dYQkGoM%oZEejP$0xO^eq@nV&Q}pRGrkLIJG1I6RoK(BoyVRN5OG1eTJhJ|K39 zU&J4*oLA||=7&>u_^Y7hkbl7)wkg=Z*!*{PhiqSa$;c(FAZUXbTOy|M=bS(J5rTAZ z>T5mO%)`|WHhjsRE{RcvarLndKDuUbLLBTmt{YS|%uP)uYT~$HY$V|@?KN{Zw(3*b z&a=a$+7H6!QaYVJ@UwR_en^P1O9<%TTfqp*@X!2Apd{(l{6;zMXd!H$ zo$G63(>i*SnH>VHX-z*{*6#J#2)9b=HlRPto%uT`QO`L?_Vrm?#eTfN$K2u69!v^Qh+AF(XVfw6#|IRr^SCuuiSCihgD(P*2%sgk@qoVS$$;mfGnoYR#U`q1<-}9t`C6~1e)He^rLAq8P+AjW_fX<3?eG< z)DjTPd$5@}I>#Wfum}FCXA?JZ%zi8SxjzDD@XWqL1ih1-uv-T<3p)4bX0mN#>Usi6 zFE?7G&AAt3U5TpLj#E-K(=^gnjgS2dc9I+RrOI#jB)wk?T2?%1HidjnY>TLqIQ zrQ&7;0|9_c^*%pfF(H(4^=n6e#g%HlQZI7wHnuzWI)C;!cKlSC?mq$Wvt;haJ<_%{ z`y8!b^7~&q3#cp1xu-+v+QgPlktHp_l!zI;=qyZZ3FIS^svroNv>(l zCePftLBm_j+$4cTfnYc!SH3HHUQ{wblBYCP^#i?9+PM#ky$bx8dnKYEcOXrhR;Brz zVXgJy${(|%n=jT(I>a|}|if51Z zogFh@GN3DZNs0B+lyfJbvn)|@Rni^g8ygnVuGS|TV}!(-n{24E|ESph56Lnn z1YEu2=Xoy%!udf~sSs>zyi5|Lgt(@R)#5XcHp_?-D!JLwTsfm9VW_k*rk{l@!{4!= z%O8+g?%3ruNgi|lt6g2|d^NOn~0KV>gDr}5beACov;lh)!(Ab0ep zq^o8A_t}B7MHR^h(8ftuZ^^!NHsRJ81a~4_`T2(ksM4v{w)q9f#|{^jm3;@@4)NLM z_n&cLtR(f}HkV#GIw!W5_0qyE&M&^gAy)jmE;(2(Dc>um512Hu-mxEB-!S7i^4#Oj zm5hGQP2N-;HFFn=XZ~redFoF)`KAiWiG6JOCY!Ou zIw29n4d8rsYA1G-jjVmiFfzlSktqzRBwC|YVtDxrO&A5kUU&(hp~)gh}HSK^!R7J z14L8@qf;nd#Crd(P zJ&2r9lTmpU(;pC04<(fRAZ<*SIFZ!WN$=L<798HiRTr6q&^&pPZkLMgtmgtwA1kB; zb(7J@8U~5bBf5rH(j3aPzj6r>N@=W}(BoBPzjBq(V_A471Yjiel^)3E*tgOK-$6xt z5BdTCyO#{6Y`xc^CUe4W$30AX+-8((R9*7tpw4&PhS+t?FIrFf^k(3??v-Kg?>B;X zRnb{Nvw7kARb+DX{2^EpP`m-=ua>n(U-4hM6(}DCn2 z?afn4+Yj6Oy{k14^1Sab)Ufh%|NC2HDBlp$BOQ~vL)qyYVr(6V{f}k-UHca>rYW7{ zAb)lqPwD(7Ys6MpUD87ij4d7P#5Y5N)0zl|TIs*dtdB@W=9J7yrZ~itWpwA}$Yda;d2kIzqc*c%R9QxEfzEO%|f@{16zi$3FLV5|L3 z{mB@*va^vJ&o;4rGk|_C$=c@=acD9C0&`gJEwHc*#Pzi@6yprAR~pWwpv=A2)k>OZZrCQ zf{D11AzCuQUFc1MKHb57R+9*vv=yJSOc(!*&N+}g+eWZNW2mduefx)PmDo7O(aNE< zGb9O@7i-Z6{^1liA!RRZckp5j?umUv_7@m>tqI;jnv1}mL-F}Abw4I2GRc%f1p@`T z)Qks?LIgz?i}i{3ip?1v^@GpU#RO{hFZedE4Z)&GoMnWl(#ncR3KTeGRvtHcLT+9U za4xUUH=NJsCLy;@*xg*YvD`npc;aGfJ!L-zc3Hd=vNB|+h6GA(pNe&bz^w7jTj2bA zyNK;_yT?=#Bq^uz@ViNel!l~WN-T6kw&*YWSvAQ)UJ`K@4r&mum6-||GW()OR6$C< zW}9S62zDoUd*_C^GiB6lj@68^G{q_piAmS3VC|P@#A@|9e>v>mc zQ(~ymk_$toCVLq&#rheCb`>nxY}e7Rvj)7AMmvPihTv1?G~-g@o^`C6*TjXVS2OP= z*;}nr8$5=Iy^K~3!)oSHSIq@r%Nt-UEG0ws$;rptUFl!?cjXfhO?+eG4p zk{GX}Okqv7$19<&rn7#x`%L3x?2j&~6VMb!d+n1n?A=5lXZC*WOJq>plKBU`-SBz$ zo+Sv@X0!G^hBEB8*I9&M`c)i$$R9Ro&WtaS-l^Fx2dgdj97x?pP8}REAh0!hk6acR z`5X4YOmeOznv&|lah*|7DA)ZR3MU~(d@G-NtK{J$Ji|eW{!ISomd4IJ; z1)!;UFIWCLBOMMb{<2=y#1(nzq5YPp`XKwtCpOmWyR8Yy2G!VhrB`sG#7Jm>_^61G zhf>gHu0R&aCtmw6SH#rXm)LqI+Zi~i)(qDNQf-lnhyq=Da9>FF(Vyk5zm@hj1dW>V ze5(M!H6P;Np9F!*0|ikTJ(%?!CXcQRzio0aA-p)hQ34>YhQo*UI9Mm4rUkGW&9NN6IotC0M;5i2o(N$f|0pcF*Z9kp{loJKjB~c}qdhkX zv4d0RNcvTR#0>R18VVsxAsB6U!6n?5@+Ej@j3bgoI|FGQJ3O!Z&+}aeQb=QRCzWY3 zuIi9}b9JsZi!X(I(q`Rh^D7g8!yd)Px#DWIV(pK82eyi-k0BUB+=MhK6=y9aF|))9 zL3m2*w<^P*lB*FOzsIcOw_m6A%eLg}GYdApl=A#VGLZvXcjg^QkB%PS@X?}WkYK;P z#`@x&O5!RcY%+_Qd3-iqbw{#|&}$Pr>n0n8o5UOtE9iS`miR)$_i91%$x4#0^12l@ zu#DZ{lS@A~ILSE-Smm*3E*W3=^;uHBc^NOA-Y~8NMvmL6V@;ZZeOP ziy^1fv$N}N9AZ0o78?Bkc`b_@sH&0}XpKqw%<%fgl3CH$P^Drcq_bu;$CVR9_B=%A zk{?vWSZ%Q@*gM|@hd;_%an7lpz5{k$xz)R#V*?m@B+0w&l=BgLVc#HUv0)QY2-sYH zz+X65@3ff4N$+dpmh>NKTlcfi2~Kn0$GnRsq?zxWK4#E0)q1ttG|eXR-gql%Mj3$L zFppux-48XL@tGD}iD+q0gMy@`|l(C)sq$BbrX z8YFUZ<%{2?#Gs45M9T2Y-M^{5GB8=Mzc!H_;tytmu%#U)r_oNRir_6k-(#?GgzcOe z)gx`o+h?drlzT!lRo;;d9QSZ8MV7tA*k3Z{e12{RVwn#K#NG@be@62Phyn=@IjI+sv#9uyV0K{g$Y|PT<*l)7wBA7$r(^I9{ z>(+$)AB;hbxQ{g&c9xO6%;a5n9T-mJ+Rn?zd+OFB=#&Qv())AcidiG3MsIs?px06O zXmE;cITtpeKl`Kkl32*<#|Y1e^0L}M_ps(xuTuz_nTmw92Y{vo;sc1DlIjhZZwbfm z>$*52g+?5}q4(fnKhF+3;d%SsQu?Ih24-a^`&b(oZ;$H4!KCHsMf_?>ln2>;lOm9` zJ$l7pqI#(;XylQX&)#q@q1m&JWO2QJuf(i^%!!)xK2K|&fRG!Q=OlY%VE@kEhr4>c zLG~vM=gc>=m8`P{-q3_&g1dQ}JEomo+@BHU#go)J#cz888Cm{(uz%aTTk^b{N9tU& z!-eg_GhBs;pM6Ir14B^M21dSqaP+<(5Z46p^Io8^ z{*Hi-z-Bq@cJML!bvbGOD%V{;NPs^Q1MJ^#Y$7mm$=$>I`m2$t!{@6lUmbfUNTb=o zpt&6(n8?|CZLn1{`u+U3IoDWSpYy9y=5-AAIYxg61ipNCNAB+pQTzQdaj`34#>U@> z?+J;q^E^14;H;31xjGRyjeN`w#IdfEZ`F={mxJ^T zrqHL%OSp5D>dL=@Ra2Q(YY$7J9DfM>{Jt)ihD@>{Sd5VBvhO)yn6(am*`Qgxk;II@ zU#okS3gz)WB&a-!|J5}tH6Q(I$j9DuSU0IGd%%$FMQ(A!Zl(=!KKj;+f3FRgUljk8 z&Wqp8e1XqzYL<#t%G3pMR?182>Hs>{`;ra5ixByr+s&1UF38=Fd(Q8Y> z+lg^%&#R8q=j3TCLrNTp6G~z(VGs-1eZPBn6U_Tg%FQIwz0tzwyf{4eb|klW-B>S` zy)nLda&XEH!0)V|HZz{UCIY>nlub{4CLn zz}J;m&8&e)1~K;}Zx$SbawDjl6^=xhD9b^3e_RO=lM#@dvioRnd*G2ZRxgHv@ZZKT zB~3E9aL(l=*C1omM)Ezg5t{7dKPM9 z=MGw%WF|+OS-%rAv#!rO{P}l4@N4NkpDKl%ZtaIqJQO|C+`WITWSFY%8E#2woMI)Y zi*aDc))r2-!RvTGI`N*zDsz3vWi@mCQsWe)*?w4|^rZ(tk8Z$5KN!n{!Eq5tq39{q z(;9*VF)wcNPSs{m6&c5o?mgf>6x(R#9XS3-$<%Fk$*{3^V0|@8R-0rUolc`B*15 zgDZgEPB@P9osa#iwG9FBlJ0bSg7@uLtS^T?O)&J zHufwv47W1~bI-T+`~3ibTk45E2i!{=KpQ*M$01q1tNrX&nbajXN^kHE;490zjy}ZI zge&nIPAoWCtSa-jt3RoXxq4?HA)f1ZTp|!4KS~x>Ynx2g5Ja@)t@Q?PDTHP#8Peup z3Lu&)E%c-v1m}9!29Z?v4k{CS?5k|aYPRj=8!}9`rOy8NJvY+?DU(ut+j#8D#J(i( z*S6iH6n5raADn_+R%scA)cK7|IHS@pKP{6KjkdLReIlz`_?DUC{eP9pqC$sms|&Uu z0sZb_uil0S-YT-lVzQGTyk*&a)z5>DQUAjkXbfDT+P{j!gMV_%udSs)vu zVnhqNGgLiP5(W;wRx3%|oT~erncb_MaGolkWWgjgvetZl(18r%OZ(=_^!pP6N9=ix z-Va7d>&1ea_?+;#x^`MiBHC8vYSq2ckV?pn*%bI+>q}Jz-^I3D5Behq><`(q&6cuG z6QVa`>{bu*py!^EgFXKh+_8KJJ~y`;-8;=8Wk0@dc!!fnRodiBlimyW_6AU{TWppTgK*A`CWUr&H2z6>?R$E%B*;&>)WZCDs0EWH>M~i`uu~6glYw*w(X}HQD6wRVixQ_&(W8`7!<2y)L#7P8J|6Ns zFklJx!hRDy8J#K#xl*#6rMEITYCh#U3~|5+ZKFh?#0c;t*r?j~SDdwXK_`79ZJYnH zhP_CKpv3HYhGY0_Wan+B^`pR6{DSZz5f z`lJ@zk0|Rt9~p^&vhP)z7xF`p8=CDEkFmFNE#jLvxB40wIcx3nZ-B$8%k+uBywfwQ z?9a3EkupTZ*w_j&X?u7E-+oHYFlCsafxY@H(cu8^U{w4&bfH)$6c+Hx!`@RH?~tpt zW?}d4Q&7ha?U0=;YOcL=B>_nSO2vRmMGvzXawbK;Tzz5(0!Sv{kNrJ-jzC)g6hk5$b%r7y>@wt!2?oD}edby4nyT2WK}$kNZjbU= ztYsOk4VibBrVW(v3DY`mw_5 zP4YqFF2#uBdig<_|agd*b1;z$eFsBVh=X5(|K0>))>1MdMN9( z?fYy6OR~+IiCAs0{crq;30mX?{Gk`$NH86YXG}KoK)rt=tY%ABtE{@dVJV|@aSRo77|9-F^`;&O-8=xCcL=Qp| z&LtU&*+)F73ep34%QIFv*U#MJu4(0-bytEN?KQ&Sl{&e)7~fHePdI9f^|y3C8TF$i z4(em8Xl)r(O^G*bhh}leT`g2l?C@l$Pp!i4pvm;DEGJ zX&{#I6R#fSO3lPp6QU`zLK44~ZA>cdW9r!+!nihTI8Lg~XDDV-?crE~n45h}%yF#t z22lM4aywc2ld0n0Pn|oP?#TDZb9j@oxnr=5%P`>VPY;|$WUB-QWjs-K4$Jm(*4f0t zopJv!N2|SMfHj#+J_=?v=J02qm?WR7g#qR%jCoZQzyPxWft9ir^%~3KJ^lT+Y>g ztY3oVgQcn?)gwJeo2{;tHAVpH5T_k!o-GxvW0tk%h`EOTbF$EtcRqdl@za;QnOCi` z$i2D3cxQbRrh9S4VBi5tl4Y2oMhhw-1U8e+ z`NoJ@U68ZfxsA~o&`D=6Wus+-$@Ox=*zPqWQqSXa*r8cWz6TI!C-e_vW@^$!_`=*_$n zGmqZX*i6dnSw#jH$F{G!l>d^rlXnvFoHaV&-(_1;rd(Y+8;_#R5 zLFW7h_?)ui4|^Ybm$^C@PxJy-#eR;|PlB#@jJ9}8xHIgb@D zcbNk>36SQ)=VRL^w)JMuiaiPO)?2zd+RC;X&0EeEt5o$%b@+jR&s+}^HksgFPwZ7~ z22Xtnu!!Kh;(t$X`EXhk1u3)FSfeKdbuz??YKY(s@61&u&PIzX>^EEZ!#X?P z$e(rA=RB5~IW`_94*Wke&gzan{ufZY!J*1ivPEJ<2VTt!3t4aD6nX~O{*phf{s8;g z1iS~4DqukM*v`GY=i&P+d6*ObeKWVSej*hB2C4G~MHzLMEdLc?J*6l9=@kAtwgQ=~ z_E1&L;>Y1e4JF{%^+k4O^)_~>f(wHuWB*eni~T&{2hH&<4jZg-qLHN}R3@_9N)_L& zXe6k}op#{Rr_wGF6NXP4@Ba;IZ?epy4tc=fwt6d`;N}vJc&Cun{bxNGOi7a>M-pug zSRUTs<@A)gvnF@_eq<&F*(S*5OQ+pIZDCk|KXuTp!Oq z2r5}DhB|1 zLN*q*gn3paeVmW&(+QcVZ{*6~4KbfHIgxYrK%;IZ?=J!Jv1__XI*Xatob$km@eVwb z@7N*MwBT9(4n#_0ThK{CR2I7U(T7P3&vAfZU+5In{vQN!Cx(4$?Ularp(L%n=0`~q zVX~*3T_5cXHYMhZeq@ku2qg5&vqDQczTHeG=C*gQ=aimVByw$Pff+P9XOzM2AwrTg zGB5eyOQJ7k8#XS*2B&ocH_i)${vx|6Ij~M}-vJ;B2{7xp1xsy~H&Q|HilS=we5`l~ zOXZLihiwzm$pF?ki_reAM-Hq-)+~tElNjUJ{Lg@ZR4~NhMVMJ8Y$HaRp!!6~GDTW) zCJ=Tn!xrNIay?Y_?*%84OH5J*_T=y)#FvGM!R;iI(lhGx9tb4e;(oXHF*)Kgsb+MzYdS2nvpz)yM<#6Z4mg51#+2R#MH(*<>)+r@WT@!ka;zL1pRgcy~Z&C0vXCj*!DfF}C}ywYrzC?tqWQ4@%Vtm`#w#UbbU zEoXZZ^f()nQ1K3b+DXz|#xA7K+(lBlWq?5h}M!i&Tz!O9A({^k?z-#%$*oDTPd)+Fd(C?WjZHpqE(rA*Aj4UHN%gChlj+~JBj476=)L#xrL0&= zG~&B`0if+)+{&$6DkP~POT2MY($o3e?Ad2#K&D=A?|b~o-eGO{n&FVbMkb)G4lQCm zD&}1r&LluU&E8>eKRcvaJC_Xp?ddOsYCdQ9K)j_-)^QlRI;}&8`dHk2ipXJ2t{3$N zSIXRxeYbG60^XeY@cLejcAkd@;z&xiY3JS~FjW7O$vezduzkps_~m%0BFvR#A`990 zC%4NFs@+W5>9_B()%U=6I%-m99}=S%J^ zM!;Ws^A4Fae^2X`-dAo~d>5)FAh6A79f;AG^RM;rIR#ax-1+*l?pE4GuQ3fhBW*!@ z_8%B95L7iUcn%pH-FK;AxjQKE{zZV)*Pm2D66r3x=M@tsw!P@Z&uvCFq-fcyU4Jy6 ztr#cMCV|9l0X=bSGX(7>pog0KvEb5Pz~ZJh_^K%e1N&#&A3kExVsP0fV?3i&(F%O| z4McZ{WBO=^{Ts1Q2YZ_87j&vF2Hv5G)5xkxUB?X9zHN??nKNAE#4>HvGAF7(bG?EQ zI&n_b7Kx5dEp}_`H#t!e1~`GH%>7^?GdeF0aDtK08O}TBR^&r#w5=aQ(nXmq8mtm< zSmOb)+?$z}yvm)&d)a*gHQTPj9-RcfV}h;a=j~wg4@sM#y}lJhOIBGYy}bIoESMr- zNBOxRthwoA#&OD=?{@v(V2rDv;nV*~9QyX2WxLv;(bthVg4 z*8Wy3EJ*~DOtuTwb^OkZiWy?9t3y#q$rx;Nk0t3By-k}tGLU~i6JkO7i9HBt$nfsT z!@vTS_y)3py>ykl6=jomX{|%}xlh4oA73HqG{Rt@FnR-S4wE4}GWo2`wb!UzoiNKl z%o^U(RdhR*L*FdfGJ?J)3#)oQ**3(WB=^6J^ibyU0jjyTTwPnblD?8m`3~E%_qgZO zqNptm3~u}qmCC9bWVY5YNI3s^91MEO{#9lDWIy_abyM=zg+bxVhF&Da0oMMf74fhe zW~$DDz?S5JM$}IDU5^c-OG^@9@{VDp+2b8U^n^MmFMIhZ2oYu29CNcymn-@*}?(*mByKw1(TLZ51+ftWRY&%_d}59 z`Md8TcyiYC(H(hu?%=yBWq?tFWslOxM0k40c&2T9DHGH);BdA?Ijml?3CQfJdxg@Z zkLWs>EPHhv$y)FbydaL|btc|A4LFC)Fqk|T`;c&T%S6{G;aDVzQl?(KVZA&QGWZon zcaZ4WOqJnUG$Ay{eJ_Vk8L$Ww%l?mgTNQ?xBj-GtNl){r>s3P9lrF1$-H&Sa9qYVz z{Zt>NNW>xnh*I(VOA@HcWKjAQHO%(m72$V3bvAX9ta1qy&T*(OEaKW(4hF@O!TZj}3jZxC0?)r7{E4H$lr%TG@% zI`|IqTOs_Owb{u=<-9X5Bl_3Taa(#ysK1l3AuiTGiSxr^QFT)@dqIS6ZLqLre?mX0 zxV~}5839QF;<*QSu&-5DK1^~Py;Zb#UHi3@ewC2U@9(?T#~Cp7`cLcGp_%}{GuFRM z>y9-HjZ`!0x1EQWhqj4OF-J;I@f1DvmzdMIG9*_g<{tXAt|5q)mnxkxuGtK{EXyzY z{Z6f{Xnj<2=J_kY;1Xut3DqEt&tMvC-?t@YGUeT*PrzpfUr-(7_K=ad`YFXP-wf70 zdtE{m8v>)gHmSY1$8j(lm z{R1WGgRk4NnDeVdA}Mi_*W@gve~-8nlJkKux>c@TF9gRctqsNP1 zY|dGY9%M4Q6f7(I+4hVu+oGf2Dyd#gx$QWgP2d~-Z=^P{g$bRrnT?76+Z!W{A2j96 z^UJ>JK#pGh@fOB+(0oktu=ZK(Rn_+V2SF8yT$0Y|L7)=8lvKa^r~QF$FR5w}R z%6q=!48Ampq0$<*fG9H`HeR`BvH>_THXo!v zqKN6#I(L`wvzSK^SL|K8#=; z&AfK-aiB7?DVYp-qaZQ8O9x8nm5WSU1Sv1)x6Zjnp%eg~0O4q7*hQb>$9v=}h+dfg zg7`PMuBQlAVOXOB_W1ac@;&l#rb+6^=UT|;byh{2NI>DaL6}_f*UK8R8QO4DM`JoKhMkg1ibldOR>Y(PO1`6 z&H~*zef>i!r0Nr_odenRd1uxV4e=YKbqeQ602aARzTJZtcPZ5|Zk*~lF-W$&;?Inty8f?@BO(&D_CE6F* zX8_wVD?o5&ITGYt4z*v_D(rjZjFA1}h5~HPmZ6jNU%y)85@oVCJ=Z=5q!*EH$RdkA z0&G#S8mWgME&x^i1tz2kw z^($qtNf>G9X!Dr{GhP7^6&bS$gG@vB>P(e9z+75vKY5Vx+Te zoI<8zGxEpLM^%**ZI+n8x7b(TT;%<|+3pZ{SsPc@Q;KT&9em%c9hy@*=aRWtc1@~{ zRLB55K*GP^XzCX^Pa=-$>1=RW<|jrp!G4XTXDdDD*8$<-eMhz*#U2#l$tIzYVU1@#XZCsW+S$RcZnPfk4?(8iA|Eg!8tx$EivFNiG;&CFPg3;pOv3g zOjBv6|A%a=(!4jpk)&x+MdTO&VxmKv<&6thW$@KfZA)?`hDg;>VLy&~xQ)KXZDJL> zh-{9rl1bNVztYl}zdHIK+t6tvVlz&w>OrTB|8}0hobPSMs~!m=KG0xoOP`qgy1_Gk zKX|zwfz#{*l^IlnVq?e?TO>6F=BzsL&o1GgtRXsY<+MNC*c;Qh&a<3@Onei0`jA2X zw)FhpiaT?WiI*k)IL0susa`yE}8)(K?x(0t|EDFg2yx%w);n@tj9fmvFE^9fW)G zN4#5Zfb=`1>wAu4`9<$hmV9(nBK?_jx#VE7oXthY%oVZV>!ppK63Q-{I7*#W<2b@oOf zxH+?Xw%>a=4FZ8Hg`&E#kHCyTgOW#AT=WL{GunRJZ1~7d7EVr=SkMAf0cukc?U^3Y z>G#8uVl2W4mRN6XX5*P6!#Ak}_51I%SceAwS7-5y0Xgem|=M# zlBchJPHAt)un{z<>6F2xJnqP(2#&`u$Ir$=aOs~O`EzB>J$qU?%T+q|iw*emyXVRk z>r>rfby(3&EJ-pfa~1+0J*ip;XdyaP!rvqQ+m^4v1O9FNxAVo@U!PJ+KJ&1 zHhTN-J1d{DTsT|D|4dUM;K!XAXyXfr({9mKntm5(^jjWaX8~qF5jL@7+lKE4AiNCr z8$xybdvjvHvVFMZV9)f~fb+I#@$*;gSI#%XK8K-2elY{uOLXkDa!!d5kU$~EKC5_- zib}ZZyAxbjgnJ24DFHFMEPO^t0*JF`$rL_Z!2`MR#WJ11w{x|ww1imXJ2vxH;Ju$c zlGGVc>;3CVCiCi!RW#jZ-$jgY&YsqQkY0t4O+b{XibK0+#J7 zMY#44jVz9X(Z-iWC4#$~lJO2U!hS85)ZF%wavfM2Dhm7Q>#G8gkLJCD(h6CzO-nFL z@}y#)qD!%#`Q6B*iOg1OucJ)#oy?Ew`svt;-0aYtRLEldTPoXD$vg4{H3=^D0LKL1 zCD+|T7J(1bUoDZ=DW7w=E zDS5vk=$<>^l$Cl<3JjRx+1^e2M{uTZ@~E}VaPo?l`twxuw#%FyBw?$y+oK)is7KST z33dJe8FjTS;W2!G{N*w`)KGw!#;Ek9$q0iZSxLKKThC9?dZD?vzL# z1&2u)4$DOgON6e@3;pXM{K=T618WJ4S(6gePXoR;!$h&??86{$ebwpz!zm+ zUgD43T69Ur$iE|*ind|O?CQWZ1`Ns#L|&sCZj>$9>s^!};Tll)TVD3A5R8RlbDfDk zLi5^UYwz@>Kj-|(#)WeYdBcmNi55azlF|;4 ztfKwYhZ)957#!FBSpg#EL>RsQjWiCN7oj8zDt{$v8Fnroms5kvPy^6foDi$E*gr>+Y&CTpg zHk0R-uTtYbq5@uNCjVrEaP-MgCJW(;fy;<~TSao!(eiHm-Z=>-V6g!ssierz$ul=- zSlwj5Gl;JM5&qn|aK8t>Fxhd*Ot0hGvq4lv9=2g!Vgd?AKK~LDP4F>&v#c2J_<)04 zhxRE5xH|V_&Pf@yWi}kl2RG4=snt(aY5F8K7qw>WW^z<8tntLrLx%O(JTd%jMmp_s zz196+OHcy54B-4l4q^^=BCi){ImD1pWP!!U5g>=zr(B!0_rX$@avc>}3I)d+WcqqZ zU$=F$cJu`6)p%n&{FCOx`kaBh1# z=vi*BI1~HJ?losuQl@}!5ELO;k5cvnnr#8q(ALv_Ig?*rk7X*P3<7onAhR|yA-eCc z1gqOOT)NlDY037Y6fmAGM0jV$$ClWN3F03ETt{hBm?Pw^Bud^@RLEf4F2Rk{rS;2+ zfB=WVz9t7An`P~sa^`(z^d7B`y=2y0*<19P z^|~&;+3P}sf8z5S@Pubc}$R+`* z*t;c($BlSf@ph2G`K%u8?|x*mw5?b7 zHmL-1=T@MK+~h|UoS2#1MS0&(kRY1%#yvM!v94*)(vlc`t93NjSKo86uRMNENQ7^a z0`M>;+S|tadYK8GLoi#P@8_ltwJOHU4Ocvt9K4Zq zmhH@GfT*U_PqVyZl|jB>^i0+nZy-_zqF#K51nmZNN8R z$SjH6@>J}{;%GUBiT|DY_!zL!Ofez?GKsc@5x+~4U~fw!u&(UvfG?k5s`xkdB*_oW z`sWv{P?LD?qaT@xGy?(Z*tgWm?E1w*S96s(C$ZhN3HUie9v^MmaDthx-w3XeF3}#? zlNlMg26M>(6WY5Jg`r3SI1WGvbDZ^MeFHWEoU{%%YdgPrMm7`SX&+<&S5AHuMiU@B zVU8(Fs@aXk5^DBf^SznAT@VmHi0tmHa}n0$Y^FGUx&hKDJ^VVX+=pI^KCW#~u51?Y z@xuz}EF1DLTnMoWC9Ui+Krrko#Ku8KeLd9R+tV-IN)MYVSrosE=a&#tv4=b0W~VZ@ z(nB(0a72Lmo_9(?eOAuK#F6+8ZnNtpmk!}@VoW|lUT`S`(5q>e*&=s#))o+M>W$s8lH~n6*#^oNCq$Hg{+5{Fnc&mwR9)aBMAVs^ z_w)W~#lT^$VGL_N8w)jOd51ej3u{TD(=zE1hXUbsP{L&l^Zj+@@@oHF-B)_o!dSxW zGbqxx0;l@M@4g0JE@VxhWYHi9z!`Q)@0cXWbwyPP;%46ycjus8FoB!}8oG3}?O0PyPg_Zw(d9cnS@8=H16+`;I_eB3l7kyz2fOCl zH-bb|b#-#ENol!C$*!}aV|#$yhIrlnd|f^K)I91v00;u=%s6q+ zaW%?NF!tcgPUVgNXfp5>|3tukouUITb#HZ><)u{rVZYzm_9`amp<)nfkau2}+unRP z@n{%!Xg+eNcEHXynSN>in^cZ^&-VsK8N}oPb?*gF-3EteG;gopwH+f^M0e7^p8yhX z#cZ2mrbMadT?|kG*ZYpgHZQgUJCoAM?Tzh`&a0i~&vS;fReEbl;mPF26`YwHZ0szJrvui;o_mNS8U5`amqv$MB?b(Etg)sAbzq?kx(*?q ztiFHmKwKEary^!TDCdaq8(vFnX{@1rm`a?zR)04qG#8`2ymQO_#PiJB!`O<`tFhWl zJift8D(z5OY->ed<`SUszrlH#D{)|qqREki48M(>3i~4DH>!F@WS~AOSi0{J+d)9# z%}9@JX7XsWdk!}H5`z-eK6MFQ0+(&k`JF$;dhKL>zCm+Hmaz++1oE@Nt-t@TJ@X*9 zqh#hsANwCHL$HKVVIO?fuDp3j{y8jcs<7f~UEM%bQ?XYee=I?<{n5upHoBy0DplFy zcYMBj%7OFTft*ri)Z{N_1*H-u>1$A(lZKf;0|wi~%EunX+D!PMB*k(Tb4itUVC_JD zr(tOHDpQJWR|%SR->;%|ylj#l^T9c3-~Cye8vo1&ZENa){jg^rd@3bT8C!2L@LWG^ z1JAY^VGG7o(@;+D9iNqN$#_xCjRIH2(be)G-9Fpu6|8E%hGpDRethVcjKtPdk~ia_ znJ4z8y#J|C(FU`e1=!#@i5pu|2Id8_UPPdEY~t)?}~x&WOZs zFQHN8YIY*I>e1OOO5XXvnwC^2rGE)dO1@fcR=A%S)>v4qX27K_zScN?`&N>9x%)lJ zb!0wM0@J!dUVpMMCx&=h={wk=JDj6cDpX&Fy+}JLU{LRp%iJXu1I`8p)thKGFayr& z;!}l00iEnV9GvHV&#Apt;IH$|Iv&qnPRVi%};4a!>!Jd zPA~h`IsB4po?X9OC2R!4@3~6XM?=};sZp7>+rF_FIU4{e{Ynq^`Xqbs`QL%; z@DQ2%=h-m_w>DEXK}Zm(%{@Q63{m)1AD;kvfNJ9ASbOYlSy#f zsl3Uq#jqaUm6z|2GvN2lEMKwHG{A49Grr;oUDF5lTS4E~)glL|*3xN!2d7)6v@2bsRkN<-CTu9NGI52?`k#heTvF zA!JJ%a0gZ)s@AUwnOc6oy7IY!mInsd7W28*N1vrziZULylOSx+ui@Qy;0z>*vpg9l zWroEh)ByO>MwNgt8FU}edyaDD;%&S2`@2$$(j|a+-nBKVXOy9;F~>gUTDy_J_5xx^ zvcnqZ(UNx2t-0i414NGSk5`&03+CCfom}}_8}y?lxO%$e$x89vRMjY?bL*j`bS?Gb z1l=qXYl#nDd-do8>SX=B1_q$X(9dL^ge(JIGHaz@ttH$o94&bk;<7%^&l{)RU{Y>` zZTH#<;sH?^_`7#ub1h*4E};YQP&Pa_nP9RrZIAj4$;Hkk+C8ZPfwa7)`NEaXY!UV) zvNEEgV^~+OKH7a2M}&Rqz+!{#E$OvAo2`H%7(5d@Wu=M# z-OOw31$szmrM#T|)U&ctAGuv}Y`ihCMH20RgDk~>iTBB%bjVsm0TAYV){J&-xI zd)2!5Fcw(YpC-8%fKl0(9%EO9^}o7YIgmB5!@HKA7C5w(5||~>2jh0XUb!kgJ*Zszr{rR3t4DUwBT;uxH-u zt^CsF;&q8Jo(UGrB7#Jv5H7?d%6t829^$WVoy1QzYL=^wZ6N2$NyB_^&NS5%r>^~L z*_odo$0fJ2APJ~v&-PS&s!5F#Vmo%^n1ty`^>xk@Mof*}8L1OyOP8BWOBfTj>;WeHwxRC+^4o0!+-i3RO09o2m2)AoM zM)ZG(pBK*F!0bqOV|78%d^Ve$)C$dL7<4qSUV+5yxyabhsCItX`SI8bWnpYKg!g_SwnA37bQ@PHJ=GH8poa9F;&clGvbwD zS<4Lhbhh$g;?Dw3*sF=->6N2YB;TPj-v7S?L84MH@Yr{J=*=pGg#8$tHF$e!=i_wA zgd-!>*75YjpOUB1j9JsEfU#m<&AQikjGT0QkH!n)#k9ok#b4Rkh{2JE3^id|;shUM%ok zX;ShEGf6)3_saRV>e(AE(!v#x^K532xeXAtZ$*EuB-mE+o)Z_wyq1_F#QT1#-6|sb z0wEy^+o6&E+=hVu&qx{GS#=*gYdoHngELq>&hb>;a>scI>>Ms87~iZ?&5~O0I4;uE z5(?+-U2_9=;?G=l@Pfled%MVd+qk|?ecZNkS_!fPWdc@mHqzYch1lKJ-UzTD^Syp6 zK=B;1_@5y4@E!B9AI65S9;JU%tEU&FOPN1$0I3hUwh_B*vs?vWS}OKGd;U!iRmxfZ zoON0Z{RcUR7_U--b%Fuxsms;lEw%Rd-T`v;ucJ#NGQ0S2z$LhJ&mlG}S039|L;Or^ zk@sm1D|nn7yZnO#z<_uN{>msM>RJb_r9T2P#fb=nYZf8xX$G>BggXwD}sW84Ey1&tow z9Ak2VMF%oxa!CHJb9_FhK0W044wW_|??gy^!xcYYi8ru&b+K7#D?SUHb(1|4NfVxd z&y};-Bib7xbL_V`sBqSE{Y0{_CsX4j}M*M|?-Bta-9(}Ohq{hw^35=<&MbFMz(sIV;``$*DbQDEW^NQL+c z`!-ix1mB$v5-!fr{iEGSV-f68fkT(rvdlgOF%z4*FTr^cuQ;LM&0P5<9#GIs-dEWk zFd#wul5p%P7EODz7$gnZ1yyB6#S7N2E8~#=HIQAwr?NZ%(d=*GJYrgfT*XkDj7QQd zn|OosYKkKVc%H)geiUxHH%mZ~WgOtNO;c{w^xL%7nlvZFNRYb@id8q3(n?bTU*9d(}F!+nb@ z#FDtW%mGVZ$r^2$PoYFHo&ni29-tJVSp6?&&#D1t33|F0XvDzguiBBheVzXNI{S;U zJ2;kr#q_RZNJ<`;;fp8VH_nQijf;>{5I@Nm&J8Z2-Wja6b&tBi{j#JH!mwUBv+GPW zH*g;e;mVz^=1!ikv+or4&!X3BuzgDaJ>nn_%lFW;Du5mWJr_jedOnlrnRttVBsdw3 zy0@5_y}?mF@kO)WD~;-Xak|J&evWa^1at|8e8gR@JfR7IPT08GR*C=~VF-H)`9fxL z0~qqVf6gDy$b;y14JJ8a*b}%`UjStu3B#jX=4MABoqMg~XyjI#~aIX6NZc4K({`WJD=L|T`*yF-(rSz=FcZFv{s%=*C*Iw|+ zzm^?M+H_7!xn3r=ZeqwmP{9wn=)3GmO`Igpf?482*4*qGSRQHF9k*&ML!vR-4R-u)S};ahIp zt$868=k9AtSEnP-_&xyP+5%?J>|YC}TyDMU|F!GF9fY(=3zx7R?LF)Cxg|@9Oc`i! zGsc+Q&VbBZ$Mj%}{Cr3P+E0I&S-`KpIlcrXOq*;4r*N@*P zs8aTRMq5!ef!z=G-y78d`#kzZ<|cq$0CN7_)kwX=yav)t_~0En#rh4#2|@`H&+o^l zv}YPs5&@J+hm3665L{;5E7R)It3xpVE|5*D(JlA>UHexW?d>9y{pR!21WvV#`N;Sy zw-?w_(8eOnllo69$dPUkD16)csM@Tw<)36qr3p4VpUs`Ci+tbyBU5J+>~obF=Gn4# z@S)u+^NOBGx&xVe?;B5bhYIs^31X9Re=1<|6uytY`k+m((tKsHI&=@6lyg@~$D} z&z~~5liNnb0Jr!g-By|{XUF$1{vjg0WMxR-12F*ZuOx0h*z8yO(=Mx_HFehC=CR(B z5H<5>|3}xpBw$;d_&Vv-h_OlOT7u^L+2ErPk-45!r}o_4dUb8$a+tw2?seQ;*cJSO zpkuFSi(7rJTrrnD!B=n=1^!!XDw=_iftj-;x88x|WWOGfDDk48$uT0qb+&P?#%|>b z+EHnN0%Bo&A~{GD_A&d(=U3=t>=v6B47#$8?t?KlY#RKvhFdzakzny)06sLqSIu5k z{18O*W1`B#BY6f11QS-dZ}7GjKiv!gh)?-e;o+BJ|Hpp2c=e;Q8y+d8uQl z`eYy_!ck17lqzKaqmM9TDK@f4ln!SzHj;?Pcog=z&nOpmhh3_AHr0IZd}erq#1wso z;~l9UvwIydoQy*Nrp~T^d&fr$6rEYBJq*>uQq2$VAQGIMhp~j&%OX~%Eyi=7Iajj? zF1$A$dPh5g91Oa0gp$4a^q_^6WseSLBF$!a=dnXSBIs``f9tYYOEs3Xe3b)lM;}^urDOU{uq)tJe0iJ$KI*;reCDqQMxY?qx zn1g)8s2gPXeYT2&TOwjSV{p@Uk^Px;6`})c3ss?HY}KFh=>`fI@VjN+*MQ z(22|JBLC_IGCBDezqAl6IT%M8Kt=}mq@PUsWSh|WWEpCF?aApqk2~rp0qp41i!Fy` z4*&pgnP5D+TQk?`*41YNbI$rmxiYl4ZpbJ6V~|7^)sp(c+05FH3OG2W?FW~nd6et8 zu0zx%m{jU*8ycnBru(yzu~`g43S=|@|K3Avxq9lBGK=hDwB!<3*r9n$MA~4Hg!>F` zn(NfO&jB){v<*!)l_K#ot$FRXb}DdyS(U1q``@KISqz{PawVptjuL=pM_fK^edtP- z#R1dwV~g>bMGzn9HnV8Lti68e#b|j*+!9p-T!4&PP>d`yliTNT@Uwv6KL3E#$MRDL zTf7#}`duaLD)xf@+sJ)SyMGY?@V;4KNw4+G>u`1=)3<%b*QV&)42pf#vzaf=t6Xu1 zr_+}1mNxL48WNg-4s6fNFc`bnKc?D6_`D}-k%vZb<9}rNOE{B_wAgxJ+5@6Gk&pS5 zYHK!2mh&0*)06x0%V3swTvy?sD|RjCSgwkh*hggMyvjU)(vJetJEX}Q`;;_tLOkS~ z1c=H0M@z1&cS}wObKYsxLz(>nG!OVIkRw6p%y3&ovWV7~J~yHXcG3JGqaR}7NKH%I z+Va@xW9?~dZ4+b_es|Uo1sva={F&iy@eol?;3Q*zF~jiQ1^!HMS~@9}75D-F7GmG) zo%J(fj*UL@Hk-Ah9TmOp5ZSw?$Pu>xXmOYN0V97}YlE+_k2@5uw5Kl!Az#3B$?dEX8a>R)mCuLQHE{c1zhTU z3c@Y+Q9BQ+O48%^cn+QF;GcUHB&`{|;kNKpt-SYfE@iKo18_O{hn@T!ahI!VEU&bt zB)W~WoU|f;Z$v|0&NpBS{)lKM=JLgQw~IU^0h`|^!G_omWR+P)(Bgc5{ znN2n)Qe2!63p#-mrHAjgEXQ6#b|`1(v$B0d{;|kz-7>k;Jy$0?Yx48<-8lag6=EH1Of>@p^C-6XV4MBTMyc=OtIki#zjI=X5R@Q!x1wL4?D^@X zGeoufp3B0qf(K)aWZ0R`g8Q-WQSS0RrMmZe!@94o#L;tXV-Tam&>3eluc*hM;1v4U zc%-*&0B{lHFd^r@02%rEkQX*E0sh#GH5{ox*fpSR;S?qry3g|`e=g}z!+-Dj6u?3o zphbwH&~?b&MX@=!oxG|?ufo(-$kXiCw)fqX;oS8q`#Sng9^V~;;M*G@%D0TW_YXsK z0`j%NVe!8;1h*Px;NX@xpZlA3UI2#!cpn?{bF{N2at+N^94v6qI^)mI`QK0))zPGF z=Zb?yVQNi)WVn<4W3MxjS-HxT(m4sGif70JZN?kd3F>*j+`hn%ouNJ^6ngPNF+is< zaHFuX1@RA9C0*&r8ntRg3X2slFUIN(W2~zZgP(?7 zhDlyBM0~z5Fx$TySj?&@zjs?!!Z3S8!vHHw84i|J7n>@+~Ylyii zU2;_pC0ZK9R9wq>k4cv3jlYg;MK*E*@Qaec z*#8V;f}o3C8(O3?)MxFx1E1K2*xHoVdLInBZ(YfJ{N6{qHYkv@Z*@GYEWbAG3|H(b z{#g@SkiGXBnuLWvpMhUw8Ig@%RCg{hD-*!J_OHlp5-5+E(rr$g5N*<_<7$*8Y6z^F z;Bst_Gl;c|)|6FK_O5HoOPzF(h8YMp6%N`$$_Bjn>axfy?-+rcWa9&Ty|)vxI2)xT zIp#y?XnD=J`9P?a7;@-d-meeNkt+VoqP)I*iLQ{ygCB!je5*WIb^Ol3SS~S_GHD%# zWm{-=_DV_f^kNbFnWIcWO|X#QJAr1Sh8|93+uO@H+X)>oLkq071lvdcvYRy!wfK^b zE>S;{gpG>!v;q9lA z&2XM1LaO!_5p=w`r@iwTww)pruPay+;?e4o!ut>>q5AC`{E_IWlBlRuBU~agL7Ohl z=~C6q?o*T~B*QqQ?^HYp9M`H7e6|LYA%&$4bnfsss>`rbT~*E^hm7|+-(UtwOBAC_ zCb&{zz;}kkIHQb(u?fkPMW2&nDEr^0jS^3zi0~%qBvn;8n8QDpwDVOYl}=@v`~HQf z6o9WZQA&tnztd=>qdEz8(WTg9eaXHc8@mOnUs#MTj$&K!Po?@fRvpS|(nhx~A9B?( zT&>OOIyYI2N@V|M_lWPr=O%o!eMXMT(y$~Vc4g{*e_nazT96zHaWd<}hM7VQ&QJBo zPz{sTve=)r_F(cMa%!t@H02iLIXN`@_jAvn*!b-K+Al|D#5Y;Ft}WI$-vV*{;_ad&S0KhgU-bSAg}2d< znl&0JD_ue04|7jqz%usye&5`n@fuIEB~fMxwpiN1JR=0OL#7%4D+<{+veT|>R>Tn| zZ^bRuXx6p;seQ`_1Cm^#iy^B1FxEw@h&MM0GjgujDMrlU$I}Z}@l>M?jyzSD=N%4j zv!Nj+K7r0$qN3IaQD$0^NbJ*#k_168AY~ZH=GPd#EjuX$d#CfKAg!2WAqrJSjG#vLVsW34L~z6LE>BX8%XrG|20vs>yJySxzXa3h zql+E;iJfcoC(kBue>EFa&8I!2G*bj3o_o~N=N@{+Hs?R5K4tjxVbAeZ^8TbJp6#t%I?;&(rQd&yJH|3wg zzMC|>rK3w0yeum6J+XbZZLMSxN87s_pVbT_P4z}ML~ZG{flzk_Vn+AD3FvR!Ky1$5 zy*^SV6K9}P+<|2X{k(A?Pp*~U)K2D>kbU%Jgr(oeAB}XJZSy`Mmo?nZu*Y_;UK5D; zS8E9ut^c#Y&Dw*r3YlOhKC%1Jq}-Uv-D@P;Ij{_xy!*rVbJjOW!uoWS+bI0*F0XBk z?L*g=NoSAM0OK4`u+wPgBWy&{K93Omjt=kVLN1F6h}dWATh45#ED2gb&ShHTRupo6 z7@XWXGQxJ`EE84syS>iES=wYpuQF@klDFO}a2ASHkQ{rfLW1xbXT;PbAbh4Kb8I(` zu6}|7IBO|a{{_#L#PHwD25|ON=bJde7jhNuUUhKw;3MKx# zw&Leo{Kc1jeU7*1K70LeDwWz2Y%_Y3KqcC9 zcCCM9IKgX5>BrY9t*(ar(Aa}a@Ky{SFF7hqp7*Ry&I&xo{#~-|RnP2b?tD*Xew0kY z*~g>_Gsi&P>#Lm$=6LK91{&kuts>Ff_*1K{$Y?nqH4onp&cYAcCTW6KqXt(~v!`IM zC%L24eN3&Q0bdEen}8w4VfOd((LN&3Dzk7yc_zSPCx?DOd-HrdsnVV9mQA>Z(bwYW z0>KEZu2TzpUUD`z#=0>$RoCH5rC;-gdu_sHN&F5y7liM1fC2n*#OE5ahUtZ(!P(4a zG-K({oHe>CjP#|eFeXyU_U4`a{_5K+zoSbr`IbR?Qv_NfmS_ZW*}gxD9^obvNA@Ct z%XIBmZ3O@z$>+|#5{xc0eeM*}t_;emLJvmjjhycZ_L|KC1?P3!C9>~4SD!b6NkVQiD9SDc^feov*xHpa>V z-BnEZ{g%I8Gkw^<)qM8wf|Jqq5MHMYv5+A(DHigfZt3^2!9GqqIanyUREhnKBPzX1 zf+W-BM_@Zn?Cuamz1l%G2}XEWhp-@XVdKlZh2sFPdx(qrGTP&8=bL#Mk~R9zcW39U zZ#{7su9_&$G-bQO&TyjEt8Dk7QpDQftX8rlArV7ZXqa}--b^buZ^R~6xnKFHrAjHU zWpQB811y{kFrAfs?7s4qJLBUF7Tk{4R^1CIL(24Qh`$bN!Hlr;$g{J_M^&$%^VWjv z-Q#oRz*z_w^!9!gpd@!X@0^Q@zEN>-TpsV>rm~m?6g&OCdu2bQgw1ZnBqFn;TyeT~ zN+X>|$v9vCG4_md)lgak>>h8Kbo5G7>KDS|*lyX+V5jG(-vhbjwt`H(X44J)`m=xW zxu3g*cN#smV|efNZGB0BWV`n2rM_no;%6FJ8OvaIK8u};jV5A#=cQr=AGWrwuCDF+ zskA5pxk>esZOZa=vTyZ3(fQ~r>z&z*^zw!4D`P)g71X=#_-U4zjvY;ROadblQ?^kKn6L($n_!RPQ*u7|$~sg;#qW+3Hq~oLuGOI9jV0J@ z*^=~5HqD-tgZ(3dWpVc7A$#Bw??-$gIXm#=+Mr1#0>1SL`Is=_&Q{q(N@inJQDLVg ztRr*Xvb~7y9-eoCo04^mA>v&2%X;l`=nA;;5xqGAblz!C1*C*C*L4e_?*Fi#r4NOkAfHc@AX2I?mQ5$>BKh$ zp~J|hWhZ28)m=kupN5}zY(8>8TGKOI&Ru_tRx-=@$>*_uu}@vnytL5ujfEUL*i)x^ z^Gxsa?jGWHbL8@#9PGyi{lriAGh}^eyRn0r+kG}0daiETCzqaJ6?8F}@9iMBg2 zfrb5vZ4D-p&_A%@&h$ASe2rs0A~_s8Sx&O$Jxzu9%VU3Xwzon~0l35(4pCFaZ*rhE z{^)bf)4ntN(R1x4_mtfbx?~J;m0si=y|bFKTr0=YtXD3%C$7CQ87vZ*{oky z<|=v?10Irn0ffetsi6{Lx7?` zF}EhEJaM$ADY3?QQ66f?3;VofXsqvvv5ms|mBOxjNR5S}cmKxEp-$CY2u3*iLvS6! zqU7L4ZSY|od)E1cJ-^p622jPwA&%|mb`STXYISRQQ-EKMtJ_^&!F2iw2#Wms&(7!1 z6#bQ)b*VTX8`_W+={=3EJQz{U=r@2rAZE#Z}D-bR-rM-Zk(pd8O@Y&bsK;r*kl z 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 literal 0 HcmV?d00001 diff --git a/resources/textures/spotLightIcon.png b/resources/textures/spotLightIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..c40bb4971004fe50894ceda57ce1f75fc751b024 GIT binary patch literal 1330186 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

gPWCXAjSq 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

ByF*Ny< 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%0ASduKe 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

  • ohka@NWQ%vJ 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

    jIC{BAD-lm@gT`1S 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

    S8R;ogIdGMr7j2n03WUa2fV;@KO-MXC5sWS1zg> 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%0AWxBIuQeiVp7%2$_+d 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

    + /// Provides P/Invoke declarations for calling native C++ functions from C# + /// + public static class NativeInterop + { + // The name of the native library that contains our exported functions + // On Windows, this would typically be "engine.dll" + // On macOS, it would be "libengine.dylib" + // On Linux, it would be "libengine.so" + // Since we're using dynamic loading, we'll set this at runtime + private static string s_nativeLibraryName; + + /// + /// Sets the native library name to use for P/Invoke calls + /// + public static void SetNativeLibraryName(string libraryName) + { + s_nativeLibraryName = libraryName; + Console.WriteLine($"Native library name set to: {libraryName}"); + } + + /// + /// Import of the HelloFromNative function from the native library + /// + [DllImport("nexoApi", EntryPoint = "HelloFromNative")] + private static extern void HelloFromNativeInternal(); + + /// + /// Import of the AddNumbers function from the native library + /// + [DllImport("nexoApi", EntryPoint = "AddNumbers")] + private static extern int AddNumbersInternal(int a, int b); + + /// + /// Import of the GetNativeMessage function from the native library + /// + [DllImport("nexoApi", EntryPoint = "GetNativeMessage")] + private static extern IntPtr GetNativeMessageInternal(); + + /// + /// Calls the HelloFromNative function in the native library + /// + public static void HelloFromNative() + { + try + { + HelloFromNativeInternal(); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling HelloFromNative: {ex.Message}"); + } + } + + /// + /// Calls the AddNumbers function in the native library + /// + public static int AddNumbers(int a, int b) + { + try + { + return AddNumbersInternal(a, b); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling AddNumbers: {ex.Message}"); + return 0; + } + } + + /// + /// Calls the GetNativeMessage function in the native library + /// + public static string GetNativeMessage() + { + try + { + IntPtr messagePtr = GetNativeMessageInternal(); + if (messagePtr == IntPtr.Zero) + return string.Empty; + + return Marshal.PtrToStringAnsi(messagePtr); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling GetNativeMessage: {ex.Message}"); + return string.Empty; + } + } + + /// + /// Demonstrates calling native functions from C# + /// + [UnmanagedCallersOnly] + public static void DemonstrateNativeCalls() + { + Console.WriteLine("=== Starting Native Call Demonstration ==="); + + // Call the void function + Console.WriteLine("Calling HelloFromNative:"); + HelloFromNative(); + + // Call the function that returns an int + int a = 42; + int b = 123; + Console.WriteLine($"Calling AddNumbers({a}, {b}):"); + int result = AddNumbers(a, b); + Console.WriteLine($"Result: {result}"); + + // Call the function that returns a string + Console.WriteLine("Calling GetNativeMessage:"); + string message = GetNativeMessage(); + Console.WriteLine($"Received message: {message}"); + + Console.WriteLine("=== Native Call Demonstration Complete ==="); + } + } +} diff --git a/engine/src/scripting/managed/ObjectFactory.cs b/engine/src/scripting/managed/ObjectFactory.cs index 58cc82c61..fce9eb120 100644 --- a/engine/src/scripting/managed/ObjectFactory.cs +++ b/engine/src/scripting/managed/ObjectFactory.cs @@ -12,6 +12,8 @@ // /////////////////////////////////////////////////////////////////////////////// +using System.Runtime.InteropServices; + namespace Nexo { public static class ObjectFactory @@ -24,11 +26,13 @@ public static int CreateInstance(string typeName) { try { - Type type = Type.GetType(typeName); + Type? type = Type.GetType(typeName); if (type == null) return -1; - object instance = Activator.CreateInstance(type); + object? instance = Activator.CreateInstance(type); + if (instance == null) + return -1; int id = _nextId++; _instances[id] = instance; return id; @@ -44,11 +48,13 @@ public static int CreateInstanceWithParams(string typeName, object[] parameters) { try { - Type type = Type.GetType(typeName); + Type? type = Type.GetType(typeName); if (type == null) return -1; - object instance = Activator.CreateInstance(type, parameters); + object? instance = Activator.CreateInstance(type, parameters); + if (instance == null) + return -1; int id = _nextId++; _instances[id] = instance; return id; @@ -68,12 +74,14 @@ public static bool ReleaseInstance(int id) // Helper to invoke methods on instances public static bool InvokeMethod(int id, string methodName, object[] parameters) { - if (!_instances.TryGetValue(id, out object instance)) + if (!_instances.TryGetValue(id, out object? instance)) return false; - + try { - instance.GetType().GetMethod(methodName).Invoke(instance, parameters); + if (instance == null) + return false; + instance?.GetType().GetMethod(methodName)?.Invoke(instance, parameters); return true; } catch diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp new file mode 100644 index 000000000..40b9a915e --- /dev/null +++ b/engine/src/scripting/native/NativeApi.cpp @@ -0,0 +1,42 @@ +//// NativeApi.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: Guillaume HEIN +// Date: 06/05/2025 +// Description: Implementation file for native API functions exposed to managed code +// +/////////////////////////////////////////////////////////////////////////////// + +#include "NativeApi.hpp" +#include + +namespace nexo::scripting { + + // Static message to return to C# + static const char* nativeMessage = "Hello from C++ native code!"; + + // Implementation of the native functions + extern "C" { + + NEXO_RET(void) HelloFromNative() { + std::cout << "Hello World from C++ native code!" << std::endl; + } + + NEXO_RET(int) AddNumbers(int a, int b) { + std::cout << "Native AddNumbers called with " << a << " and " << b << std::endl; + return a + b; + } + + NEXO_RET(const char*) GetNativeMessage() { + std::cout << "GetNativeMessage called from C#" << std::endl; + return nativeMessage; + } + + } + +} // namespace nexo::scripting diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp new file mode 100644 index 000000000..b71b53955 --- /dev/null +++ b/engine/src/scripting/native/NativeApi.hpp @@ -0,0 +1,47 @@ +//// NativeApi.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: Guillaume HEIN +// Date: 06/05/2025 +// Description: Header file for native API functions exposed to managed code +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifdef NEXO_EXPORT + #ifdef WIN32 + #define NEXO_API __declspec(dllexport) + #else + #define NEXO_API __attribute__((visibility("default"))) + #endif +#else + #ifdef WIN32 + #define NEXO_API __declspec(dllimport) + #else + #define NEXO_API + #endif +#endif + +#ifdef WIN32 // Set calling convention according to .NET https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportattribute.callingconvention?view=net-9.0#remarks + #define NEXO_CALL __stdcall +#else + #define NEXO_CALL __cdecl +#endif + +#define NEXO_RET(type) NEXO_API type NEXO_CALL + +namespace nexo::scripting { + + extern "C" { + NEXO_RET(void) HelloFromNative(); + NEXO_RET(int) AddNumbers(int a, int b); + NEXO_RET(const char*) GetNativeMessage(); + } + +} // namespace nexo::scripting diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 168d434da..2f5913a8d 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -276,6 +276,26 @@ namespace nexo::scripting { std::cout << "Testing AddToPtr(1000, 234, ptr), *ptr = " << result << std::endl; } + // Demonstrate C# calling C++ (managed to native) + // First, register our native functions + //registerNativeFunctions(); + + // Get function pointer for DemonstrateNativeCalls method + typedef void (CORECLR_DELEGATE_CALLTYPE *demonstrate_native_calls_fn)(); + demonstrate_native_calls_fn demonstrateNativeCalls = host.getManagedFptr( + STR("Nexo.NativeInterop, Nexo"), + STR("DemonstrateNativeCalls"), + UNMANAGEDCALLERSONLY_METHOD + ); + + if (demonstrateNativeCalls == nullptr) { + std::cout << "Failed to get function pointer for DemonstrateNativeCalls" << std::endl; + return EXIT_FAILURE; + } + + // Call the method to demonstrate calling C++ from C# + std::cout << "\nDemonstrating calling C++ functions from C#:" << std::endl; + demonstrateNativeCalls(); return EXIT_SUCCESS; } diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index 9c1189c8a..15fda4538 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -72,7 +72,7 @@ namespace nexo::scripting { // Globals static inline const std::filesystem::path DEFAULT_NEXO_MANAGED_PATH = - Path::resolvePathRelativeToExe("../engine/src/scripting/managed/bin/Debug/net9.0/"); // TODO: Change it later for packing + Path::resolvePathRelativeToExe("."); // TODO: Change it later for packing static inline const std::string NEXO_RUNTIMECONFIG_FILENAME = "Nexo.runtimeconfig.json"; static inline const std::string NEXO_ASSEMBLY_FILENAME = "Nexo.dll"; static inline const ErrorCallBackFn DEFAULT_ERROR_CALLBACK = HostHandler::defaultErrorCallback; @@ -172,4 +172,7 @@ namespace nexo::scripting { int runScriptExample(const HostHandler::Parameters& params); + // Function to register native callback functions + void registerNativeFunctions(); + } // namespace nexo::scripting From ca90c5fd81a206458ba97699cd17425315cf0a8e Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 6 May 2025 04:36:32 +0900 Subject: [PATCH 379/450] feat(scripting): remove csproj file from repo (now generated by cmake) --- engine/src/scripting/managed/Nexo.csproj | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 engine/src/scripting/managed/Nexo.csproj diff --git a/engine/src/scripting/managed/Nexo.csproj b/engine/src/scripting/managed/Nexo.csproj deleted file mode 100644 index 3da8ca61e..000000000 --- a/engine/src/scripting/managed/Nexo.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - Library - net9.0 - enable - enable - x64 - true - - - $(SolutionDir).bin\$(Configuration)-$(Platform)\ - x64 - - - $(SolutionDir).bin\$(Configuration)-$(Platform)\ - x64 - - - From abf694affa20a63bf18907cb3bde35b9b1207ac5 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 6 May 2025 04:37:56 +0900 Subject: [PATCH 380/450] feat(scripting): add gitignore for managed + missing csproj.in --- engine/src/scripting/managed/.gitignore | 7 ++++++ engine/src/scripting/managed/Nexo.csproj.in | 27 +++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 engine/src/scripting/managed/.gitignore create mode 100644 engine/src/scripting/managed/Nexo.csproj.in diff --git a/engine/src/scripting/managed/.gitignore b/engine/src/scripting/managed/.gitignore new file mode 100644 index 000000000..7ede49076 --- /dev/null +++ b/engine/src/scripting/managed/.gitignore @@ -0,0 +1,7 @@ +obj/ +bin/ +.idea/ + +Folder.DotSettings.user + +Nexo.csproj diff --git a/engine/src/scripting/managed/Nexo.csproj.in b/engine/src/scripting/managed/Nexo.csproj.in new file mode 100644 index 000000000..2eea7d4f4 --- /dev/null +++ b/engine/src/scripting/managed/Nexo.csproj.in @@ -0,0 +1,27 @@ + + + + Library + @NEXO_FRAMEWORK@ + enable + enable + @NEXO_PLATFORM@ + true + false + + + + + + + + @NEXO_MANAGED_OUTPUT_DIR@/Debug-x64/ + @NEXO_PLATFORM@ + + + + @NEXO_MANAGED_OUTPUT_DIR@/Release-x64/ + @NEXO_PLATFORM@ + + + From 89931a58320fe132b352347372772a3f68d55732 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Thu, 8 May 2025 23:27:46 +0900 Subject: [PATCH 381/450] feat(scripting): enhance native interop with new API functions and logging capabilities + can create cube from C# --- CMakeLists.txt | 3 - common/Logger.hpp | 2 +- engine/CMakeLists.txt | 4 + engine/src/scripting/CMakeLists.txt | 2 +- engine/src/scripting/managed/CMakeLists.txt | 2 +- engine/src/scripting/managed/Logger.cs | 25 ++++ engine/src/scripting/managed/NativeInterop.cs | 124 ++++++++++++------ engine/src/scripting/managed/Nexo.csproj.in | 12 +- .../src/scripting/native/ManagedTypedef.hpp | 51 +++++++ engine/src/scripting/native/NativeApi.cpp | 25 +++- engine/src/scripting/native/NativeApi.hpp | 46 ++++++- engine/src/scripting/native/Scripting.cpp | 15 +++ 12 files changed, 252 insertions(+), 59 deletions(-) create mode 100644 engine/src/scripting/managed/Logger.cs create mode 100644 engine/src/scripting/native/ManagedTypedef.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 593503346..bf893e845 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,9 +96,6 @@ message(STATUS "VCPKG done.") include("${CMAKE_CURRENT_SOURCE_DIR}/editor/CMakeLists.txt") # SETUP ENGINE include("${CMAKE_CURRENT_SOURCE_DIR}/engine/CMakeLists.txt") -# SETUP ENGINE API -include("${CMAKE_CURRENT_SOURCE_DIR}/engine/src/scripting/CMakeLists.txt") -add_dependencies(nexoEditor nexoApi) # SETUP MANAGED CSHARP LIB include("${CMAKE_CURRENT_SOURCE_DIR}/engine/src/scripting/managed/CMakeLists.txt") add_dependencies(nexoEditor nexoManaged) diff --git a/common/Logger.hpp b/common/Logger.hpp index 09402e3e4..a8afd4825 100644 --- a/common/Logger.hpp +++ b/common/Logger.hpp @@ -38,7 +38,7 @@ namespace nexo { } } - enum class LogLevel { + enum class LogLevel : uint32_t { FATAL, ERR, WARN, diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 2f86d2fbb..e018d4d07 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -63,6 +63,7 @@ set(COMMON_SOURCES engine/src/assets/Assets/Texture/TextureImporter.cpp engine/src/scripting/native/Scripting.cpp engine/src/scripting/native/HostString.cpp + engine/src/scripting/native/NativeApi.cpp ) # Add API-specific sources @@ -122,3 +123,6 @@ if(NEXO_GRAPHICS_API STREQUAL "OpenGL") find_package(glad CONFIG REQUIRED) target_link_libraries(nexoRenderer PRIVATE OpenGL::GL glfw glad::glad) endif() + +target_compile_definitions(nexoRenderer PRIVATE NEXO_EXPORT) +set_target_properties(nexoRenderer PROPERTIES ENABLE_EXPORTS ON) diff --git a/engine/src/scripting/CMakeLists.txt b/engine/src/scripting/CMakeLists.txt index 00a0cda52..546381d1d 100644 --- a/engine/src/scripting/CMakeLists.txt +++ b/engine/src/scripting/CMakeLists.txt @@ -21,5 +21,5 @@ target_include_directories(nexoApi PUBLIC ${CMAKE_SOURCE_DIR}/engine/src ${CMAKE_SOURCE_DIR}/common) -target_link_libraries(nexoApi PRIVATE nexoRenderer) target_compile_definitions(nexoApi PRIVATE NEXO_EXPORT) +set_target_properties(nexoApi PROPERTIES ENABLE_EXPORTS ON) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 62bd96984..ccf1d2184 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES Lib.cs ObjectFactory.cs NativeInterop.cs + Logger.cs ) # Locate the dotnet executable @@ -34,7 +35,6 @@ configure_file( add_custom_target(nexoManaged ALL COMMAND ${DOTNET_EXECUTABLE} build Nexo.csproj -c $ # Matches Debug/Release configuration - --output ${NEXO_MANAGED_OUTPUT_DIR} # Custom output directory WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} # Working directory for the build COMMENT "Building .NET managed project (Nexo.csproj)..." ) diff --git a/engine/src/scripting/managed/Logger.cs b/engine/src/scripting/managed/Logger.cs new file mode 100644 index 000000000..68f6aeea1 --- /dev/null +++ b/engine/src/scripting/managed/Logger.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace Nexo; + +public enum LogLevel : UInt32 +{ + Fatal, + Error, + Warn, + Info, + Debug, + Dev, + User +} + +public static class Logger +{ + /// + /// Logs a message with the specified log level. + /// + /// Specifies the log level (e.g., Fatal, Error, Warn, Info, Debug, Dev, User). + /// The message to be logged. + public static void Log(LogLevel level, string message) => NativeInterop.NxLog((uint)level, message); + +} diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index d7f79a51d..1504b1746 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -4,43 +4,58 @@ namespace Nexo { /// - /// Provides P/Invoke declarations for calling native C++ functions from C# + /// Provides interop functionality for calling native C++ functions from C# using function pointers. /// public static class NativeInterop { - // The name of the native library that contains our exported functions - // On Windows, this would typically be "engine.dll" - // On macOS, it would be "libengine.dylib" - // On Linux, it would be "libengine.so" - // Since we're using dynamic loading, we'll set this at runtime - private static string s_nativeLibraryName; - /// - /// Sets the native library name to use for P/Invoke calls + /// Native API struct that matches the C++ struct /// - public static void SetNativeLibraryName(string libraryName) + [StructLayout(LayoutKind.Sequential)] + private struct NativeApiCallbacks { - s_nativeLibraryName = libraryName; - Console.WriteLine($"Native library name set to: {libraryName}"); + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate void HelloFromNativeDelegate(); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate Int32 AddNumbersDelegate(Int32 a, Int32 b); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate IntPtr GetNativeMessageDelegate(); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate void NxLogDelegate(UInt32 level, String message); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate void CreateCubeDelegate(); + + // Function pointers + public HelloFromNativeDelegate HelloFromNative; + public AddNumbersDelegate AddNumbers; + public GetNativeMessageDelegate GetNativeMessage; + public NxLogDelegate NxLog; + public CreateCubeDelegate CreateCube; } - /// - /// Import of the HelloFromNative function from the native library - /// - [DllImport("nexoApi", EntryPoint = "HelloFromNative")] - private static extern void HelloFromNativeInternal(); + private static NativeApiCallbacks s_callbacks; /// - /// Import of the AddNumbers function from the native library + /// Initialize the native API with the provided struct pointer and size. /// - [DllImport("nexoApi", EntryPoint = "AddNumbers")] - private static extern int AddNumbersInternal(int a, int b); + /// Pointer to the struct + /// Size of the struct + [UnmanagedCallersOnly] + public static void Initialize(IntPtr structPtr, Int32 structSize) + { + if (structSize != Marshal.SizeOf()) + { + throw new ArgumentException($"Struct size mismatch between C++ and C# for {nameof(NativeApiCallbacks)}, expected {Marshal.SizeOf()}, got {structSize}"); + } - /// - /// Import of the GetNativeMessage function from the native library - /// - [DllImport("nexoApi", EntryPoint = "GetNativeMessage")] - private static extern IntPtr GetNativeMessageInternal(); + // Marshal the struct from the IntPtr to the managed struct + s_callbacks = Marshal.PtrToStructure(structPtr); + Console.WriteLine("Native API initialized."); + } /// /// Calls the HelloFromNative function in the native library @@ -49,7 +64,7 @@ public static void HelloFromNative() { try { - HelloFromNativeInternal(); + s_callbacks.HelloFromNative?.Invoke(); } catch (Exception ex) { @@ -60,11 +75,11 @@ public static void HelloFromNative() /// /// Calls the AddNumbers function in the native library /// - public static int AddNumbers(int a, int b) + public static Int32 AddNumbers(Int32 a, Int32 b) { try { - return AddNumbersInternal(a, b); + return s_callbacks.AddNumbers?.Invoke(a, b) ?? 0; } catch (Exception ex) { @@ -76,15 +91,12 @@ public static int AddNumbers(int a, int b) /// /// Calls the GetNativeMessage function in the native library /// - public static string GetNativeMessage() + public static String GetNativeMessage() { try { - IntPtr messagePtr = GetNativeMessageInternal(); - if (messagePtr == IntPtr.Zero) - return string.Empty; - - return Marshal.PtrToStringAnsi(messagePtr); + IntPtr messagePtr = s_callbacks.GetNativeMessage?.Invoke() ?? IntPtr.Zero; + return messagePtr != IntPtr.Zero ? Marshal.PtrToStringAnsi(messagePtr) ?? string.Empty : string.Empty; } catch (Exception ex) { @@ -93,6 +105,35 @@ public static string GetNativeMessage() } } + /// + /// Logs a message using the native NxLog function + /// + /// The level of the log message + /// The message to log + public static void NxLog(UInt32 level, String message) + { + try + { + s_callbacks.NxLog?.Invoke(level, message); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling NxLog: {ex.Message}"); + } + } + + public static void CreateCube() + { + try + { + s_callbacks.CreateCube?.Invoke(); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling CreateCube: {ex.Message}"); + } + } + /// /// Demonstrates calling native functions from C# /// @@ -106,18 +147,23 @@ public static void DemonstrateNativeCalls() HelloFromNative(); // Call the function that returns an int - int a = 42; - int b = 123; + const Int32 a = 42; + const Int32 b = 123; Console.WriteLine($"Calling AddNumbers({a}, {b}):"); - int result = AddNumbers(a, b); + Int32 result = AddNumbers(a, b); Console.WriteLine($"Result: {result}"); // Call the function that returns a string Console.WriteLine("Calling GetNativeMessage:"); - string message = GetNativeMessage(); - Console.WriteLine($"Received message: {message}"); + String message = GetNativeMessage(); + Logger.Log(LogLevel.Info, $"Logging from C# :) got native message: {message}"); + + // Call the function that creates a cube + Console.WriteLine("Calling CreateCube:"); + CreateCube(); Console.WriteLine("=== Native Call Demonstration Complete ==="); } + } } diff --git a/engine/src/scripting/managed/Nexo.csproj.in b/engine/src/scripting/managed/Nexo.csproj.in index 2eea7d4f4..43c97a6ff 100644 --- a/engine/src/scripting/managed/Nexo.csproj.in +++ b/engine/src/scripting/managed/Nexo.csproj.in @@ -8,19 +8,17 @@ @NEXO_PLATFORM@ true false + true - - @NEXO_MANAGED_OUTPUT_DIR@/Debug-x64/ - @NEXO_PLATFORM@ - - - - @NEXO_MANAGED_OUTPUT_DIR@/Release-x64/ + + false + false + @NEXO_MANAGED_OUTPUT_DIR@ @NEXO_PLATFORM@ diff --git a/engine/src/scripting/native/ManagedTypedef.hpp b/engine/src/scripting/native/ManagedTypedef.hpp new file mode 100644 index 000000000..8fe55603b --- /dev/null +++ b/engine/src/scripting/native/ManagedTypedef.hpp @@ -0,0 +1,51 @@ +//// ManagedTypedef.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: Guillaume HEIN +// Date: 07/05/2025 +// Description: Header file for defining typedef equivalent to C# managed types +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +namespace nexo::scripting { + + extern "C" { + + // Category: Integer + using Byte = uint8_t; // An 8-bit unsigned integer. + using SByte = int8_t; // An 8-bit signed integer. Not CLS-compliant. + using Int16 = int16_t; // A 16-bit signed integer. + using Int32 = int32_t; // A 32-bit signed integer. + using Int64 = int64_t; // A 64-bit signed integer. + using UInt16 = uint16_t; // A 16-bit unsigned integer. Not CLS-compliant. + using UInt32 = uint32_t; // A 32-bit unsigned integer. Not CLS-compliant. + using UInt64 = uint64_t; // A 64-bit unsigned integer. Not CLS-compliant. + + // Category: Floating Point + // Half A half-precision (16-bit) floating-point number. Unsupported in C++ + using Single = float; // A 32-bit single-precision floating point number. + using Double = double; // A 64-bit double-precision floating point number. + + // Category: Logical + using Boolean = bool; // A Boolean value (true or false). + + // Category: Other + using Char = uint16_t; // A Unicode (16-bit) character. + // Decimal A decimal (128-bit) value. Unsupported in C++ + using IntPtr = void*; // A pointer to an unspecified type. + + + + + } + +} // namespace nexo::scripting diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 40b9a915e..d66b8147b 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -15,6 +15,10 @@ #include "NativeApi.hpp" #include +#include "EntityFactory3D.hpp" +#include "Logger.hpp" +#include "Nexo.hpp" + namespace nexo::scripting { // Static message to return to C# @@ -22,21 +26,34 @@ namespace nexo::scripting { // Implementation of the native functions extern "C" { - - NEXO_RET(void) HelloFromNative() { + + void HelloFromNative() { std::cout << "Hello World from C++ native code!" << std::endl; } - NEXO_RET(int) AddNumbers(int a, int b) { + Int32 AddNumbers(const Int32 a, const Int32 b) { std::cout << "Native AddNumbers called with " << a << " and " << b << std::endl; return a + b; } - NEXO_RET(const char*) GetNativeMessage() { + const char* GetNativeMessage() { std::cout << "GetNativeMessage called from C#" << std::endl; return nativeMessage; } + void NxLog(UInt32 level, const char *message) { + LOG(static_cast(level), "[Scripting] {}", message); + } + + void CreateCube() { + + auto &app = getApp(); + const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, 1.25f, 0.0f}, {5.0f, 0.5f, 5.0f}, + {0.0f, 0.0f, 0.0f}, {0.05f * 1.7, 0.09f * 1.35, 0.13f * 1.45, 1.0f}); + app.getSceneManager().getScene(0).addEntity(basicCube); + } + } + } // namespace nexo::scripting diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index b71b53955..cb68400c9 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -36,12 +36,52 @@ #define NEXO_RET(type) NEXO_API type NEXO_CALL +#include +#include + +#include "ManagedTypedef.hpp" + namespace nexo::scripting { + template + struct ApiCallback; + + template + struct ApiCallback { + using Type = Ret(NEXO_CALL *)(Args...); + + // Constructor explicitly accepting function pointers + explicit ApiCallback(Type f) : func(f) {} + + // Delete the default constructor to enforce initialization + ApiCallback() = delete; + + private: + Type func; + }; + extern "C" { - NEXO_RET(void) HelloFromNative(); - NEXO_RET(int) AddNumbers(int a, int b); - NEXO_RET(const char*) GetNativeMessage(); + + NEXO_RET(void) HelloFromNative(void); + NEXO_RET(Int32) AddNumbers(Int32 a, Int32 b); + NEXO_RET(const char*) GetNativeMessage(void); + NEXO_RET(void) NxLog(UInt32 level, const char *message); + + NEXO_RET(void) CreateCube(void); + + + } + struct NativeApiCallbacks { + ApiCallback HelloFromNative{&scripting::HelloFromNative}; + ApiCallback AddNumbers{&scripting::AddNumbers}; + ApiCallback GetNativeMessage{&scripting::GetNativeMessage}; + ApiCallback NxLog{&scripting::NxLog}; + + ApiCallback CreateCube{&scripting::CreateCube}; + }; + + inline NativeApiCallbacks nativeApiCallbacks; + } // namespace nexo::scripting diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 2f5913a8d..72d77fc9c 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -19,6 +19,8 @@ #include "Scripting.hpp" #include "HostString.hpp" #include "Logger.hpp" +#include "NativeApi.hpp" +#include "ManagedTypedef.hpp" namespace nexo::scripting { @@ -186,6 +188,19 @@ namespace nexo::scripting { return EXIT_FAILURE; } + // Initialize callbacks + typedef void (CORECLR_DELEGATE_CALLTYPE *initialize_callbacks_fn)(NativeApiCallbacks *callbacks, Int32 callbackSize); + auto initializeCallbacks = host.getManagedFptr( + STR("Nexo.NativeInterop, Nexo"), + STR("Initialize"), + UNMANAGEDCALLERSONLY_METHOD + ); + if (initializeCallbacks == nullptr) { + return EXIT_FAILURE; + } + initializeCallbacks(&nativeApiCallbacks, sizeof(nativeApiCallbacks)); + + // Get function pointers to managed methods // Regular method component_entry_point_fn hello = host.getManagedFptr( From 22303477f06f7a7afcdccae266380a62fec6e581 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 06:32:56 +0900 Subject: [PATCH 382/450] chore(scripting): delete outdated CMakeLists and README for scripting --- engine/src/scripting/CMakeLists.txt | 25 ------------------------- engine/src/scripting/README.md | 27 --------------------------- 2 files changed, 52 deletions(-) delete mode 100644 engine/src/scripting/CMakeLists.txt delete mode 100644 engine/src/scripting/README.md diff --git a/engine/src/scripting/CMakeLists.txt b/engine/src/scripting/CMakeLists.txt deleted file mode 100644 index 546381d1d..000000000 --- a/engine/src/scripting/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -cmake_minimum_required(VERSION 3.10) - -# Set project name -project(nexoApi VERSION 1.0.0) - -# Specify the C++ standard -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED True) - -# Add source files -set(COMMON_SOURCES - engine/src/scripting/native/NativeApi.cpp -) - -# Create the library -add_library(nexoApi SHARED ${COMMON_SOURCES}) - -# Add include directories for this target -target_include_directories(nexoApi PUBLIC - ${CMAKE_SOURCE_DIR}/engine/include - ${CMAKE_SOURCE_DIR}/engine/src - ${CMAKE_SOURCE_DIR}/common) - -target_compile_definitions(nexoApi PRIVATE NEXO_EXPORT) -set_target_properties(nexoApi PROPERTIES ENABLE_EXPORTS ON) diff --git a/engine/src/scripting/README.md b/engine/src/scripting/README.md deleted file mode 100644 index 91daea587..000000000 --- a/engine/src/scripting/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# How to use scripting - -## Dependencies - -You first need to install .NET 9.0 SDK. You can download it from the official Microsoft website. - -## Build the managed C# NEXO Library - -1. Open a terminal and navigate to the `engine/src/scripting/managed` directory. -2. Run the following command to build the C# NEXO library: - -```bash -dotnet build -``` - -This will create a `Nexo.dll` file in the `bin/Debug/net9.0` directory. -It is important that the `Nexo.dll` file is in this directory, as the C++ code will look for it there. - -TODO: this should be done automatically when building the project, for now it's done manually. - -## Build the C++ NEXO Library - -The scripting C++ library is built using CMake when you build the project. - -## Run the project - -When running `nexoEditor` the script samples are automatically loaded and executed. You should see the output in the console. From c2d5ca568d6a4f4c7826a09ba79c010ab008e720 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 06:33:19 +0900 Subject: [PATCH 383/450] fix(scripting): undefine ERROR to avoid conflict with Windows.h --- engine/src/assets/Asset.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index 28336a47a..aaf96826f 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -25,6 +25,8 @@ #include "AssetRef.hpp" #include "json.hpp" +#undef ERROR // Avoid conflict with Windows.h + namespace nexo::assets { constexpr unsigned short ASSET_MAX_DEPENDENCIES = 10000; From 421a04ee54f11a27dad2dcf240999552775c7db1 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 06:34:16 +0900 Subject: [PATCH 384/450] chore(scripting): add header comments to CMakeLists.txt and Lib.cs for documentation --- engine/src/scripting/managed/CMakeLists.txt | 14 ++++++++++++++ engine/src/scripting/managed/Lib.cs | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index ccf1d2184..51d9039a0 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -1,3 +1,17 @@ +#### CMakeLists.txt ########################################################### +# +# 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: Guillaume HEIN +# Date: 09/05/2025 +# Description: CMakeLists.txt for the NEXO managed library +# +############################################################################### + cmake_minimum_required(VERSION 3.20) # Set project name diff --git a/engine/src/scripting/managed/Lib.cs b/engine/src/scripting/managed/Lib.cs index 55be71354..6b4010922 100644 --- a/engine/src/scripting/managed/Lib.cs +++ b/engine/src/scripting/managed/Lib.cs @@ -1,3 +1,17 @@ +//// Lib.cs /////////////////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 09/05/2025 +// Description: Source file for the NEXO managed library +// +/////////////////////////////////////////////////////////////////////////////// + using System; using System.Runtime.InteropServices; From 5431a0258fd0266ddd35542c118f9df06ba4efe2 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 06:34:40 +0900 Subject: [PATCH 385/450] chore(logging): add header comments to Logger.cs for documentation --- engine/src/scripting/managed/Logger.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/engine/src/scripting/managed/Logger.cs b/engine/src/scripting/managed/Logger.cs index 68f6aeea1..c47d1c408 100644 --- a/engine/src/scripting/managed/Logger.cs +++ b/engine/src/scripting/managed/Logger.cs @@ -1,4 +1,18 @@ -using System.Runtime.InteropServices; +//// Logger.cs //////////////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 09/05/2025 +// Description: Source file for the logger class +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Runtime.InteropServices; namespace Nexo; @@ -20,6 +34,6 @@ public static class Logger /// /// Specifies the log level (e.g., Fatal, Error, Warn, Info, Debug, Dev, User). /// The message to be logged. - public static void Log(LogLevel level, string message) => NativeInterop.NxLog((uint)level, message); + public static void Log(LogLevel level, String message) => NativeInterop.NxLog((UInt32)level, message); } From 3d56dc06943a05cf9206db3b050e2e74e039241e Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 06:36:53 +0900 Subject: [PATCH 386/450] feat(scripting): add glm vector types for enhanced vector math support --- engine/src/scripting/native/ManagedTypedef.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/src/scripting/native/ManagedTypedef.hpp b/engine/src/scripting/native/ManagedTypedef.hpp index 8fe55603b..be14db9e4 100644 --- a/engine/src/scripting/native/ManagedTypedef.hpp +++ b/engine/src/scripting/native/ManagedTypedef.hpp @@ -16,6 +16,8 @@ #include +#include + namespace nexo::scripting { extern "C" { @@ -43,8 +45,8 @@ namespace nexo::scripting { // Decimal A decimal (128-bit) value. Unsupported in C++ using IntPtr = void*; // A pointer to an unspecified type. - - + using Vector3 = glm::vec3; + using Vector4 = glm::vec4; } From d1a54f53ef7448fbc8f5b310b7c8d0632ccbdb9a Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 06:37:25 +0900 Subject: [PATCH 387/450] feat(scripting): enhance native interop with updated CreateCube and GetTransform methods + rotating cube --- editor/main.cpp | 17 +++++ .../scripting/managed/Components/Transform.cs | 29 +++++++ engine/src/scripting/managed/NativeInterop.cs | 76 ++++++++++++++++--- engine/src/scripting/native/NativeApi.cpp | 19 ++++- engine/src/scripting/native/NativeApi.hpp | 12 +-- 5 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 engine/src/scripting/managed/Components/Transform.cs diff --git a/editor/main.cpp b/editor/main.cpp index 3af510f17..cd26cbf4a 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -25,6 +25,7 @@ #include #include "Path.hpp" +#include "scripting/native/ManagedTypedef.hpp" #include "scripting/native/Scripting.hpp" int main(int argc, char **argv) @@ -61,6 +62,15 @@ try { LOG(NEXO_INFO, "Successfully ran runScriptExample"); } + nexo::scripting::HostHandler& host = nexo::scripting::HostHandler::getInstance(); + + typedef void (CORECLR_DELEGATE_CALLTYPE *update_fn)(nexo::scripting::Double deltaTime); + update_fn updateScript = host.getManagedFptr(STR("Nexo.NativeInterop, Nexo"), STR("Update"), UNMANAGEDCALLERSONLY_METHOD); + + + auto clock = std::chrono::high_resolution_clock::now(); + auto scriptDetlaStart = clock; + std::chrono::duration scriptDeltaElapsed = clock - scriptDetlaStart; while (editor.isOpen()) { auto start = std::chrono::high_resolution_clock::now(); @@ -69,7 +79,14 @@ try { auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = end - start; + + scriptDeltaElapsed = std::chrono::high_resolution_clock::now() - scriptDetlaStart; + updateScript(scriptDeltaElapsed.count() / 1000.0); + scriptDetlaStart = std::chrono::high_resolution_clock::now(); + std::this_thread::sleep_for(std::chrono::milliseconds(16) - elapsed); + + } editor.shutdown(); return 0; diff --git a/engine/src/scripting/managed/Components/Transform.cs b/engine/src/scripting/managed/Components/Transform.cs new file mode 100644 index 000000000..4ddab5a9d --- /dev/null +++ b/engine/src/scripting/managed/Components/Transform.cs @@ -0,0 +1,29 @@ +//// Transform.cs ///////////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 03/05/2025 +// Description: Source file for the Transform component in C#. +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Nexo.Components +{ + + [StructLayout(LayoutKind.Sequential)] + public struct Transform + { + public Vector3 pos; + public Vector3 size; + public Quaternion quat; + } + +} diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 1504b1746..e886b3ebf 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -1,5 +1,23 @@ +//// NativeInterop.cs ///////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 09/05/2025 +// Description: Source file for the NativeInterop class in C#. +// This class provides interop functionality for calling native +// C++ functions. +// +/////////////////////////////////////////////////////////////////////////////// + using System; +using System.Numerics; using System.Runtime.InteropServices; +using Nexo.Components; namespace Nexo { @@ -27,7 +45,10 @@ private struct NativeApiCallbacks public delegate void NxLogDelegate(UInt32 level, String message); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate void CreateCubeDelegate(); + public delegate UInt32 CreateCubeDelegate(Vector3 position, Vector3 size, Vector3 rotation, Vector4 color); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate ref Transform GetTransformDelegate(UInt32 entityId); // Function pointers public HelloFromNativeDelegate HelloFromNative; @@ -35,6 +56,7 @@ private struct NativeApiCallbacks public GetNativeMessageDelegate GetNativeMessage; public NxLogDelegate NxLog; public CreateCubeDelegate CreateCube; + public GetTransformDelegate GetTransform; } private static NativeApiCallbacks s_callbacks; @@ -54,7 +76,7 @@ public static void Initialize(IntPtr structPtr, Int32 structSize) // Marshal the struct from the IntPtr to the managed struct s_callbacks = Marshal.PtrToStructure(structPtr); - Console.WriteLine("Native API initialized."); + Logger.Log(LogLevel.Info, "Native API initialized."); } /// @@ -64,7 +86,7 @@ public static void HelloFromNative() { try { - s_callbacks.HelloFromNative?.Invoke(); + s_callbacks.HelloFromNative.Invoke(); } catch (Exception ex) { @@ -79,7 +101,7 @@ public static Int32 AddNumbers(Int32 a, Int32 b) { try { - return s_callbacks.AddNumbers?.Invoke(a, b) ?? 0; + return s_callbacks.AddNumbers.Invoke(a, b); } catch (Exception ex) { @@ -95,7 +117,7 @@ public static String GetNativeMessage() { try { - IntPtr messagePtr = s_callbacks.GetNativeMessage?.Invoke() ?? IntPtr.Zero; + IntPtr messagePtr = s_callbacks.GetNativeMessage.Invoke(); return messagePtr != IntPtr.Zero ? Marshal.PtrToStringAnsi(messagePtr) ?? string.Empty : string.Empty; } catch (Exception ex) @@ -114,7 +136,7 @@ public static void NxLog(UInt32 level, String message) { try { - s_callbacks.NxLog?.Invoke(level, message); + s_callbacks.NxLog.Invoke(level, message); } catch (Exception ex) { @@ -122,17 +144,33 @@ public static void NxLog(UInt32 level, String message) } } - public static void CreateCube() + public static UInt32 CreateCube(in Vector3 position, in Vector3 size, in Vector3 rotation, in Vector4 color) { try { - s_callbacks.CreateCube?.Invoke(); + return s_callbacks.CreateCube.Invoke(position, size, rotation, color); } catch (Exception ex) { Console.WriteLine($"Error calling CreateCube: {ex.Message}"); + return UInt32.MaxValue; + } + } + + public static ref Transform GetTransform(UInt32 entityId) + { + try + { + return ref s_callbacks.GetTransform.Invoke(entityId); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling GetTransform: {ex.Message}"); + throw new InvalidOperationException($"Failed to get transform for entity {entityId}", ex); } } + + private static UInt32 _cubeId = 0; /// /// Demonstrates calling native functions from C# @@ -160,10 +198,28 @@ public static void DemonstrateNativeCalls() // Call the function that creates a cube Console.WriteLine("Calling CreateCube:"); - CreateCube(); + UInt32 cubeId = CreateCube(new Vector3(1, 4.2f, 3), new Vector3(4, 5, 6), new Vector3(7, 8, 9), new Vector4(1, 0, 0, 1)); + _cubeId = cubeId; + Console.WriteLine($"Created cube with ID: {cubeId}"); + + // Call the function that gets a transform + Console.WriteLine($"Calling GetTransform({cubeId}):"); + ref Transform transform = ref GetTransform(cubeId); + Console.WriteLine($"Transform for cube {cubeId}: Position: {transform.pos}, Scale: {transform.size}, Rotation Quat: {transform.quat}"); + Console.WriteLine("=== Native Call Demonstration Complete ==="); } - + + [UnmanagedCallersOnly] + public static void Update(Double deltaTime) + { + // Get the transform of the cube + ref Transform transform = ref GetTransform(_cubeId); + + // Make the cube rotate on the Y axis + transform.quat = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)deltaTime) * transform.quat; + } + } } diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index d66b8147b..3a731554c 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -12,9 +12,9 @@ // /////////////////////////////////////////////////////////////////////////////// -#include "NativeApi.hpp" #include +#include "NativeApi.hpp" #include "EntityFactory3D.hpp" #include "Logger.hpp" #include "Nexo.hpp" @@ -45,12 +45,23 @@ namespace nexo::scripting { LOG(static_cast(level), "[Scripting] {}", message); } - void CreateCube() { + ecs::Entity CreateCube(const Vector3 pos, const Vector3 size, const Vector3 rotation, const Vector4 color) { auto &app = getApp(); - const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, 1.25f, 0.0f}, {5.0f, 0.5f, 5.0f}, - {0.0f, 0.0f, 0.0f}, {0.05f * 1.7, 0.09f * 1.35, 0.13f * 1.45, 1.0f}); + const ecs::Entity basicCube = EntityFactory3D::createCube(std::move(pos), std::move(size), std::move(rotation), std::move(color)); app.getSceneManager().getScene(0).addEntity(basicCube); + return basicCube; + } + + components::TransformComponent *GetTransformComponent(ecs::Entity entity) + { + const auto &app = getApp(); + const auto opt = app.m_coordinator->tryGetComponent(entity); + if (!opt.has_value()) { + LOG(NEXO_WARN, "GetTransformComponent: Entity {} does not have a TransformComponent", entity); + return nullptr; + } + return &opt.value().get(); } } diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index cb68400c9..d8589516e 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -36,10 +36,9 @@ #define NEXO_RET(type) NEXO_API type NEXO_CALL -#include -#include - +#include "Entity.hpp" #include "ManagedTypedef.hpp" +#include "components/Transform.hpp" namespace nexo::scripting { @@ -67,8 +66,8 @@ namespace nexo::scripting { NEXO_RET(const char*) GetNativeMessage(void); NEXO_RET(void) NxLog(UInt32 level, const char *message); - NEXO_RET(void) CreateCube(void); - + NEXO_RET(ecs::Entity) CreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); + NEXO_RET(components::TransformComponent *) GetTransformComponent(ecs::Entity entity); } @@ -79,7 +78,8 @@ namespace nexo::scripting { ApiCallback GetNativeMessage{&scripting::GetNativeMessage}; ApiCallback NxLog{&scripting::NxLog}; - ApiCallback CreateCube{&scripting::CreateCube}; + ApiCallback CreateCube{&scripting::CreateCube}; + ApiCallback GetTransformComponent{&scripting::GetTransformComponent}; }; inline NativeApiCallbacks nativeApiCallbacks; From 4c9a90afc8f0a6adec1757d9b186b6adb75e7060 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 07:15:46 +0900 Subject: [PATCH 388/450] chore(scripting): update CMakeLists.txt and source files for Windows compatibility --- editor/src/utils/FileSystem.cpp | 7 +++++++ editor/src/utils/FileSystem.hpp | 4 ---- engine/src/scripting/managed/CMakeLists.txt | 1 + engine/src/scripting/native/Scripting.cpp | 6 ++++++ engine/src/scripting/native/Scripting.hpp | 8 -------- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/editor/src/utils/FileSystem.cpp b/editor/src/utils/FileSystem.cpp index 341fd4ee7..c681265d8 100644 --- a/editor/src/utils/FileSystem.cpp +++ b/editor/src/utils/FileSystem.cpp @@ -12,8 +12,15 @@ // /////////////////////////////////////////////////////////////////////////////// +#ifdef WIN32 + #define NOMINMAX + #include +#endif + #include "FileSystem.hpp" + + namespace nexo::editor::utils { void openFolder(const std::string &folderPath) { diff --git a/editor/src/utils/FileSystem.hpp b/editor/src/utils/FileSystem.hpp index c7498edc7..6adf8e17a 100644 --- a/editor/src/utils/FileSystem.hpp +++ b/editor/src/utils/FileSystem.hpp @@ -16,10 +16,6 @@ #include #include -#ifdef _WIN32 - #include -#endif - namespace nexo::editor::utils { /** * @brief Opens a file explorer window showing a specified folder. diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 51d9039a0..386b9a211 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -27,6 +27,7 @@ set(SOURCES ObjectFactory.cs NativeInterop.cs Logger.cs + Components/Transform.cs ) # Locate the dotnet executable diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 72d77fc9c..1c87431bf 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -16,6 +16,12 @@ #include #include +#ifdef WIN32 + #define NOMINMAX + #include +#endif + + #include "Scripting.hpp" #include "HostString.hpp" #include "Logger.hpp" diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index 15fda4538..5246371ac 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -26,14 +26,9 @@ #include "Path.hpp" #ifdef WIN32 - #include - #define STR(s) L ## s #define CH(c) L ## c #define DIR_SEPARATOR L'\\' - - #define string_compare wcscmp - #else #include #include @@ -42,9 +37,6 @@ #define CH(c) c #define DIR_SEPARATOR '/' #define MAX_PATH PATH_MAX - - #define string_compare strcmp - #endif namespace nexo::scripting { From e566c6ec7d4657f40e577b111dc12d0e41615e12 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 18:17:06 +0900 Subject: [PATCH 389/450] feat(scripting): improve cross-platform compatibility with updated NEXO_API and NEXO_CALL definitions --- engine/src/scripting/native/NativeApi.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index d8589516e..3c183413a 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -17,8 +17,10 @@ #ifdef NEXO_EXPORT #ifdef WIN32 #define NEXO_API __declspec(dllexport) - #else + #elif defined(__GNUC__) || defined(__clang__) #define NEXO_API __attribute__((visibility("default"))) + #else + #define NEXO_API #endif #else #ifdef WIN32 @@ -30,6 +32,8 @@ #ifdef WIN32 // Set calling convention according to .NET https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportattribute.callingconvention?view=net-9.0#remarks #define NEXO_CALL __stdcall +#elif defined(__GNUC__) || defined(__clang__) + #define NEXO_CALL __attribute__((cdecl)) #else #define NEXO_CALL __cdecl #endif @@ -47,7 +51,7 @@ namespace nexo::scripting { template struct ApiCallback { - using Type = Ret(NEXO_CALL *)(Args...); + using Type = Ret (NEXO_CALL *)(Args...); // Constructor explicitly accepting function pointers explicit ApiCallback(Type f) : func(f) {} From 68aa4a5a6f23e672b728cd837418a21aba9de058 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 18:35:13 +0900 Subject: [PATCH 390/450] fix(scripting): remove warn for default init and add null check for update function --- editor/main.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/editor/main.cpp b/editor/main.cpp index cd26cbf4a..748b95422 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -51,11 +51,11 @@ try { /*if (int rc = nexo::scripting::load_hostfxr() != 0) { LOG(NEXO_ERROR, "Failed to load hostfxr error code {}", rc); }*/ - const nexo::scripting::HostHandler::Parameters params = { - .errorCallback = [](const nexo::scripting::HostString& message) { - LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); - }, + nexo::scripting::HostHandler::Parameters params; + params.errorCallback = [](const nexo::scripting::HostString& message) { + LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); }; + if (nexo::scripting::runScriptExample(params) == EXIT_FAILURE) { LOG(NEXO_ERROR, "Error in runScriptExample"); } else { @@ -67,7 +67,6 @@ try { typedef void (CORECLR_DELEGATE_CALLTYPE *update_fn)(nexo::scripting::Double deltaTime); update_fn updateScript = host.getManagedFptr(STR("Nexo.NativeInterop, Nexo"), STR("Update"), UNMANAGEDCALLERSONLY_METHOD); - auto clock = std::chrono::high_resolution_clock::now(); auto scriptDetlaStart = clock; std::chrono::duration scriptDeltaElapsed = clock - scriptDetlaStart; @@ -81,7 +80,8 @@ try { std::chrono::duration elapsed = end - start; scriptDeltaElapsed = std::chrono::high_resolution_clock::now() - scriptDetlaStart; - updateScript(scriptDeltaElapsed.count() / 1000.0); + if (updateScript) + updateScript(scriptDeltaElapsed.count() / 1000.0); scriptDetlaStart = std::chrono::high_resolution_clock::now(); std::this_thread::sleep_for(std::chrono::milliseconds(16) - elapsed); From a819af066779033102d2be21afb2896847d81693 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 18:43:46 +0900 Subject: [PATCH 391/450] ci(scripting): install .NET SDK 9.0 in build and codeql workflows --- .github/workflows/build.yml | 8 +++++++- .github/workflows/codeql.yml | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd7ee13a9..c43978775 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -109,10 +109,16 @@ jobs: libxext-dev libxi-dev libgl1-mesa-dev libxinerama-dev \ libxcursor-dev '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev \ libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev \ - libegl1-mesa-dev mono-complete + libegl1-mesa-dev mono-complete dotnet-sdk-9.0 # Pre-install .NET SDK 9.0 for caching version: 1.0 execute_install_scripts: true + - name: Install .NET SDK 9.0 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + cache: true + - name: Init submodules run: | git submodule update --init --recursive diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2630c63f7..c30d587c2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -61,10 +61,16 @@ jobs: libxext-dev libxi-dev libgl1-mesa-dev libxinerama-dev \ libxcursor-dev '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev \ libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev \ - libegl1-mesa-dev mono-complete + libegl1-mesa-dev mono-complete dotnet-sdk-9.0 # Pre-install .NET SDK 9.0 for caching version: 1.0 execute_install_scripts: true + - name: Install .NET SDK 9.0 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + cache: true + - name: Set up GCC if: ${{ matrix.compiler == 'gcc' }} id: set-up-gcc From 242c9e9b0b4cadb6351c6122d1986952652652ee Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 18:48:47 +0900 Subject: [PATCH 392/450] ci(scripting): remove cache option from .NET SDK setup in build and codeql workflows --- .github/workflows/build.yml | 1 - .github/workflows/codeql.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c43978775..6be610d96 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -117,7 +117,6 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: '9.x' - cache: true - name: Init submodules run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c30d587c2..23b142b54 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -69,7 +69,6 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: '9.x' - cache: true - name: Set up GCC if: ${{ matrix.compiler == 'gcc' }} From 8e52b618756e6cf4c43d00f0baa2f388ced25fda Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 21:53:10 +0900 Subject: [PATCH 393/450] feat(scripting): add .NET SDK 9.0 as a dependency for .deb and implement installation steps for managed output --- engine/src/scripting/managed/CMakeLists.txt | 20 ++++++++++++++++++++ scripts/CMakeCPackOptions.cmake.in | 2 +- scripts/install.cmake | 2 ++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 386b9a211..b7a8c2d9a 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -61,3 +61,23 @@ set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.runtimeconfig.json ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.deps.json ) + +# Install step +install(CODE " + execute_process( + COMMAND ${DOTNET_EXECUTABLE} publish + -c ${CMAKE_BUILD_TYPE} + --output \"${NEXO_MANAGED_OUTPUT_DIR}/publish-managed\" + WORKING_DIRECTORY \"${CMAKE_CURRENT_LIST_DIR}\" + )" + COMPONENT scripts +) + +install(DIRECTORY "${NEXO_MANAGED_OUTPUT_DIR}/publish-managed/" # source directory + COMPONENT scripts + DESTINATION "bin/" + FILES_MATCHING # install only matched files + PATTERN "*.dll" # select dll files + PATTERN "*.runtimeconfig.json" # select runtimeconfig.json files + PATTERN "*.deps.json" # select deps.json files +) diff --git a/scripts/CMakeCPackOptions.cmake.in b/scripts/CMakeCPackOptions.cmake.in index 20d23469d..e520010f9 100755 --- a/scripts/CMakeCPackOptions.cmake.in +++ b/scripts/CMakeCPackOptions.cmake.in @@ -41,7 +41,7 @@ if (CPACK_GENERATOR MATCHES "DEB") set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/share/nexo-engine") message(STATUS "Setting CPACK_PACKAGING_INSTALL_PREFIX to ${CPACK_PACKAGING_INSTALL_PREFIX}") if("@NEXO_GRAPHICS_API@" STREQUAL "OpenGL") - set(CPACK_DEBIAN_PACKAGE_DEPENDS "mesa-utils, libglfw3, libxrandr2 (>= 2:1.2.0), libxrender1") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "mesa-utils, libglfw3, libxrandr2 (>= 2:1.2.0), libxrender1, dotnet-sdk-9.0") else() message(WARNING "Unknown graphics API: @NEXO_GRAPHICS_API@, cannot set dependencies") endif() diff --git a/scripts/install.cmake b/scripts/install.cmake index cef29ed6c..12a19c5b0 100755 --- a/scripts/install.cmake +++ b/scripts/install.cmake @@ -88,3 +88,5 @@ install(SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/copyright.cmake" ) + +# Install for scripting can be found in engine/src/scripting/managed/CMakeLists.txt From 5fd8a419f87b5cbcd664fe01a550d591f0f87b25 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 22:15:17 +0900 Subject: [PATCH 394/450] fix(scripting): update output path to use relative directory for managed output + push .csproj --- engine/src/scripting/managed/.gitignore | 2 -- engine/src/scripting/managed/CMakeLists.txt | 2 +- engine/src/scripting/managed/Nexo.csproj | 23 +++++++++++++++++++++ engine/src/scripting/managed/Nexo.csproj.in | 4 +--- 4 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 engine/src/scripting/managed/Nexo.csproj diff --git a/engine/src/scripting/managed/.gitignore b/engine/src/scripting/managed/.gitignore index 7ede49076..1cebac754 100644 --- a/engine/src/scripting/managed/.gitignore +++ b/engine/src/scripting/managed/.gitignore @@ -3,5 +3,3 @@ bin/ .idea/ Folder.DotSettings.user - -Nexo.csproj diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index b7a8c2d9a..68c7ca868 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -19,8 +19,8 @@ project(nexoManaged LANGUAGES NONE) # Define variables for configuration set(NEXO_MANAGED_OUTPUT_DIR ${CMAKE_BINARY_DIR}) +file(RELATIVE_PATH NEXO_MANAGED_OUTPUT_DIR_REL "${NEXO_MANAGED_OUTPUT_DIR}" "${CMAKE_CURRENT_LIST_DIR}") set(NEXO_FRAMEWORK "net9.0") -set(NEXO_PLATFORM "x64") set(SOURCES Lib.cs diff --git a/engine/src/scripting/managed/Nexo.csproj b/engine/src/scripting/managed/Nexo.csproj new file mode 100644 index 000000000..f131fd437 --- /dev/null +++ b/engine/src/scripting/managed/Nexo.csproj @@ -0,0 +1,23 @@ + + + + Library + net9.0 + enable + enable + true + false + true + + + + + + + + false + false + ../engine/src/scripting/managed + + + diff --git a/engine/src/scripting/managed/Nexo.csproj.in b/engine/src/scripting/managed/Nexo.csproj.in index 43c97a6ff..52a074aaa 100644 --- a/engine/src/scripting/managed/Nexo.csproj.in +++ b/engine/src/scripting/managed/Nexo.csproj.in @@ -5,7 +5,6 @@ @NEXO_FRAMEWORK@ enable enable - @NEXO_PLATFORM@ true false true @@ -18,8 +17,7 @@ false false - @NEXO_MANAGED_OUTPUT_DIR@ - @NEXO_PLATFORM@ + @NEXO_MANAGED_OUTPUT_DIR_REL@ From 5c6136aec8e27ebb5760990feb1707753c59b04b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 22:51:33 +0900 Subject: [PATCH 395/450] feat(scripting): add .NET SDK 9.0 installation steps and update component names in CMake files --- .github/workflows/build.yml | 10 ++++++++++ README.md | 1 + engine/src/scripting/managed/CMakeLists.txt | 4 ++-- scripts/CMakeCPackOptions.cmake.in | 1 + 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6be610d96..d80ee4da8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -271,6 +271,11 @@ jobs: with: pattern: 'nexo-engine-installer-msvc14-windows-latest' + - name: Install .NET SDK 9.0 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + - name: Run NSIS installer shell: pwsh run: | @@ -336,6 +341,11 @@ jobs: with: pattern: 'nexo-engine-installer-gcc13-ubuntu-latest' + - name: Install .NET SDK 9.0 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x' + - name: Install DEB package shell: bash run: | diff --git a/README.md b/README.md index dfa0d538d..1dc07dded 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ https://github.com/user-attachments/assets/f675cdc0-3a53-4fb8-8544-a22dc7a332f4 To run this project, ensure you have the following: - **CMake**: Necessary for building the project from source. - **C++ Compiler**: We recommend using GCC or Clang for Linux and MacOS, and MSVC for Windows. +- **.NET SDK 9.0**: Required for the C# scripting support. ## Build the project diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 68c7ca868..0bba3ae7e 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -70,11 +70,11 @@ install(CODE " --output \"${NEXO_MANAGED_OUTPUT_DIR}/publish-managed\" WORKING_DIRECTORY \"${CMAKE_CURRENT_LIST_DIR}\" )" - COMPONENT scripts + COMPONENT scripting ) install(DIRECTORY "${NEXO_MANAGED_OUTPUT_DIR}/publish-managed/" # source directory - COMPONENT scripts + COMPONENT scripting DESTINATION "bin/" FILES_MATCHING # install only matched files PATTERN "*.dll" # select dll files diff --git a/scripts/CMakeCPackOptions.cmake.in b/scripts/CMakeCPackOptions.cmake.in index e520010f9..0d6ee6d45 100755 --- a/scripts/CMakeCPackOptions.cmake.in +++ b/scripts/CMakeCPackOptions.cmake.in @@ -64,6 +64,7 @@ elseif (CPACK_GENERATOR MATCHES "NSIS") Unspecified documentation headers + scripting ) else() message(WARNING "Unknown CPACK_GENERATOR: ${CPACK_GENERATOR}, cannot configure package generator specific settings. This may result in a broken package.") From 2bb7574237a33f344ab0ac45d75c9ad83b54e6f1 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sat, 10 May 2025 00:08:02 +0900 Subject: [PATCH 396/450] fix(scripting): correct relative path for managed output directory in CMake configuration --- engine/src/scripting/managed/.gitignore | 2 ++ engine/src/scripting/managed/CMakeLists.txt | 12 +++++------ engine/src/scripting/managed/Nexo.csproj | 23 --------------------- 3 files changed, 7 insertions(+), 30 deletions(-) delete mode 100644 engine/src/scripting/managed/Nexo.csproj diff --git a/engine/src/scripting/managed/.gitignore b/engine/src/scripting/managed/.gitignore index 1cebac754..7ede49076 100644 --- a/engine/src/scripting/managed/.gitignore +++ b/engine/src/scripting/managed/.gitignore @@ -3,3 +3,5 @@ bin/ .idea/ Folder.DotSettings.user + +Nexo.csproj diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 0bba3ae7e..8e4f7df6b 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -19,7 +19,7 @@ project(nexoManaged LANGUAGES NONE) # Define variables for configuration set(NEXO_MANAGED_OUTPUT_DIR ${CMAKE_BINARY_DIR}) -file(RELATIVE_PATH NEXO_MANAGED_OUTPUT_DIR_REL "${NEXO_MANAGED_OUTPUT_DIR}" "${CMAKE_CURRENT_LIST_DIR}") +file(RELATIVE_PATH NEXO_MANAGED_OUTPUT_DIR_REL "${CMAKE_CURRENT_LIST_DIR}" "${NEXO_MANAGED_OUTPUT_DIR}") set(NEXO_FRAMEWORK "net9.0") set(SOURCES @@ -70,14 +70,12 @@ install(CODE " --output \"${NEXO_MANAGED_OUTPUT_DIR}/publish-managed\" WORKING_DIRECTORY \"${CMAKE_CURRENT_LIST_DIR}\" )" - COMPONENT scripting ) install(DIRECTORY "${NEXO_MANAGED_OUTPUT_DIR}/publish-managed/" # source directory - COMPONENT scripting - DESTINATION "bin/" + DESTINATION "bin" FILES_MATCHING # install only matched files - PATTERN "*.dll" # select dll files - PATTERN "*.runtimeconfig.json" # select runtimeconfig.json files - PATTERN "*.deps.json" # select deps.json files + PATTERN "*.dll" # select dll files + PATTERN "*.runtimeconfig.json" # select runtimeconfig.json files + PATTERN "*.deps.json" # select deps.json files ) diff --git a/engine/src/scripting/managed/Nexo.csproj b/engine/src/scripting/managed/Nexo.csproj deleted file mode 100644 index f131fd437..000000000 --- a/engine/src/scripting/managed/Nexo.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Library - net9.0 - enable - enable - true - false - true - - - - - - - - false - false - ../engine/src/scripting/managed - - - From cb6653b308d343013b56ee59da571013af9c4aba Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sat, 10 May 2025 00:26:31 +0900 Subject: [PATCH 397/450] fix(scripting): update output directory for managed publish and remove .NET SDK dependency from package --- engine/src/scripting/managed/CMakeLists.txt | 7 ++++--- scripts/CMakeCPackOptions.cmake.in | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 8e4f7df6b..6d23f12b6 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -66,13 +66,14 @@ set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES install(CODE " execute_process( COMMAND ${DOTNET_EXECUTABLE} publish - -c ${CMAKE_BUILD_TYPE} - --output \"${NEXO_MANAGED_OUTPUT_DIR}/publish-managed\" + -c $ + --no-build + --output \"${NEXO_MANAGED_OUTPUT_DIR}/publish\" WORKING_DIRECTORY \"${CMAKE_CURRENT_LIST_DIR}\" )" ) -install(DIRECTORY "${NEXO_MANAGED_OUTPUT_DIR}/publish-managed/" # source directory +install(DIRECTORY "${NEXO_MANAGED_OUTPUT_DIR}/publish/" # source directory DESTINATION "bin" FILES_MATCHING # install only matched files PATTERN "*.dll" # select dll files diff --git a/scripts/CMakeCPackOptions.cmake.in b/scripts/CMakeCPackOptions.cmake.in index 0d6ee6d45..20d23469d 100755 --- a/scripts/CMakeCPackOptions.cmake.in +++ b/scripts/CMakeCPackOptions.cmake.in @@ -41,7 +41,7 @@ if (CPACK_GENERATOR MATCHES "DEB") set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/share/nexo-engine") message(STATUS "Setting CPACK_PACKAGING_INSTALL_PREFIX to ${CPACK_PACKAGING_INSTALL_PREFIX}") if("@NEXO_GRAPHICS_API@" STREQUAL "OpenGL") - set(CPACK_DEBIAN_PACKAGE_DEPENDS "mesa-utils, libglfw3, libxrandr2 (>= 2:1.2.0), libxrender1, dotnet-sdk-9.0") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "mesa-utils, libglfw3, libxrandr2 (>= 2:1.2.0), libxrender1") else() message(WARNING "Unknown graphics API: @NEXO_GRAPHICS_API@, cannot set dependencies") endif() @@ -64,7 +64,6 @@ elseif (CPACK_GENERATOR MATCHES "NSIS") Unspecified documentation headers - scripting ) else() message(WARNING "Unknown CPACK_GENERATOR: ${CPACK_GENERATOR}, cannot configure package generator specific settings. This may result in a broken package.") From 22f924344712e037e2c47b050dcc3344515ce8bf Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sat, 10 May 2025 02:55:27 +0900 Subject: [PATCH 398/450] fix(scripting): override signal handler after loading C# assembly (was leading to SEGV on SIGTERM before) --- engine/src/core/event/SignalEvent.hpp | 4 +++- engine/src/scripting/native/Scripting.cpp | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/engine/src/core/event/SignalEvent.hpp b/engine/src/core/event/SignalEvent.hpp index 70f80b260..382302cb5 100644 --- a/engine/src/core/event/SignalEvent.hpp +++ b/engine/src/core/event/SignalEvent.hpp @@ -71,6 +71,8 @@ namespace nexo::event { static std::shared_ptr getInstance(); + void initSignals() const; + private: static void signalHandler(int signal); @@ -79,7 +81,7 @@ namespace nexo::event { template static void emitEventToAll(Args &&... args); - void initSignals() const; + std::vector > m_eventManagers; diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 1c87431bf..adc942426 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -27,6 +27,7 @@ #include "Logger.hpp" #include "NativeApi.hpp" #include "ManagedTypedef.hpp" +#include "core/event/SignalEvent.hpp" namespace nexo::scripting { @@ -63,6 +64,9 @@ namespace nexo::scripting { if (loadManagedAssembly() != SUCCESS) return m_status; + // Take over signal handling because the CoreCLR library overrides them?? + event::SignalHandler::getInstance()->initSignals(); + return m_status = SUCCESS; } From d6895f921408fa13edc765a995df5b5569741d12 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sat, 10 May 2025 05:21:08 +0900 Subject: [PATCH 399/450] fix(scripting): update install steps to publish to a new output directory and include no-self-contained option --- engine/src/scripting/managed/CMakeLists.txt | 19 ------------------- scripts/install.cmake | 20 ++++++++++++++++++-- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 6d23f12b6..b568231e0 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -61,22 +61,3 @@ set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.runtimeconfig.json ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.deps.json ) - -# Install step -install(CODE " - execute_process( - COMMAND ${DOTNET_EXECUTABLE} publish - -c $ - --no-build - --output \"${NEXO_MANAGED_OUTPUT_DIR}/publish\" - WORKING_DIRECTORY \"${CMAKE_CURRENT_LIST_DIR}\" - )" -) - -install(DIRECTORY "${NEXO_MANAGED_OUTPUT_DIR}/publish/" # source directory - DESTINATION "bin" - FILES_MATCHING # install only matched files - PATTERN "*.dll" # select dll files - PATTERN "*.runtimeconfig.json" # select runtimeconfig.json files - PATTERN "*.deps.json" # select deps.json files -) diff --git a/scripts/install.cmake b/scripts/install.cmake index 12a19c5b0..62daa14d1 100755 --- a/scripts/install.cmake +++ b/scripts/install.cmake @@ -87,6 +87,22 @@ install(SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/copyright.cmake" COMPONENT generate-copyright EXCLUDE_FROM_ALL ) +# Install for scripting +install(CODE " + execute_process( + COMMAND dotnet publish + -c $ + --no-build + --output \"${CMAKE_BINARY_DIR}/publish\" + --no-self-contained + WORKING_DIRECTORY \"${CMAKE_SOURCE_DIR}/engine/src/scripting/managed\" + )" +) - -# Install for scripting can be found in engine/src/scripting/managed/CMakeLists.txt +install(DIRECTORY "${CMAKE_BINARY_DIR}/publish/" # source directory + DESTINATION "bin" + FILES_MATCHING # install only matched files + PATTERN "*.dll" # select dll files + PATTERN "*.runtimeconfig.json" # select runtimeconfig.json files + PATTERN "*.deps.json" # select deps.json files +) From 5926fa42395fe2f6d8507b3e25d83424ba2fd1ce Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 16 May 2025 18:28:39 +0900 Subject: [PATCH 400/450] feat(scripting): enhance managed API integration and improve function pointer handling --- engine/src/scripting/managed/Lib.cs | 8 +- engine/src/scripting/managed/NativeInterop.cs | 7 +- engine/src/scripting/native/ManagedApi.hpp | 108 +++++++++ engine/src/scripting/native/NativeApi.hpp | 2 + engine/src/scripting/native/Scripting.cpp | 220 ++++++++++-------- engine/src/scripting/native/Scripting.hpp | 37 +-- 6 files changed, 262 insertions(+), 120 deletions(-) create mode 100644 engine/src/scripting/native/ManagedApi.hpp diff --git a/engine/src/scripting/managed/Lib.cs b/engine/src/scripting/managed/Lib.cs index 6b4010922..64bbb2dd2 100644 --- a/engine/src/scripting/managed/Lib.cs +++ b/engine/src/scripting/managed/Lib.cs @@ -19,7 +19,7 @@ namespace Nexo { public static class Lib { - private static int _sCallCount = 1; + private static Int32 _sCallCount = 1; [StructLayout(LayoutKind.Sequential)] public struct LibArgs @@ -42,13 +42,13 @@ public static int Hello(IntPtr arg, int argLength) } [UnmanagedCallersOnly] - public static int Add(int a, int b) + public static Int32 Add(Int32 a, Int32 b) { return a + b; } [UnmanagedCallersOnly] - public static int AddToPtr(int a, int b, IntPtr result) + public static Int32 AddToPtr(Int32 a, Int32 b, IntPtr result) { if (result == IntPtr.Zero) { @@ -60,7 +60,7 @@ public static int AddToPtr(int a, int b, IntPtr result) } [UnmanagedCallersOnly] - public static int AddNexoDllDirectory(IntPtr pPathString) + public static Int32 AddNexoDllDirectory(IntPtr pPathString) { if (pPathString == IntPtr.Zero) { diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index e886b3ebf..3e12453ac 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -67,16 +67,18 @@ private struct NativeApiCallbacks /// Pointer to the struct /// Size of the struct [UnmanagedCallersOnly] - public static void Initialize(IntPtr structPtr, Int32 structSize) + public static Int32 Initialize(IntPtr structPtr, Int32 structSize) { if (structSize != Marshal.SizeOf()) { - throw new ArgumentException($"Struct size mismatch between C++ and C# for {nameof(NativeApiCallbacks)}, expected {Marshal.SizeOf()}, got {structSize}"); + Logger.Log(LogLevel.Fatal, $"Struct size mismatch between C++ and C# for {nameof(NativeApiCallbacks)}, expected {Marshal.SizeOf()}, got {structSize}"); + return 1; } // Marshal the struct from the IntPtr to the managed struct s_callbacks = Marshal.PtrToStructure(structPtr); Logger.Log(LogLevel.Info, "Native API initialized."); + return 0; } /// @@ -141,6 +143,7 @@ public static void NxLog(UInt32 level, String message) catch (Exception ex) { Console.WriteLine($"Error calling NxLog: {ex.Message}"); + Console.WriteLine($"Fallback to WriteLine: Log Level: {level}, Message: {message}"); } } diff --git a/engine/src/scripting/native/ManagedApi.hpp b/engine/src/scripting/native/ManagedApi.hpp new file mode 100644 index 000000000..4f1ba585a --- /dev/null +++ b/engine/src/scripting/native/ManagedApi.hpp @@ -0,0 +1,108 @@ +//// ManagedApi.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: Guillaume HEIN +// Date: 06/05/2025 +// Description: Header file for managed API functions exposed to native code +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +#include "Entity.hpp" +#include "Exception.hpp" +#include "ManagedTypedef.hpp" +#include "components/Transform.hpp" + +namespace nexo::scripting { + + class InvalidManagedApi final : public Exception { + public: + explicit InvalidManagedApi( + std::string_view message, + const std::source_location loc = std::source_location::current() + ) : Exception(std::format("Invalid managed API call: {}", message), loc) {}; + }; + + template + struct ManagedApiFn; + + template + struct ManagedApiFn { + using Type = Ret (CORECLR_DELEGATE_CALLTYPE *)(Args...); + + // Constructor explicitly accepting function pointers + explicit(false) ManagedApiFn(Type f) : func(f) + { + if (!func) { + THROW_EXCEPTION(InvalidManagedApi, std::format("Function pointer is null: {}", typeid(Type).name())); + } + } + + Ret operator()(Args... args) const + { + assert(func != nullptr && "Called function pointer is null"); + return func(args...); + } + + explicit ManagedApiFn(nullptr_t) = delete; + + // Delete the default constructor to enforce initialization + ManagedApiFn() = default; + + private: + Type func = nullptr; + }; + + struct lib_args { + const char_t* message; + int number; + }; + + struct NativeApiCallbacks; + + /** + * @brief ManagedApi struct to hold function pointers to managed API functions + * + * This struct is used to hold function pointers to managed API functions that can be called from native code. + * + * @warning At runtime, all functions MUST be initialized to a none nullptr value. + * Otherwise, an exception will be thrown by \c HostHandler::checkManagedApi(). + */ + struct ManagedApi { + + struct NativeInteropApi { + ManagedApiFn Initialize; + + ManagedApiFn DemonstrateNativeCalls; + + ManagedApiFn Update; + } NativeInterop; + + struct LibApi { + /** + * @brief Example call from C++ to C# + * + * @param args Arguments passed to managed code + */ + ManagedApiFn CustomEntryPoint; + + ManagedApiFn CustomEntryPointUnmanagedCallersOnly; + + ManagedApiFn Hello; + + ManagedApiFn Add; + ManagedApiFn AddToPtr; + } Lib; + + + }; + +} // namespace nexo::scripting diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index 3c183413a..c8d908fcc 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -56,6 +56,8 @@ namespace nexo::scripting { // Constructor explicitly accepting function pointers explicit ApiCallback(Type f) : func(f) {} + explicit ApiCallback(nullptr_t) = delete; + // Delete the default constructor to enforce initialization ApiCallback() = delete; diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index adc942426..e7f8d309c 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -25,6 +25,7 @@ #include "Scripting.hpp" #include "HostString.hpp" #include "Logger.hpp" +#include "ManagedApi.hpp" #include "NativeApi.hpp" #include "ManagedTypedef.hpp" #include "core/event/SignalEvent.hpp" @@ -67,9 +68,43 @@ namespace nexo::scripting { // Take over signal handling because the CoreCLR library overrides them?? event::SignalHandler::getInstance()->initSignals(); + if (initManagedApi() != SUCCESS) + return m_status; + + if (initCallbacks() != SUCCESS) + return m_status; + return m_status = SUCCESS; } + HostHandler::Status HostHandler::update(Double deltaTime) + { + m_managedApi.NativeInterop.Update(deltaTime); + return m_status; + } + + void *HostHandler::getManagedFptrVoid(const char_t* typeName, const char_t* methodName, + const char_t* delegateTypeName) const + { + if (m_status != SUCCESS) { + m_params.errorCallback("getManagedFptr: HostHandler not initialized"); + return nullptr; + } + + void *fptr = nullptr; + unsigned int rc = m_delegates.load_assembly_and_get_function_pointer( + m_assembly_path.c_str(), typeName, methodName, delegateTypeName, nullptr, &fptr); + if (rc != 0 || fptr == nullptr) { + m_params.errorCallback(std::format("Failed to get function pointer Type({}) Method({}): 0x{:X}", + typeName ? HostString(typeName).to_utf8() : "", + methodName ? HostString(methodName).to_utf8() : "", + rc) + ); + return nullptr; + } + return fptr; + } + void HostHandler::defaultErrorCallback(const HostString& message) { std::cerr << "[Scripting] Error: " << message.to_utf8() << std::endl; @@ -187,48 +222,98 @@ namespace nexo::scripting { return m_status = SUCCESS; } - int runScriptExample(const HostHandler::Parameters& params) - { - // Get the instance of the HostHandler singleton - HostHandler& host = HostHandler::getInstance(); - // Initialize the host - HostHandler::Status status = host.initialize(params); - if (status != HostHandler::SUCCESS) { - return EXIT_FAILURE; - } + HostHandler::Status HostHandler::initManagedApi() + try { + m_managedApi.NativeInterop = { + .Initialize = getManagedFptr( + STR("Nexo.NativeInterop, Nexo"), + STR("Initialize"), + UNMANAGEDCALLERSONLY_METHOD + ), + .DemonstrateNativeCalls = getManagedFptr( + STR("Nexo.NativeInterop, Nexo"), + STR("DemonstrateNativeCalls"), + UNMANAGEDCALLERSONLY_METHOD + ), + .Update = getManagedFptr( + STR("Nexo.NativeInterop, Nexo"), + STR("Update"), + UNMANAGEDCALLERSONLY_METHOD + ) + }; - // Initialize callbacks - typedef void (CORECLR_DELEGATE_CALLTYPE *initialize_callbacks_fn)(NativeApiCallbacks *callbacks, Int32 callbackSize); - auto initializeCallbacks = host.getManagedFptr( - STR("Nexo.NativeInterop, Nexo"), - STR("Initialize"), - UNMANAGEDCALLERSONLY_METHOD - ); - if (initializeCallbacks == nullptr) { - return EXIT_FAILURE; + m_managedApi.Lib = { + .CustomEntryPoint = getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("CustomEntryPoint"), + STR("Nexo.Lib+CustomEntryPointDelegate, Nexo") + ), + .CustomEntryPointUnmanagedCallersOnly = getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("CustomEntryPointUnmanagedCallersOnly"), + UNMANAGEDCALLERSONLY_METHOD + ), + .Hello = getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("Hello"), + nullptr + ), + .Add = getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("Add"), + UNMANAGEDCALLERSONLY_METHOD + ), + .AddToPtr = getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("AddToPtr"), + UNMANAGEDCALLERSONLY_METHOD + ) + }; + + return m_status = checkManagedApi(); + + } catch (const std::exception& e) { + m_params.errorCallback(std::format("Failed to initialize managed API: {}", e.what())); + return m_status = INIT_MANAGED_API_ERROR; + } + + HostHandler::Status HostHandler::checkManagedApi() + { + // Assert that the ManagedApiFn struct is properly defined + static_assert(sizeof(ManagedApiFn) == sizeof(nullptr), "ManagedApiFn: struct size is not a pointer size"); + // Assert that the ManagedApi struct is properly defined (check that every field is void *) + if constexpr (sizeof(ManagedApi) % sizeof(nullptr) != 0) { + m_params.errorCallback("ManagedApi: struct size is not a multiple of pointer size"); } - initializeCallbacks(&nativeApiCallbacks, sizeof(nativeApiCallbacks)); + // Check all fields of ManagedApi for nullptr + constexpr size_t nbFields = sizeof(ManagedApi) / sizeof(void*); + + for (size_t i = 0; i < nbFields; ++i) { + void **fieldPtr = reinterpret_cast(&m_managedApi) + i; + if (*fieldPtr == nullptr) { + m_params.errorCallback(std::format("ManagedApi: ManagedApiFn function pointer number {} is null in the struct", i)); + return m_status = INIT_MANAGED_API_ERROR; + } + } - // Get function pointers to managed methods - // Regular method - component_entry_point_fn hello = host.getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("Hello"), - nullptr - ); + return m_status = SUCCESS; + } - if (hello == nullptr) { - return EXIT_FAILURE; + HostHandler::Status HostHandler::initCallbacks() + { + // Initialize callbacks + if (m_managedApi.NativeInterop.Initialize(&nativeApiCallbacks, sizeof(nativeApiCallbacks))) { + m_params.errorCallback("Failed to initialize native API callbacks"); + return m_status = INIT_CALLBACKS_ERROR; } + return m_status = SUCCESS; + } + int HostHandler::runScriptExample() + { // Run managed code - struct lib_args { - const char_t* message; - int number; - }; - // Call the Hello method multiple times for (int i = 0; i < 3; ++i) { lib_args args { @@ -236,91 +321,34 @@ namespace nexo::scripting { i }; - hello(&args, sizeof(args)); + m_managedApi.Lib.Hello(&args, sizeof(args)); } - // Get function pointer for UnmanagedCallersOnly method - typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_fn)(lib_args args); - custom_entry_point_fn custom_unmanaged = host.getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("CustomEntryPointUnmanagedCallersOnly"), - UNMANAGEDCALLERSONLY_METHOD - ); - - if (custom_unmanaged == nullptr) { - return EXIT_FAILURE; - } // Call UnmanagedCallersOnly method lib_args args_unmanaged { STR("from host!"), -1 }; - custom_unmanaged(args_unmanaged); - - // Get function pointer for custom delegate type method - custom_entry_point_fn custom = host.getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("CustomEntryPoint"), - STR("Nexo.Lib+CustomEntryPointDelegate, Nexo") - ); + m_managedApi.Lib.CustomEntryPointUnmanagedCallersOnly(args_unmanaged); - if (custom == nullptr) { - return EXIT_FAILURE; - } // Call custom delegate type method - custom(args_unmanaged); - - typedef int32_t (CORECLR_DELEGATE_CALLTYPE *add_fn)(int32_t a, int32_t b); - add_fn add = host.getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("Add"), - UNMANAGEDCALLERSONLY_METHOD - ); - if (add == nullptr) { - return EXIT_FAILURE; - } - - std::cout << "Testing Add(30, -10) = " << add(30, -10) << std::endl; + m_managedApi.Lib.CustomEntryPoint(args_unmanaged); - typedef int32_t (CORECLR_DELEGATE_CALLTYPE *add_to_ptr_fn)(int32_t a, int32_t b, int32_t *result); - add_to_ptr_fn addToPtr = host.getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("AddToPtr"), - UNMANAGEDCALLERSONLY_METHOD - ); - if (addToPtr == nullptr) { - return EXIT_FAILURE; - } + std::cout << "Testing Add(30, -10) = " << m_managedApi.Lib.Add(30, -10) << std::endl; int32_t result = 0; - if (addToPtr(1000, 234, &result)) { + if (m_managedApi.Lib.AddToPtr(1000, 234, &result)) { std::cout << "addToPtr returned an error" << std::endl; } else { std::cout << "Testing AddToPtr(1000, 234, ptr), *ptr = " << result << std::endl; } // Demonstrate C# calling C++ (managed to native) - // First, register our native functions - //registerNativeFunctions(); - - // Get function pointer for DemonstrateNativeCalls method - typedef void (CORECLR_DELEGATE_CALLTYPE *demonstrate_native_calls_fn)(); - demonstrate_native_calls_fn demonstrateNativeCalls = host.getManagedFptr( - STR("Nexo.NativeInterop, Nexo"), - STR("DemonstrateNativeCalls"), - UNMANAGEDCALLERSONLY_METHOD - ); - - if (demonstrateNativeCalls == nullptr) { - std::cout << "Failed to get function pointer for DemonstrateNativeCalls" << std::endl; - return EXIT_FAILURE; - } - // Call the method to demonstrate calling C++ from C# std::cout << "\nDemonstrating calling C++ functions from C#:" << std::endl; - demonstrateNativeCalls(); + m_managedApi.NativeInterop.DemonstrateNativeCalls(); return EXIT_SUCCESS; } diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index 5246371ac..64e24cd4c 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -23,6 +23,7 @@ #include #include "HostString.hpp" +#include "ManagedApi.hpp" #include "Path.hpp" #ifdef WIN32 @@ -101,6 +102,10 @@ namespace nexo::scripting { ASSEMBLY_NOT_FOUND, LOAD_ASSEMBLY_ERROR, + INIT_MANAGED_API_ERROR, + + INIT_CALLBACKS_ERROR, + }; @@ -118,34 +123,29 @@ namespace nexo::scripting { Status initialize(Parameters parameters); + Status update(Double deltaTime); + + inline void *getManagedFptrVoid(const char_t *typeName, const char_t *methodName, const char_t *delegateTypeName) const; + + // activate if you want to use the function pointer directly template - T getManagedFptr(const char_t *typeName, const char_t *methodName, const char_t *delegateTypeName) + inline T getManagedFptr(const char_t *typeName, const char_t *methodName, const char_t *delegateTypeName) { - if (m_status != SUCCESS) { - m_params.errorCallback("getManagedFptr: HostHandler not initialized"); - return nullptr; - } - - void *fptr = nullptr; - unsigned int rc = m_delegates.load_assembly_and_get_function_pointer( - m_assembly_path.c_str(), typeName, methodName, delegateTypeName, nullptr, &fptr); - if (rc != 0 || fptr == nullptr) { - m_params.errorCallback(std::format("Failed to get function pointer Type({}) Method({}): 0x{:X}", - typeName ? HostString(typeName).to_utf8() : "", - methodName ? HostString(methodName).to_utf8() : "", - rc) - ); - return nullptr; - } - return reinterpret_cast(fptr); + return reinterpret_cast(getManagedFptrVoid(typeName, methodName, delegateTypeName)); } + + int runScriptExample(); + protected: Status loadHostfxr(); Status initRuntime(); Status getRuntimeDelegates(); Status loadManagedAssembly(); + Status initManagedApi(); + Status checkManagedApi(); + Status initCallbacks(); @@ -156,6 +156,7 @@ namespace nexo::scripting { HostfxrFn m_hostfxr_fn = {}; CoreclrDelegate m_delegates = {}; + ManagedApi m_managedApi = {}; std::shared_ptr m_dll_handle = nullptr; hostfxr_handle m_host_ctx = nullptr; From 369ea502308e00fcccf1e83c6a5622ed9b534215 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 16 May 2025 18:48:03 +0900 Subject: [PATCH 401/450] refactor(scripting): move script calls aways from main --- editor/main.cpp | 18 +++--------------- engine/src/Application.cpp | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/editor/main.cpp b/editor/main.cpp index 748b95422..44e37c4df 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -48,25 +48,14 @@ try { editor.init(); - /*if (int rc = nexo::scripting::load_hostfxr() != 0) { - LOG(NEXO_ERROR, "Failed to load hostfxr error code {}", rc); - }*/ - nexo::scripting::HostHandler::Parameters params; - params.errorCallback = [](const nexo::scripting::HostString& message) { - LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); - }; + auto &scriptHost = nexo::scripting::HostHandler::getInstance(); - if (nexo::scripting::runScriptExample(params) == EXIT_FAILURE) { + if (scriptHost.runScriptExample() == EXIT_FAILURE) { LOG(NEXO_ERROR, "Error in runScriptExample"); } else { LOG(NEXO_INFO, "Successfully ran runScriptExample"); } - nexo::scripting::HostHandler& host = nexo::scripting::HostHandler::getInstance(); - - typedef void (CORECLR_DELEGATE_CALLTYPE *update_fn)(nexo::scripting::Double deltaTime); - update_fn updateScript = host.getManagedFptr(STR("Nexo.NativeInterop, Nexo"), STR("Update"), UNMANAGEDCALLERSONLY_METHOD); - auto clock = std::chrono::high_resolution_clock::now(); auto scriptDetlaStart = clock; std::chrono::duration scriptDeltaElapsed = clock - scriptDetlaStart; @@ -80,8 +69,7 @@ try { std::chrono::duration elapsed = end - start; scriptDeltaElapsed = std::chrono::high_resolution_clock::now() - scriptDetlaStart; - if (updateScript) - updateScript(scriptDeltaElapsed.count() / 1000.0); + scriptHost.update(scriptDeltaElapsed.count() / 1000.0); scriptDetlaStart = std::chrono::high_resolution_clock::now(); std::this_thread::sleep_for(std::chrono::milliseconds(16) - elapsed); diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index 7e17c7310..ec6f8a94e 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -30,6 +30,7 @@ #include "Timestep.hpp" #include "renderer/RendererExceptions.hpp" #include "renderer/Renderer.hpp" +#include "scripting/native/Scripting.hpp" #include "systems/CameraSystem.hpp" #include "systems/RenderSystem.hpp" #include "systems/lights/DirectionalLightsSystem.hpp" @@ -234,6 +235,19 @@ namespace nexo { registerSystems(); m_SceneManager.setCoordinator(m_coordinator); + nexo::scripting::HostHandler::Parameters params; + params.errorCallback = [](const nexo::scripting::HostString& message) { + LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); + }; + + + nexo::scripting::HostHandler& host = nexo::scripting::HostHandler::getInstance(); + + // Initialize the host + if (host.initialize(params) != nexo::scripting::HostHandler::SUCCESS) { + LOG(NEXO_ERROR, "Failed to initialize host"); + } + LOG(NEXO_DEV, "Application initialized"); } From 9ca2bb5efc1b77da7e6a80bd366f3c1c7392dc68 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 16 May 2025 19:04:49 +0900 Subject: [PATCH 402/450] feat(scripting): update getManagedFptr to use ManagedFptrFlags for improved flag handling --- engine/src/scripting/native/Scripting.cpp | 12 ++++++------ engine/src/scripting/native/Scripting.hpp | 12 ++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index e7f8d309c..9fb95a460 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -229,17 +229,17 @@ namespace nexo::scripting { .Initialize = getManagedFptr( STR("Nexo.NativeInterop, Nexo"), STR("Initialize"), - UNMANAGEDCALLERSONLY_METHOD + UNMANAGEDCALLERSONLY ), .DemonstrateNativeCalls = getManagedFptr( STR("Nexo.NativeInterop, Nexo"), STR("DemonstrateNativeCalls"), - UNMANAGEDCALLERSONLY_METHOD + UNMANAGEDCALLERSONLY ), .Update = getManagedFptr( STR("Nexo.NativeInterop, Nexo"), STR("Update"), - UNMANAGEDCALLERSONLY_METHOD + UNMANAGEDCALLERSONLY ) }; @@ -252,7 +252,7 @@ namespace nexo::scripting { .CustomEntryPointUnmanagedCallersOnly = getManagedFptr( STR("Nexo.Lib, Nexo"), STR("CustomEntryPointUnmanagedCallersOnly"), - UNMANAGEDCALLERSONLY_METHOD + UNMANAGEDCALLERSONLY ), .Hello = getManagedFptr( STR("Nexo.Lib, Nexo"), @@ -262,12 +262,12 @@ namespace nexo::scripting { .Add = getManagedFptr( STR("Nexo.Lib, Nexo"), STR("Add"), - UNMANAGEDCALLERSONLY_METHOD + UNMANAGEDCALLERSONLY ), .AddToPtr = getManagedFptr( STR("Nexo.Lib, Nexo"), STR("AddToPtr"), - UNMANAGEDCALLERSONLY_METHOD + UNMANAGEDCALLERSONLY ) }; diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index 64e24cd4c..2fabbf0b0 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -134,6 +134,18 @@ namespace nexo::scripting { return reinterpret_cast(getManagedFptrVoid(typeName, methodName, delegateTypeName)); } + enum ManagedFptrFlags { + NONE = 0, + UNMANAGEDCALLERSONLY = 1 << 0, + }; + + template + inline T getManagedFptr(const char_t *typeName, const char_t *methodName, const ManagedFptrFlags flags = NONE) + { + const char_t *delegateTypeName = flags & UNMANAGEDCALLERSONLY ? UNMANAGEDCALLERSONLY_METHOD : nullptr; + return reinterpret_cast(getManagedFptrVoid(typeName, methodName, delegateTypeName)); + } + int runScriptExample(); From 304e9017e142372ad1006c786fc4fbe82a4d3a6a Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 16 May 2025 19:18:36 +0900 Subject: [PATCH 403/450] feat(scripting): update getManagedFptr to accept HostString parameters for improved type handling --- engine/src/scripting/native/Scripting.cpp | 35 +++++++++++------------ engine/src/scripting/native/Scripting.hpp | 8 +++--- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 9fb95a460..513e348dc 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -227,46 +227,45 @@ namespace nexo::scripting { try { m_managedApi.NativeInterop = { .Initialize = getManagedFptr( - STR("Nexo.NativeInterop, Nexo"), - STR("Initialize"), + "Nexo.NativeInterop, Nexo", + "Initialize", UNMANAGEDCALLERSONLY ), .DemonstrateNativeCalls = getManagedFptr( - STR("Nexo.NativeInterop, Nexo"), - STR("DemonstrateNativeCalls"), + "Nexo.NativeInterop, Nexo", + "DemonstrateNativeCalls", UNMANAGEDCALLERSONLY ), .Update = getManagedFptr( - STR("Nexo.NativeInterop, Nexo"), - STR("Update"), + "Nexo.NativeInterop, Nexo", + "Update", UNMANAGEDCALLERSONLY ) }; m_managedApi.Lib = { .CustomEntryPoint = getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("CustomEntryPoint"), - STR("Nexo.Lib+CustomEntryPointDelegate, Nexo") + "Nexo.Lib, Nexo", + "CustomEntryPoint", + "Nexo.Lib+CustomEntryPointDelegate, Nexo" ), .CustomEntryPointUnmanagedCallersOnly = getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("CustomEntryPointUnmanagedCallersOnly"), + "Nexo.Lib, Nexo", + "CustomEntryPointUnmanagedCallersOnly", UNMANAGEDCALLERSONLY ), .Hello = getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("Hello"), - nullptr + "Nexo.Lib, Nexo", + "Hello" ), .Add = getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("Add"), + "Nexo.Lib, Nexo", + "Add", UNMANAGEDCALLERSONLY ), .AddToPtr = getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("AddToPtr"), + "Nexo.Lib, Nexo", + "AddToPtr", UNMANAGEDCALLERSONLY ) }; diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index 2fabbf0b0..caa5d5fcd 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -129,9 +129,9 @@ namespace nexo::scripting { // activate if you want to use the function pointer directly template - inline T getManagedFptr(const char_t *typeName, const char_t *methodName, const char_t *delegateTypeName) + inline T getManagedFptr(const HostString& typeName, const HostString& methodName, const HostString& delegateTypeName) { - return reinterpret_cast(getManagedFptrVoid(typeName, methodName, delegateTypeName)); + return reinterpret_cast(getManagedFptrVoid(typeName.c_str(), methodName.c_str(), delegateTypeName.c_str())); } enum ManagedFptrFlags { @@ -140,10 +140,10 @@ namespace nexo::scripting { }; template - inline T getManagedFptr(const char_t *typeName, const char_t *methodName, const ManagedFptrFlags flags = NONE) + inline T getManagedFptr(const HostString& typeName, const HostString& methodName, const ManagedFptrFlags flags = NONE) { const char_t *delegateTypeName = flags & UNMANAGEDCALLERSONLY ? UNMANAGEDCALLERSONLY_METHOD : nullptr; - return reinterpret_cast(getManagedFptrVoid(typeName, methodName, delegateTypeName)); + return reinterpret_cast(getManagedFptrVoid(typeName.c_str(), methodName.c_str(), delegateTypeName)); } From 4b7be3328047f797691f6acd432b4faf5a0b6fe7 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 18 May 2025 17:51:01 +0900 Subject: [PATCH 404/450] fix(scripting): missing changes after rebase --- editor/main.cpp | 6 ++++++ editor/src/DocumentWindows/AssetManager/Init.cpp | 4 ++-- engine/CMakeLists.txt | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/editor/main.cpp b/editor/main.cpp index 44e37c4df..689d45921 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -81,4 +81,10 @@ try { } catch (const nexo::Exception &e) { LOG_EXCEPTION(e); return 1; +} catch (const std::exception &e) { + LOG(NEXO_ERROR, "Unhandled exception: {}", e.what()); + return 1; +} catch (...) { + LOG(NEXO_ERROR, "Unhandled unknown exception"); + return 1; } diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp index 2b53aa736..349c8a235 100644 --- a/editor/src/DocumentWindows/AssetManager/Init.cpp +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -25,12 +25,12 @@ namespace nexo::editor { auto asset = std::make_unique(); catalog.registerAsset(assets::AssetLocation("my_package::My_Model@foo/bar/"), std::move(asset)); - { + /*{ assets::AssetImporter importer; std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf"); assets::ImporterFileInput fileInput{path}; auto assetRef9mn = importer.importAsset(assets::AssetLocation("my_package::9mn@foo/bar/"), fileInput); - } + }*/ { assets::AssetImporter importer; std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/logo_nexo.png"); diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index e018d4d07..47e548a93 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -116,6 +116,14 @@ target_link_libraries(nexoRenderer PRIVATE assimp::assimp) find_package(Boost CONFIG REQUIRED COMPONENTS dll) target_link_libraries(nexoRenderer PRIVATE Boost::dll) +########################################### +# Scripting +########################################### + +# nethost +find_package(unofficial-nethost CONFIG REQUIRED) +target_link_libraries(nexoRenderer PRIVATE unofficial::nethost::nethost) + if(NEXO_GRAPHICS_API STREQUAL "OpenGL") target_compile_definitions(nexoRenderer PRIVATE NX_GRAPHICS_API_OPENGL) find_package(OpenGL REQUIRED) From b4f7bb4fc98df436ad02ae38c0c6ef5ad434d634 Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Wed, 21 May 2025 17:42:31 +0200 Subject: [PATCH 405/450] feat: trygetcomponentbyid, issue #284 Not final version, have to refactor getComponentTypeFromNativeId on NativeApi.cpp to remove the switch, --- engine/src/Application.cpp | 2 + engine/src/ecs/Coordinator.hpp | 36 +++++++++++- .../src/scripting/managed/Components/Light.cs | 55 +++++++++++++++++++ .../src/scripting/managed/NativeComponents.cs | 29 ++++++++++ engine/src/scripting/managed/NativeInterop.cs | 32 ++++++++++- .../src/scripting/native/ManagedTypedef.hpp | 7 +++ engine/src/scripting/native/NativeApi.cpp | 31 +++++++++++ engine/src/scripting/native/NativeApi.hpp | 4 +- 8 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 engine/src/scripting/managed/Components/Light.cs create mode 100644 engine/src/scripting/managed/NativeComponents.cs diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index ec6f8a94e..0416871f8 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -62,6 +62,7 @@ namespace nexo { void Application::registerEcsComponents() const { + m_coordinator->registerComponent(); m_coordinator->registerComponent(); m_coordinator->registerComponent(); @@ -233,6 +234,7 @@ namespace nexo { m_coordinator->init(); registerEcsComponents(); registerSystems(); + // std::cout << "Application m_coordinator: " << m_coordinator.get() << std::endl; m_SceneManager.setCoordinator(m_coordinator); nexo::scripting::HostHandler::Parameters params; diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 2596bb4f2..e27d2991e 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -141,8 +141,16 @@ namespace nexo::ecs { m_componentManager->registerComponent(); m_getComponentFunctions[typeid(T)] = [this](Entity entity) -> std::any { - return std::any(this->getComponent(entity)); + auto opt = this->tryGetComponent(entity); + if (!opt.has_value()) + return std::any(); + T* ptr = &opt.value().get(); + return std::any(static_cast(ptr)); }; + // m_getComponentFunctions[typeid(T)] = [this](Entity entity) -> std::any { + // T* ptr = &this->getComponent(entity); + // return std::any(ptr); + // }; m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); if constexpr (supports_memento_pattern_v) { @@ -272,6 +280,32 @@ namespace nexo::ecs { return m_componentManager->tryGetComponent(entity); } + void* tryGetComponentById(ComponentType componentType, Entity entity) + { + auto itType = m_typeIDtoTypeIndex.find(componentType); + if (itType == m_typeIDtoTypeIndex.end()) { + return nullptr; + } + + const std::type_index& typeIndex = itType->second; + auto itGetter = m_getComponentFunctions.find(typeIndex); + if (itGetter == m_getComponentFunctions.end()) { + return nullptr; + } + + std::any componentAny = itGetter->second(entity); + if (!componentAny.has_value()) { + return nullptr; + } + + if (componentAny.type() != typeid(void*)) { + return nullptr; + } + + return std::any_cast(componentAny); + } + + /** * @brief Get the Singleton Component object * diff --git a/engine/src/scripting/managed/Components/Light.cs b/engine/src/scripting/managed/Components/Light.cs new file mode 100644 index 000000000..380b3aa07 --- /dev/null +++ b/engine/src/scripting/managed/Components/Light.cs @@ -0,0 +1,55 @@ +//// Light.cs ///////////////////////////////////////////////////////////////// +// +// 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: Thomas PARENTEAU +// Date: 20/05/2025 +// Description: Source file for the Light component in C#. +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Nexo.Components +{ + [StructLayout(LayoutKind.Sequential)] + public struct AmbientLightComponent + { + public Vector3 color; + } + + [StructLayout(LayoutKind.Sequential)] + public struct DirectionalLightComponent + { + public Vector3 direction; + public Vector3 color; + } + + [StructLayout(LayoutKind.Sequential)] + public struct PointLightComponent + { + public Vector3 color; + public float linear; + public float quadratic; + public float maxDistance; + public float constant; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SpotLightComponent + { + public Vector3 direction; + public Vector3 color; + public float cutOff; + public float outerCutoff; + public float linear; + public float quadratic; + public float maxDistance; + public float constant; + } +} \ No newline at end of file diff --git a/engine/src/scripting/managed/NativeComponents.cs b/engine/src/scripting/managed/NativeComponents.cs new file mode 100644 index 000000000..b17a25b68 --- /dev/null +++ b/engine/src/scripting/managed/NativeComponents.cs @@ -0,0 +1,29 @@ +//// NativeComponents.cs ////////////////////////////////////////////////////// +// +// 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: Thomas PARENTEAU +// Date: 20/05/2025 +// Description: Enum for NativeComponents, used in GetComponent +// +/////////////////////////////////////////////////////////////////////////////// + +namespace Nexo; + +/// +/// Enum representing the components available in C++ accessible via interop. +/// Must exactly match the NativeComponents enum defined in ManagedTypedef.hpp on the C++ side. +/// + +public enum NativeComponents : uint +{ + Transform = 0, + AmbientLight = 1, + DirectionalLight = 2, + PointLight = 3, + SpotLight = 4, +} \ No newline at end of file diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 3e12453ac..5888d666a 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -16,6 +16,7 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Nexo.Components; @@ -49,6 +50,9 @@ private struct NativeApiCallbacks [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate ref Transform GetTransformDelegate(UInt32 entityId); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate IntPtr NxGetComponentDelegate(UInt32 typeId, UInt32 entityId); // Function pointers public HelloFromNativeDelegate HelloFromNative; @@ -57,6 +61,7 @@ private struct NativeApiCallbacks public NxLogDelegate NxLog; public CreateCubeDelegate CreateCube; public GetTransformDelegate GetTransform; + public NxGetComponentDelegate NxGetComponent; } private static NativeApiCallbacks s_callbacks; @@ -173,6 +178,27 @@ public static ref Transform GetTransform(UInt32 entityId) } } + public static unsafe ref T GetComponent(UInt32 entityId) where T : unmanaged + { + UInt32 typeId = typeof(T) switch + { + var t when t == typeof(Transform) => (UInt32)NativeComponents.Transform, + var t when t == typeof(AmbientLightComponent) => (UInt32)NativeComponents.AmbientLight, + var t when t == typeof(DirectionalLightComponent) => (UInt32)NativeComponents.DirectionalLight, + var t when t == typeof(PointLightComponent) => (UInt32)NativeComponents.PointLight, + var t when t == typeof(SpotLightComponent) => (UInt32)NativeComponents.SpotLight, + _ => throw new InvalidOperationException($"Unsupported type: {typeof(T)}") + }; + + IntPtr ptr = s_callbacks.NxGetComponent(typeId, entityId); + if (ptr == IntPtr.Zero) + throw new InvalidOperationException($"Component {typeof(T)} not found on entity {entityId}"); + + return ref Unsafe.AsRef((void*)ptr); + } + + + private static UInt32 _cubeId = 0; /// @@ -206,8 +232,8 @@ public static void DemonstrateNativeCalls() Console.WriteLine($"Created cube with ID: {cubeId}"); // Call the function that gets a transform - Console.WriteLine($"Calling GetTransform({cubeId}):"); - ref Transform transform = ref GetTransform(cubeId); + Console.WriteLine($"Calling GetComponent({cubeId}):"); + ref Transform transform = ref GetComponent(cubeId); Console.WriteLine($"Transform for cube {cubeId}: Position: {transform.pos}, Scale: {transform.size}, Rotation Quat: {transform.quat}"); @@ -218,7 +244,7 @@ public static void DemonstrateNativeCalls() public static void Update(Double deltaTime) { // Get the transform of the cube - ref Transform transform = ref GetTransform(_cubeId); + ref Transform transform = ref GetComponent(_cubeId); // Make the cube rotate on the Y axis transform.quat = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)deltaTime) * transform.quat; diff --git a/engine/src/scripting/native/ManagedTypedef.hpp b/engine/src/scripting/native/ManagedTypedef.hpp index be14db9e4..9c8187b09 100644 --- a/engine/src/scripting/native/ManagedTypedef.hpp +++ b/engine/src/scripting/native/ManagedTypedef.hpp @@ -48,6 +48,13 @@ namespace nexo::scripting { using Vector3 = glm::vec3; using Vector4 = glm::vec4; + enum class NativeComponents : UInt32 { + Transform = 0, + AmbientLight = 1, + DirectionalLight = 2, + PointLight = 3, + SpotLight = 4, + }; } } // namespace nexo::scripting diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 3a731554c..d3ac769ac 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -24,6 +24,30 @@ namespace nexo::scripting { // Static message to return to C# static const char* nativeMessage = "Hello from C++ native code!"; + ecs::ComponentType getComponentTypeFromNativeId(UInt32 typeId) + { + using enum scripting::NativeComponents; + auto& coordinator = *getApp().m_coordinator; + + switch (static_cast(typeId)) + { + case Transform: + return coordinator.getComponentType(); + case AmbientLight: + return coordinator.getComponentType(); + case DirectionalLight: + return coordinator.getComponentType(); + case PointLight: + return coordinator.getComponentType(); + case SpotLight: + return coordinator.getComponentType(); + default: + LOG(NEXO_ERROR, "Unknown NativeComponent ID: {}", typeId); + return -1; + } + } + + // Implementation of the native functions extern "C" { @@ -64,6 +88,13 @@ namespace nexo::scripting { return &opt.value().get(); } + void* GetComponent(UInt32 typeId, ecs::Entity entity) + { + ecs::ComponentType componentType = getComponentTypeFromNativeId(typeId); + const auto opt = getApp().m_coordinator->tryGetComponentById(componentType, entity); + return opt; + } + } diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index c8d908fcc..0572f50b4 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -74,7 +74,7 @@ namespace nexo::scripting { NEXO_RET(ecs::Entity) CreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); NEXO_RET(components::TransformComponent *) GetTransformComponent(ecs::Entity entity); - + NEXO_RET(void *) GetComponent(UInt32 typeId, ecs::Entity entity); } @@ -86,6 +86,8 @@ namespace nexo::scripting { ApiCallback CreateCube{&scripting::CreateCube}; ApiCallback GetTransformComponent{&scripting::GetTransformComponent}; + ApiCallback GetComponent{&scripting::GetComponent}; + }; inline NativeApiCallbacks nativeApiCallbacks; From aec62c29516d14e536f6af7f3e8ba4e6883a8c8a Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Wed, 21 May 2025 17:52:34 +0200 Subject: [PATCH 406/450] fix: source cmakelist cs proj --- engine/src/scripting/managed/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index b568231e0..a0f403a8b 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -27,7 +27,9 @@ set(SOURCES ObjectFactory.cs NativeInterop.cs Logger.cs + NativeComponents.cs Components/Transform.cs + Components/Light.cs ) # Locate the dotnet executable From e4909c1dece6e3aa33e5adf9ab654d51e1b250e8 Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Wed, 21 May 2025 18:33:29 +0200 Subject: [PATCH 407/450] fix: coordinator tests add m_getComponentPointers to the coordinator to separate engine and scripting usages --- engine/src/ecs/Coordinator.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index e27d2991e..1df15e07a 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -141,16 +141,16 @@ namespace nexo::ecs { m_componentManager->registerComponent(); m_getComponentFunctions[typeid(T)] = [this](Entity entity) -> std::any { + return this->getComponent(entity); + }; + + m_getComponentPointers[typeid(T)] = [this](Entity entity) -> std::any { auto opt = this->tryGetComponent(entity); if (!opt.has_value()) return std::any(); T* ptr = &opt.value().get(); return std::any(static_cast(ptr)); }; - // m_getComponentFunctions[typeid(T)] = [this](Entity entity) -> std::any { - // T* ptr = &this->getComponent(entity); - // return std::any(ptr); - // }; m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); if constexpr (supports_memento_pattern_v) { @@ -288,8 +288,8 @@ namespace nexo::ecs { } const std::type_index& typeIndex = itType->second; - auto itGetter = m_getComponentFunctions.find(typeIndex); - if (itGetter == m_getComponentFunctions.end()) { + auto itGetter = m_getComponentPointers.find(typeIndex); + if (itGetter == m_getComponentPointers.end()) { return nullptr; } @@ -506,5 +506,6 @@ namespace nexo::ecs { std::unordered_map m_typeIDtoTypeIndex; std::unordered_map m_supportsMementoPattern; std::unordered_map> m_getComponentFunctions; + std::unordered_map> m_getComponentPointers; }; } From 93d77c44455c5b4215483f14c6ac2a8a97990a2e Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Thu, 22 May 2025 20:34:36 +0200 Subject: [PATCH 408/450] ref: way do we get the component id for scripting usage --- engine/src/scripting/managed/NativeInterop.cs | 52 ++++++++++++++----- engine/src/scripting/native/NativeApi.cpp | 44 ++++++---------- engine/src/scripting/native/NativeApi.hpp | 10 ++++ 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 5888d666a..70bc076dc 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -22,6 +22,16 @@ namespace Nexo { + [StructLayout(LayoutKind.Sequential)] + public struct ComponentTypeIds + { + public UInt32 Transform; + public UInt32 AmbientLight; + public UInt32 DirectionalLight; + public UInt32 PointLight; + public UInt32 SpotLight; + } + /// /// Provides interop functionality for calling native C++ functions from C# using function pointers. /// @@ -53,6 +63,9 @@ private struct NativeApiCallbacks [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate IntPtr NxGetComponentDelegate(UInt32 typeId, UInt32 entityId); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); // Function pointers public HelloFromNativeDelegate HelloFromNative; @@ -62,10 +75,13 @@ private struct NativeApiCallbacks public CreateCubeDelegate CreateCube; public GetTransformDelegate GetTransform; public NxGetComponentDelegate NxGetComponent; + public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; } private static NativeApiCallbacks s_callbacks; - + private static ComponentTypeIds _componentTypeIds; + private static readonly Dictionary _typeToNativeIdMap = new(); + /// /// Initialize the native API with the provided struct pointer and size. /// @@ -82,9 +98,30 @@ public static Int32 Initialize(IntPtr structPtr, Int32 structSize) // Marshal the struct from the IntPtr to the managed struct s_callbacks = Marshal.PtrToStructure(structPtr); + _componentTypeIds = s_callbacks.NxGetComponentTypeIds.Invoke(); + InitializeTypeMap(); + Logger.Log(LogLevel.Info, "Native API initialized."); return 0; } + + private static void InitializeTypeMap() + { + var fields = typeof(ComponentTypeIds).GetFields(); + foreach (var field in fields) + { + var type = Type.GetType($"Nexo.Components.{field.Name}"); + if (type != null) + { + var value = (UInt32)field.GetValue(_componentTypeIds); + _typeToNativeIdMap[type] = value; + } + else + { + Logger.Log(LogLevel.Warn, $"[Interop] Type not found for field {field.Name}"); + } + } + } /// /// Calls the HelloFromNative function in the native library @@ -180,15 +217,8 @@ public static ref Transform GetTransform(UInt32 entityId) public static unsafe ref T GetComponent(UInt32 entityId) where T : unmanaged { - UInt32 typeId = typeof(T) switch - { - var t when t == typeof(Transform) => (UInt32)NativeComponents.Transform, - var t when t == typeof(AmbientLightComponent) => (UInt32)NativeComponents.AmbientLight, - var t when t == typeof(DirectionalLightComponent) => (UInt32)NativeComponents.DirectionalLight, - var t when t == typeof(PointLightComponent) => (UInt32)NativeComponents.PointLight, - var t when t == typeof(SpotLightComponent) => (UInt32)NativeComponents.SpotLight, - _ => throw new InvalidOperationException($"Unsupported type: {typeof(T)}") - }; + if (!_typeToNativeIdMap.TryGetValue(typeof(T), out var typeId)) + throw new InvalidOperationException($"Unsupported component type: {typeof(T)}"); IntPtr ptr = s_callbacks.NxGetComponent(typeId, entityId); if (ptr == IntPtr.Zero) @@ -196,8 +226,6 @@ public static unsafe ref T GetComponent(UInt32 entityId) where T : unmanaged return ref Unsafe.AsRef((void*)ptr); } - - private static UInt32 _cubeId = 0; diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index d3ac769ac..7ef7ba935 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -24,30 +24,6 @@ namespace nexo::scripting { // Static message to return to C# static const char* nativeMessage = "Hello from C++ native code!"; - ecs::ComponentType getComponentTypeFromNativeId(UInt32 typeId) - { - using enum scripting::NativeComponents; - auto& coordinator = *getApp().m_coordinator; - - switch (static_cast(typeId)) - { - case Transform: - return coordinator.getComponentType(); - case AmbientLight: - return coordinator.getComponentType(); - case DirectionalLight: - return coordinator.getComponentType(); - case PointLight: - return coordinator.getComponentType(); - case SpotLight: - return coordinator.getComponentType(); - default: - LOG(NEXO_ERROR, "Unknown NativeComponent ID: {}", typeId); - return -1; - } - } - - // Implementation of the native functions extern "C" { @@ -88,13 +64,27 @@ namespace nexo::scripting { return &opt.value().get(); } - void* GetComponent(UInt32 typeId, ecs::Entity entity) + void* GetComponent(UInt32 componentTypeId, ecs::Entity entity) { - ecs::ComponentType componentType = getComponentTypeFromNativeId(typeId); - const auto opt = getApp().m_coordinator->tryGetComponentById(componentType, entity); + auto& coordinator = *getApp().m_coordinator; + const auto opt = coordinator.tryGetComponentById(componentTypeId, entity); return opt; } + ComponentTypeIds GetComponentTypeIds() + { + auto& coordinator = *getApp().m_coordinator; + + return ComponentTypeIds { + .Transform = coordinator.getComponentType(), + .AmbientLight = coordinator.getComponentType(), + .DirectionalLight = coordinator.getComponentType(), + .PointLight = coordinator.getComponentType(), + .SpotLight = coordinator.getComponentType(), + }; + } + + } diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index 0572f50b4..eb21cd834 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -66,6 +66,13 @@ namespace nexo::scripting { }; extern "C" { + struct ComponentTypeIds { + UInt32 Transform; + UInt32 AmbientLight; + UInt32 DirectionalLight; + UInt32 PointLight; + UInt32 SpotLight; + }; NEXO_RET(void) HelloFromNative(void); NEXO_RET(Int32) AddNumbers(Int32 a, Int32 b); @@ -75,6 +82,7 @@ namespace nexo::scripting { NEXO_RET(ecs::Entity) CreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); NEXO_RET(components::TransformComponent *) GetTransformComponent(ecs::Entity entity); NEXO_RET(void *) GetComponent(UInt32 typeId, ecs::Entity entity); + NEXO_RET(ComponentTypeIds) GetComponentTypeIds(); } @@ -87,6 +95,8 @@ namespace nexo::scripting { ApiCallback CreateCube{&scripting::CreateCube}; ApiCallback GetTransformComponent{&scripting::GetTransformComponent}; ApiCallback GetComponent{&scripting::GetComponent}; + ApiCallback GetComponentTypeIds{&scripting::GetComponentTypeIds}; + }; From ed8a0815a7bc00f5110c2aab78abeefe3fd8a6f3 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 30 May 2025 20:33:26 +0900 Subject: [PATCH 409/450] feat(scripting): add rotation, circling, and breathing effects to cube in Update method --- engine/src/scripting/managed/NativeInterop.cs | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 70bc076dc..d9837a848 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -255,7 +255,7 @@ public static void DemonstrateNativeCalls() // Call the function that creates a cube Console.WriteLine("Calling CreateCube:"); - UInt32 cubeId = CreateCube(new Vector3(1, 4.2f, 3), new Vector3(4, 5, 6), new Vector3(7, 8, 9), new Vector4(1, 0, 0, 1)); + UInt32 cubeId = CreateCube(new Vector3(1, 4.2f, 3), new Vector3(1, 1, 1), new Vector3(7, 8, 9), new Vector4(1, 0, 0, 1)); _cubeId = cubeId; Console.WriteLine($"Created cube with ID: {cubeId}"); @@ -267,15 +267,50 @@ public static void DemonstrateNativeCalls() Console.WriteLine("=== Native Call Demonstration Complete ==="); } + + private static float _angle = 0.0f; + private static float _breathingScale = 1.0f; [UnmanagedCallersOnly] public static void Update(Double deltaTime) { - // Get the transform of the cube ref Transform transform = ref GetComponent(_cubeId); - // Make the cube rotate on the Y axis - transform.quat = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)deltaTime) * transform.quat; + // Rotating cube effect + float rotationSpeed = 1.0f; // radians per second + transform.quat = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)deltaTime * rotationSpeed) * transform.quat; + + // Circling cube effect + float speed = 1.0f; + float radius = 7.0f; + Vector3 origin = new Vector3(0, 5, 0); + + _angle += (float)(speed * deltaTime); + + if (_angle > MathF.PI * 2.0f) + { + _angle = 0.0f; + } + + transform.pos = origin + new Vector3( + (float)Math.Cos(_angle) * radius, + 0, + (float)Math.Sin(_angle) * radius + ); + + // Breathing cube effect + float startScale = 1.0f; + float endScale = 2.0f; + float breathingSpeed = 0.5f; + + _breathingScale += (float)(breathingSpeed * deltaTime * MathF.PI * 2.0f); + if (_breathingScale > MathF.PI * 2.0f) + { + _breathingScale -= MathF.PI * 2.0f; + } + + // Update the size of the cube based on the breathing effect + transform.size.Z = startScale + ((MathF.Sin(_breathingScale) * 0.5f + 0.5f) * (endScale - startScale)); } } From d872dcd9e081952c224315123465dac1ea1a4805 Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Tue, 3 Jun 2025 16:45:55 +0200 Subject: [PATCH 410/450] feat(scripting): more components c# side --- engine/src/components/Camera.hpp | 2 +- .../scripting/managed/Components/Camera.cs | 62 +++++++++++++++++++ .../scripting/managed/Components/Render.cs | 32 ++++++++++ .../src/scripting/managed/Components/Scene.cs | 30 +++++++++ .../src/scripting/managed/Components/Uuid.cs | 26 ++++++++ .../src/scripting/managed/NativeComponents.cs | 7 +++ engine/src/scripting/managed/NativeInterop.cs | 6 ++ engine/src/scripting/native/NativeApi.cpp | 7 +++ engine/src/scripting/native/NativeApi.hpp | 6 ++ 9 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 engine/src/scripting/managed/Components/Camera.cs create mode 100644 engine/src/scripting/managed/Components/Render.cs create mode 100644 engine/src/scripting/managed/Components/Scene.cs create mode 100644 engine/src/scripting/managed/Components/Uuid.cs diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 98ee49db7..8bac373de 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -39,7 +39,7 @@ namespace nexo::components { 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 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). diff --git a/engine/src/scripting/managed/Components/Camera.cs b/engine/src/scripting/managed/Components/Camera.cs new file mode 100644 index 000000000..965b1242f --- /dev/null +++ b/engine/src/scripting/managed/Components/Camera.cs @@ -0,0 +1,62 @@ +//// Camera.cs //////////////////////////////////////////////////////////////// +// +// 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: Thomas PARENTEAU +// Date: 01/06/2025 +// Description: Source file for the Camera component in C#. +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Nexo.Components +{ + public enum CameraType + { + Perspective = 0, + Orthographic = 1 + } + + [StructLayout(LayoutKind.Sequential)] + public struct CameraComponent + { + public uint width; + public uint height; + [MarshalAs(UnmanagedType.I1)] public bool viewportLocked; + public float fov; + public float nearPlane; + public float farPlane; + public CameraType type; + public Vector4 clearColor; + [MarshalAs(UnmanagedType.I1)] public bool active; + [MarshalAs(UnmanagedType.I1)] public bool render; + [MarshalAs(UnmanagedType.I1)] public bool main; + [MarshalAs(UnmanagedType.I1)] public bool resizing; + // Note: m_renderTarget is excluded (pointer to shared_ptr) + } + + [StructLayout(LayoutKind.Sequential)] + public struct PerspectiveCameraController + { + public Vector2 lastMousePosition; + public float mouseSensitivity; + public float translationSpeed; + [MarshalAs(UnmanagedType.I1)] public bool wasMouseReleased; + [MarshalAs(UnmanagedType.I1)] public bool wasActiveLastFrame; + } + + [StructLayout(LayoutKind.Sequential)] + public struct PerspectiveCameraTarget + { + public Vector2 lastMousePosition; + public float mouseSensitivity; + public float distance; + public uint targetEntity; + } +} diff --git a/engine/src/scripting/managed/Components/Render.cs b/engine/src/scripting/managed/Components/Render.cs new file mode 100644 index 000000000..f89affa3d --- /dev/null +++ b/engine/src/scripting/managed/Components/Render.cs @@ -0,0 +1,32 @@ +//// Render.cs //////////////////////////////////////////////////////////////// +// +// 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: Thomas PARENTEAU +// Date: 01/06/2025 +// Description: Source file for the Render component in C#. +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Runtime.InteropServices; + +namespace Nexo.Components +{ + public enum RenderType + { + Render2D = 0, + Render3D = 1 + } + + [StructLayout(LayoutKind.Sequential)] + public struct RenderComponent + { + [MarshalAs(UnmanagedType.I1)] public bool isRendered; + public RenderType type; + // renderable is ignored (unmanaged/shared_ptr) + } +} diff --git a/engine/src/scripting/managed/Components/Scene.cs b/engine/src/scripting/managed/Components/Scene.cs new file mode 100644 index 000000000..a6cf17c6e --- /dev/null +++ b/engine/src/scripting/managed/Components/Scene.cs @@ -0,0 +1,30 @@ +//// Scene.cs ///////////////////////////////////////////////////////////////// +// +// 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: Thomas PARENTEAU +// Date: 03/06/2025 +// Description: Source file for the SceneTag component in C#. +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Runtime.InteropServices; + +namespace Nexo.Components +{ + [StructLayout(LayoutKind.Sequential)] + public struct SceneTag + { + public uint id; + + [MarshalAs(UnmanagedType.I1)] + public bool isActive; + + [MarshalAs(UnmanagedType.I1)] + public bool isRendered; + } +} \ No newline at end of file diff --git a/engine/src/scripting/managed/Components/Uuid.cs b/engine/src/scripting/managed/Components/Uuid.cs new file mode 100644 index 000000000..3c8627f5a --- /dev/null +++ b/engine/src/scripting/managed/Components/Uuid.cs @@ -0,0 +1,26 @@ +//// Uuid.cs ////////////////////////////////////////////////////////////////// +// +// 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: Thomas PARENTEAU +// Date: 03/06/2025 +// Description: Source file for the Uuid component in C#. +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Runtime.InteropServices; + +namespace Nexo.Components +{ + [StructLayout(LayoutKind.Sequential)] + public struct UuidComponent + { + public IntPtr uuidPtr; + + public string Uuid => Marshal.PtrToStringAnsi(uuidPtr) ?? string.Empty; + } +} \ No newline at end of file diff --git a/engine/src/scripting/managed/NativeComponents.cs b/engine/src/scripting/managed/NativeComponents.cs index b17a25b68..f19aed9e0 100644 --- a/engine/src/scripting/managed/NativeComponents.cs +++ b/engine/src/scripting/managed/NativeComponents.cs @@ -26,4 +26,11 @@ public enum NativeComponents : uint DirectionalLight = 2, PointLight = 3, SpotLight = 4, + RenderComponent = 5, + SceneTag = 6, + CameraComponent = 7, + UuidComponent = 8, + PerspectiveCameraController = 9, + PerspectiveCameraTarget = 10, + } \ No newline at end of file diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index d9837a848..30e610d4a 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -30,6 +30,12 @@ public struct ComponentTypeIds public UInt32 DirectionalLight; public UInt32 PointLight; public UInt32 SpotLight; + public UInt32 RenderComponent; + public UInt32 SceneTag; + public UInt32 CameraComponent; + public UInt32 UuidComponent; + public UInt32 PerspectiveCameraController; + public UInt32 PerspectiveCameraTarget; } /// diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 7ef7ba935..3900eba7a 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -18,6 +18,7 @@ #include "EntityFactory3D.hpp" #include "Logger.hpp" #include "Nexo.hpp" +#include "components/Uuid.hpp" namespace nexo::scripting { @@ -81,6 +82,12 @@ namespace nexo::scripting { .DirectionalLight = coordinator.getComponentType(), .PointLight = coordinator.getComponentType(), .SpotLight = coordinator.getComponentType(), + .RenderComponent = coordinator.getComponentType(), + .SceneTag = coordinator.getComponentType(), + .CameraComponent = coordinator.getComponentType(), + .UuidComponent = coordinator.getComponentType(), + .PerspectiveCameraController = coordinator.getComponentType(), + .PerspectiveCameraTarget = coordinator.getComponentType(), }; } diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index eb21cd834..daa8aee95 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -72,6 +72,12 @@ namespace nexo::scripting { UInt32 DirectionalLight; UInt32 PointLight; UInt32 SpotLight; + UInt32 RenderComponent; + UInt32 SceneTag; + UInt32 CameraComponent; + UInt32 UuidComponent; + UInt32 PerspectiveCameraController; + UInt32 PerspectiveCameraTarget; }; NEXO_RET(void) HelloFromNative(void); From 76c7a07f63da29ac47a5446687ad4c2f8c0fae8c Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 11 Jun 2025 01:47:31 +0900 Subject: [PATCH 411/450] fix(scripting): error in script loading not shutting the engine --- engine/src/Application.cpp | 5 ++++- engine/src/Application.hpp | 2 ++ engine/src/scripting/native/Scripting.hpp | 9 ++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index 0416871f8..5b83aa96d 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -28,6 +28,7 @@ #include "components/Uuid.hpp" #include "core/event/Input.hpp" #include "Timestep.hpp" +#include "exceptions/Exceptions.hpp" #include "renderer/RendererExceptions.hpp" #include "renderer/Renderer.hpp" #include "scripting/native/Scripting.hpp" @@ -238,8 +239,9 @@ namespace nexo { m_SceneManager.setCoordinator(m_coordinator); nexo::scripting::HostHandler::Parameters params; - params.errorCallback = [](const nexo::scripting::HostString& message) { + params.errorCallback = [this](const nexo::scripting::HostString& message) { LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); + m_latestScriptingError = message.to_utf8(); }; @@ -248,6 +250,7 @@ namespace nexo { // Initialize the host if (host.initialize(params) != nexo::scripting::HostHandler::SUCCESS) { LOG(NEXO_ERROR, "Failed to initialize host"); + THROW_EXCEPTION(scripting::ScriptingBackendInitFailed, m_latestScriptingError); } LOG(NEXO_DEV, "Application initialized"); diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index 8ec599eba..cf5399f0f 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -260,5 +260,7 @@ namespace nexo { std::shared_ptr m_perspectiveCameraTargetSystem; std::vector m_profilesResults; + + std::string m_latestScriptingError; }; } diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index caa5d5fcd..c6d352e73 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -57,11 +57,18 @@ namespace nexo::scripting { get_function_pointer_fn get_function_pointer; }; + class ScriptingBackendInitFailed final : public Exception { + public: + explicit ScriptingBackendInitFailed(const std::string &message, + const std::source_location loc = std::source_location::current()) + : Exception("Couldn't load scripting backend: " + message, loc) {} + }; + class HostHandler { protected: static void defaultErrorCallback(const HostString& message); public: - typedef void(*ErrorCallBackFn)(const HostString& message); + using ErrorCallBackFn = std::function; // Globals static inline const std::filesystem::path DEFAULT_NEXO_MANAGED_PATH = From d5cae2618518505c9f7bcfe60f55781c03b2fe2b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 11 Jun 2025 01:48:48 +0900 Subject: [PATCH 412/450] feat(scripting): add multiple arch support for scripting (ARM, x64 ...) --- engine/src/scripting/managed/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index a0f403a8b..e864e8b11 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -48,9 +48,17 @@ configure_file( @ONLY # Only replace variables in @VAR@ format ) +if(CMAKE_GENERATOR_PLATFORM) + set(ARCH_ARG --arch ${CMAKE_GENERATOR_PLATFORM}) +else() + set(ARCH_ARG "") +endif() + + # Build step add_custom_target(nexoManaged ALL COMMAND ${DOTNET_EXECUTABLE} build Nexo.csproj + ${ARCH_ARG} -c $ # Matches Debug/Release configuration WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} # Working directory for the build COMMENT "Building .NET managed project (Nexo.csproj)..." @@ -62,4 +70,6 @@ set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.pdb ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.runtimeconfig.json ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.deps.json + ${CMAKE_CURRENT_LIST_DIR}/obj + ${CMAKE_CURRENT_LIST_DIR}/bin ) From e277baf87854e3b67af067a05d3eb2f5da009ff2 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 11 Jun 2025 02:22:00 +0900 Subject: [PATCH 413/450] fix(scripting): compilation error on processor report on ARM --- editor/src/DocumentWindows/TestWindow/Shutdown.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp index 60feeb6bc..aa0ae1587 100644 --- a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp +++ b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp @@ -85,7 +85,7 @@ namespace nexo::editor { } } return "Unknown CPU"; -#elif defined(_WIN32) +#elif defined(_WIN32) && defined(_M_X64) // Using __cpuid to get CPU brand string int cpuInfo[4] = {0}; char brand[0x40] = { 0 }; From 69913b39b16a6d5ad7f1db075b7be961ccfa0279 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 9 May 2025 21:53:10 +0900 Subject: [PATCH 414/450] feat(scripting): add .NET SDK 9.0 as a dependency for .deb and implement installation steps for managed output --- engine/src/scripting/managed/CMakeLists.txt | 20 ++++++++++++++++++++ scripts/CMakeCPackOptions.cmake.in | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index e864e8b11..d2a64038f 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -73,3 +73,23 @@ set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${CMAKE_CURRENT_LIST_DIR}/obj ${CMAKE_CURRENT_LIST_DIR}/bin ) + +# Install step +install(CODE " + execute_process( + COMMAND ${DOTNET_EXECUTABLE} publish + -c ${CMAKE_BUILD_TYPE} + --output \"${NEXO_MANAGED_OUTPUT_DIR}/publish-managed\" + WORKING_DIRECTORY \"${CMAKE_CURRENT_LIST_DIR}\" + )" + COMPONENT scripts +) + +install(DIRECTORY "${NEXO_MANAGED_OUTPUT_DIR}/publish-managed/" # source directory + COMPONENT scripts + DESTINATION "bin/" + FILES_MATCHING # install only matched files + PATTERN "*.dll" # select dll files + PATTERN "*.runtimeconfig.json" # select runtimeconfig.json files + PATTERN "*.deps.json" # select deps.json files +) diff --git a/scripts/CMakeCPackOptions.cmake.in b/scripts/CMakeCPackOptions.cmake.in index 20d23469d..e520010f9 100755 --- a/scripts/CMakeCPackOptions.cmake.in +++ b/scripts/CMakeCPackOptions.cmake.in @@ -41,7 +41,7 @@ if (CPACK_GENERATOR MATCHES "DEB") set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/share/nexo-engine") message(STATUS "Setting CPACK_PACKAGING_INSTALL_PREFIX to ${CPACK_PACKAGING_INSTALL_PREFIX}") if("@NEXO_GRAPHICS_API@" STREQUAL "OpenGL") - set(CPACK_DEBIAN_PACKAGE_DEPENDS "mesa-utils, libglfw3, libxrandr2 (>= 2:1.2.0), libxrender1") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "mesa-utils, libglfw3, libxrandr2 (>= 2:1.2.0), libxrender1, dotnet-sdk-9.0") else() message(WARNING "Unknown graphics API: @NEXO_GRAPHICS_API@, cannot set dependencies") endif() From f0d7addadded701b25b468f92edb59ccd83828ce Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Wed, 21 May 2025 17:42:31 +0200 Subject: [PATCH 415/450] feat: trygetcomponentbyid, issue #284 Not final version, have to refactor getComponentTypeFromNativeId on NativeApi.cpp to remove the switch, --- engine/src/ecs/Coordinator.hpp | 15 +++++++++--- .../src/scripting/managed/NativeComponents.cs | 2 +- engine/src/scripting/native/NativeApi.cpp | 24 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 1df15e07a..7c7e1d203 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -141,8 +141,17 @@ namespace nexo::ecs { m_componentManager->registerComponent(); m_getComponentFunctions[typeid(T)] = [this](Entity entity) -> std::any { - return this->getComponent(entity); + auto opt = this->tryGetComponent(entity); + if (!opt.has_value()) + return std::any(); + T* ptr = &opt.value().get(); + return std::any(static_cast(ptr)); }; + // m_getComponentFunctions[typeid(T)] = [this](Entity entity) -> std::any { + // T* ptr = &this->getComponent(entity); + // return std::any(ptr); + // }; + m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); m_getComponentPointers[typeid(T)] = [this](Entity entity) -> std::any { auto opt = this->tryGetComponent(entity); @@ -288,8 +297,8 @@ namespace nexo::ecs { } const std::type_index& typeIndex = itType->second; - auto itGetter = m_getComponentPointers.find(typeIndex); - if (itGetter == m_getComponentPointers.end()) { + auto itGetter = m_getComponentFunctions.find(typeIndex); + if (itGetter == m_getComponentFunctions.end()) { return nullptr; } diff --git a/engine/src/scripting/managed/NativeComponents.cs b/engine/src/scripting/managed/NativeComponents.cs index f19aed9e0..2e2c9eac9 100644 --- a/engine/src/scripting/managed/NativeComponents.cs +++ b/engine/src/scripting/managed/NativeComponents.cs @@ -1,4 +1,4 @@ -//// NativeComponents.cs ////////////////////////////////////////////////////// +//// NativeComponents.cs ////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 3900eba7a..a1235f2de 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -25,6 +25,30 @@ namespace nexo::scripting { // Static message to return to C# static const char* nativeMessage = "Hello from C++ native code!"; + ecs::ComponentType getComponentTypeFromNativeId(UInt32 typeId) + { + using enum scripting::NativeComponents; + auto& coordinator = *getApp().m_coordinator; + + switch (static_cast(typeId)) + { + case Transform: + return coordinator.getComponentType(); + case AmbientLight: + return coordinator.getComponentType(); + case DirectionalLight: + return coordinator.getComponentType(); + case PointLight: + return coordinator.getComponentType(); + case SpotLight: + return coordinator.getComponentType(); + default: + LOG(NEXO_ERROR, "Unknown NativeComponent ID: {}", typeId); + return -1; + } + } + + // Implementation of the native functions extern "C" { From f150248dacac1149a56f5138afeed0bf2feeb4e2 Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Wed, 21 May 2025 18:33:29 +0200 Subject: [PATCH 416/450] fix: coordinator tests add m_getComponentPointers to the coordinator to separate engine and scripting usages --- engine/src/ecs/Coordinator.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 7c7e1d203..86450f690 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -141,16 +141,16 @@ namespace nexo::ecs { m_componentManager->registerComponent(); m_getComponentFunctions[typeid(T)] = [this](Entity entity) -> std::any { + return this->getComponent(entity); + }; + + m_getComponentPointers[typeid(T)] = [this](Entity entity) -> std::any { auto opt = this->tryGetComponent(entity); if (!opt.has_value()) return std::any(); T* ptr = &opt.value().get(); return std::any(static_cast(ptr)); }; - // m_getComponentFunctions[typeid(T)] = [this](Entity entity) -> std::any { - // T* ptr = &this->getComponent(entity); - // return std::any(ptr); - // }; m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); m_getComponentPointers[typeid(T)] = [this](Entity entity) -> std::any { @@ -297,8 +297,8 @@ namespace nexo::ecs { } const std::type_index& typeIndex = itType->second; - auto itGetter = m_getComponentFunctions.find(typeIndex); - if (itGetter == m_getComponentFunctions.end()) { + auto itGetter = m_getComponentPointers.find(typeIndex); + if (itGetter == m_getComponentPointers.end()) { return nullptr; } From 90a4bff4f5d1fad2c3c6e1b9167726ae70b5f04f Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Wed, 4 Jun 2025 16:15:45 +0200 Subject: [PATCH 417/450] feat(scripting): expose addcomponent --- engine/src/ecs/Coordinator.hpp | 7 +++++ engine/src/scripting/managed/NativeInterop.cs | 19 +++++++++++++ engine/src/scripting/native/NativeApi.cpp | 27 +++++++++++++++++++ engine/src/scripting/native/NativeApi.hpp | 3 ++- 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 86450f690..0a7fa4511 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -314,6 +314,13 @@ namespace nexo::ecs { return std::any_cast(componentAny); } + const std::unordered_map& getTypeIdToTypeIndex() const { + return m_typeIDtoTypeIndex; + } + + const std::unordered_map>& getAddComponentFunctions() const { + return m_addComponentFunctions; + } /** * @brief Get the Singleton Component object diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 30e610d4a..47376ce53 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -72,6 +72,9 @@ private struct NativeApiCallbacks [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate ComponentTypeIds NxAddComponentDelegate(UInt32 typeId, UInt32 entityId); // Function pointers public HelloFromNativeDelegate HelloFromNative; @@ -82,6 +85,7 @@ private struct NativeApiCallbacks public GetTransformDelegate GetTransform; public NxGetComponentDelegate NxGetComponent; public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; + public NxAddComponentDelegate NxAddComponent; } private static NativeApiCallbacks s_callbacks; @@ -233,6 +237,21 @@ public static unsafe ref T GetComponent(UInt32 entityId) where T : unmanaged return ref Unsafe.AsRef((void*)ptr); } + public static void AddComponent(UInt32 entityId) + { + if (!_typeToNativeIdMap.TryGetValue(typeof(T), out var typeId)) + throw new InvalidOperationException($"Unsupported component type: {typeof(T)}"); + + try + { + s_callbacks.NxAddComponent.Invoke(typeId, entityId); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling AddComponent<{typeof(T)}>: {ex.Message}"); + } + } + private static UInt32 _cubeId = 0; /// diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index a1235f2de..83ebd170b 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -96,6 +96,33 @@ namespace nexo::scripting { return opt; } + void AddComponent(UInt32 typeId, ecs::Entity entity) + { + auto& coordinator = *getApp().m_coordinator; + + const auto& map = coordinator.getTypeIdToTypeIndex(); + auto it = map.find(typeId); + + if (it == map.end()) { + LOG(NEXO_ERROR, "AddComponent: Unknown typeId {}", typeId); + return; + } + + const std::type_index& typeIndex = it->second; + const auto& addFn = coordinator.getAddComponentFunctions().find(typeIndex); + if (addFn == coordinator.getAddComponentFunctions().end()) { + LOG(NEXO_ERROR, "AddComponent: No add function registered for component {}", typeIndex.name()); + return; + } + + try { + std::any defaultConstructed = coordinator.restoreComponent(std::any{}, typeIndex); + addFn->second(entity, defaultConstructed); + } catch (const std::bad_any_cast& e) { + LOG(NEXO_ERROR, "AddComponent: bad_any_cast for component {}: {}", typeIndex.name(), e.what()); + } + } + ComponentTypeIds GetComponentTypeIds() { auto& coordinator = *getApp().m_coordinator; diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index daa8aee95..af114f889 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -89,6 +89,7 @@ namespace nexo::scripting { NEXO_RET(components::TransformComponent *) GetTransformComponent(ecs::Entity entity); NEXO_RET(void *) GetComponent(UInt32 typeId, ecs::Entity entity); NEXO_RET(ComponentTypeIds) GetComponentTypeIds(); + NEXO_RET(void) AddComponent(UInt32 typeId, ecs::Entity entity); } @@ -102,7 +103,7 @@ namespace nexo::scripting { ApiCallback GetTransformComponent{&scripting::GetTransformComponent}; ApiCallback GetComponent{&scripting::GetComponent}; ApiCallback GetComponentTypeIds{&scripting::GetComponentTypeIds}; - + ApiCallback AddComponent{&scripting::AddComponent}; }; From e2218cd24a8b59d6b7d7bf6ebbeda090b9faa33b Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Thu, 12 Jun 2025 12:14:36 +0200 Subject: [PATCH 418/450] fix(scripting-api): miss spelled component c# side and fix addcomponent Add setrestorecomponent to the ecs and link it to all component (same as the registercomponent) --- engine/src/Application.cpp | 19 ++++++++++++ engine/src/ecs/Coordinator.hpp | 13 +++++++- engine/src/scripting/managed/NativeInterop.cs | 30 ++++++++++++++----- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index 5b83aa96d..421aa8e4f 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -65,21 +65,35 @@ namespace nexo { { m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); m_coordinator->registerSingletonComponent(); m_coordinator->registerComponent(); + m_coordinator->setRestoreComponent(); } void Application::registerWindowCallbacks() const @@ -238,6 +252,11 @@ namespace nexo { // std::cout << "Application m_coordinator: " << m_coordinator.get() << std::endl; m_SceneManager.setCoordinator(m_coordinator); + const auto& map = m_coordinator->getTypeIdToTypeIndex(); + for (auto& [typeId, typeIndex] : map) + std::cout << "Registered: " << typeId << " => " << typeIndex.name() << std::endl; + + nexo::scripting::HostHandler::Parameters params; params.errorCallback = [this](const nexo::scripting::HostString& message) { LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 0a7fa4511..5fc9f1e09 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -497,7 +497,18 @@ namespace nexo::ecs { return signature.test(componentType); } - bool supportsMementoPattern(std::type_index typeIndex) const; + template + void setRestoreComponent() { + m_restoreComponentFunctions[typeid(T)] = [](const std::any&) -> std::any { + return std::any(T{}); // default-constructed + }; + } + + + 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; diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 47376ce53..778d5f14e 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -26,10 +26,10 @@ namespace Nexo public struct ComponentTypeIds { public UInt32 Transform; - public UInt32 AmbientLight; - public UInt32 DirectionalLight; - public UInt32 PointLight; - public UInt32 SpotLight; + public UInt32 AmbientLightComponent; + public UInt32 DirectionalLightComponent; + public UInt32 PointLightComponent; + public UInt32 SpotLightComponent; public UInt32 RenderComponent; public UInt32 SceneTag; public UInt32 CameraComponent; @@ -74,7 +74,8 @@ private struct NativeApiCallbacks public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate ComponentTypeIds NxAddComponentDelegate(UInt32 typeId, UInt32 entityId); + public delegate void NxAddComponentDelegate(UInt32 typeId, UInt32 entityId); + // Function pointers public HelloFromNativeDelegate HelloFromNative; @@ -120,17 +121,21 @@ private static void InitializeTypeMap() var fields = typeof(ComponentTypeIds).GetFields(); foreach (var field in fields) { - var type = Type.GetType($"Nexo.Components.{field.Name}"); + var expectedTypeName = $"Nexo.Components.{field.Name}"; + var type = Type.GetType(expectedTypeName); + if (type != null) { var value = (UInt32)field.GetValue(_componentTypeIds); _typeToNativeIdMap[type] = value; + Logger.Log(LogLevel.Debug, $"[Interop] Mapped {expectedTypeName} => {value}"); } else { - Logger.Log(LogLevel.Warn, $"[Interop] Type not found for field {field.Name}"); + Logger.Log(LogLevel.Warn, $"[Interop] Type not found for field {field.Name} (expected {expectedTypeName})"); } } + } /// @@ -283,7 +288,18 @@ public static void DemonstrateNativeCalls() UInt32 cubeId = CreateCube(new Vector3(1, 4.2f, 3), new Vector3(1, 1, 1), new Vector3(7, 8, 9), new Vector4(1, 0, 0, 1)); _cubeId = cubeId; Console.WriteLine($"Created cube with ID: {cubeId}"); + NativeInterop.AddComponent(cubeId); + + try + { + ref CameraComponent cam = ref NativeInterop.GetComponent(cubeId); + Console.WriteLine($"[✔] CameraComponent added: active={cam.active}, fov={cam.fov}"); + } + catch (Exception e) + { + Console.WriteLine($"[✘] Failed to get CameraComponent: {e.Message}"); + } // Call the function that gets a transform Console.WriteLine($"Calling GetComponent({cubeId}):"); ref Transform transform = ref GetComponent(cubeId); From 5e668eefb39bc3307c80f3fcb8ae3ec3e48c553b Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Mon, 16 Jun 2025 13:17:49 +0200 Subject: [PATCH 419/450] feat(scripting-api): expose HasComponent to the cpp api and interop c# --- engine/src/ecs/Coordinator.hpp | 4 ++ engine/src/scripting/managed/NativeInterop.cs | 54 +++++++++++++++---- engine/src/scripting/native/NativeApi.cpp | 28 ++++++++++ engine/src/scripting/native/NativeApi.hpp | 2 + 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 5fc9f1e09..3483d8387 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -322,6 +322,10 @@ namespace nexo::ecs { return m_addComponentFunctions; } + Signature getSignature(Entity entity) const { + return m_entityManager->getSignature(entity); + } + /** * @brief Get the Singleton Component object * diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 778d5f14e..6e0f769d0 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -76,6 +76,8 @@ private struct NativeApiCallbacks [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate void NxAddComponentDelegate(UInt32 typeId, UInt32 entityId); + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate bool NxHasComponentDelegate(UInt32 typeId, UInt32 entityId); // Function pointers public HelloFromNativeDelegate HelloFromNative; @@ -87,6 +89,7 @@ private struct NativeApiCallbacks public NxGetComponentDelegate NxGetComponent; public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; public NxAddComponentDelegate NxAddComponent; + public NxHasComponentDelegate NxHasComponent; } private static NativeApiCallbacks s_callbacks; @@ -257,6 +260,23 @@ public static void AddComponent(UInt32 entityId) } } + public static bool HasComponent(UInt32 entityId) + { + if (!_typeToNativeIdMap.TryGetValue(typeof(T), out var typeId)) + throw new InvalidOperationException($"Unsupported component type: {typeof(T)}"); + + try + { + return s_callbacks.NxHasComponent.Invoke(typeId, entityId); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling HasComponent<{typeof(T)}>: {ex.Message}"); + return false; + } + } + + private static UInt32 _cubeId = 0; /// @@ -288,18 +308,34 @@ public static void DemonstrateNativeCalls() UInt32 cubeId = CreateCube(new Vector3(1, 4.2f, 3), new Vector3(1, 1, 1), new Vector3(7, 8, 9), new Vector4(1, 0, 0, 1)); _cubeId = cubeId; Console.WriteLine($"Created cube with ID: {cubeId}"); - NativeInterop.AddComponent(cubeId); - - try - { - ref CameraComponent cam = ref NativeInterop.GetComponent(cubeId); - Console.WriteLine($"[✔] CameraComponent added: active={cam.active}, fov={cam.fov}"); + // AddComponent test + AddComponent(cubeId); + + try { + ref CameraComponent cam = ref GetComponent(cubeId); + Console.WriteLine($"CameraComponent added: active={cam.active}, fov={cam.fov}"); } - catch (Exception e) - { - Console.WriteLine($"[✘] Failed to get CameraComponent: {e.Message}"); + catch (Exception e) { + Console.WriteLine($"Failed to get CameraComponent: {e.Message}"); } + + // HasComponent test + if (HasComponent(cubeId)) + Console.WriteLine("Entity has a camera!"); + else + Console.WriteLine("Entity does NOT have a camera."); + + if (HasComponent(cubeId)) + Console.WriteLine("Entity has a Transform!"); + else + Console.WriteLine("Entity does NOT have a Transform."); + + if (HasComponent(cubeId)) + Console.WriteLine("Entity has a AmbientLight!"); + else + Console.WriteLine("Entity does NOT have a AmbientLight."); + // Call the function that gets a transform Console.WriteLine($"Calling GetComponent({cubeId}):"); ref Transform transform = ref GetComponent(cubeId); diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 83ebd170b..06fbb9076 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -123,6 +123,34 @@ namespace nexo::scripting { } } + bool HasComponent(UInt32 typeId, ecs::Entity entity) + { + auto& coordinator = *getApp().m_coordinator; + + const auto& typeMap = coordinator.getTypeIdToTypeIndex(); + auto it = typeMap.find(typeId); + if (it == typeMap.end()) + return false; + + const auto& typeIndex = it->second; + + ecs::ComponentType matchedType = 0; + bool found = false; + for (const auto& [compType, idx] : typeMap) { + if (idx == typeIndex) { + matchedType = compType; + found = true; + break; + } + } + + if (!found) + return false; + + ecs::Signature sig = coordinator.getSignature(entity); + return sig.test(matchedType); + } + ComponentTypeIds GetComponentTypeIds() { auto& coordinator = *getApp().m_coordinator; diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index af114f889..5b901b00d 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -90,6 +90,7 @@ namespace nexo::scripting { NEXO_RET(void *) GetComponent(UInt32 typeId, ecs::Entity entity); NEXO_RET(ComponentTypeIds) GetComponentTypeIds(); NEXO_RET(void) AddComponent(UInt32 typeId, ecs::Entity entity); + NEXO_RET(bool) HasComponent(UInt32 typeId, ecs::Entity entity); } @@ -104,6 +105,7 @@ namespace nexo::scripting { ApiCallback GetComponent{&scripting::GetComponent}; ApiCallback GetComponentTypeIds{&scripting::GetComponentTypeIds}; ApiCallback AddComponent{&scripting::AddComponent}; + ApiCallback HasComponent{&scripting::HasComponent}; }; From 8530c7453ad889d8fbc5f459a948dcf2d3441500 Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Mon, 16 Jun 2025 13:25:22 +0200 Subject: [PATCH 420/450] fix(scripting-api): missing file that cause problem on CI and build --- engine/src/scripting/managed/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index d2a64038f..e2fc43bec 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -29,7 +29,12 @@ set(SOURCES Logger.cs NativeComponents.cs Components/Transform.cs + Components/Camera.cs + Components/Render.cs + Components/Scene.cs Components/Light.cs + Components/Uuid.cs + ) # Locate the dotnet executable From 035188d6b940028fb1d0b1b6eaa52c2fec665e94 Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Sun, 22 Jun 2025 01:40:51 +0200 Subject: [PATCH 421/450] feat(scripting-api): build crash due to rebase conflicts --- engine/src/ecs/Coordinator.cpp | 26 +++++++++++++++++++++++++- engine/src/ecs/Coordinator.hpp | 5 ++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index 28fe82700..12f556af3 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -98,4 +98,28 @@ namespace nexo::ecs { auto it = m_supportsMementoPattern.find(typeIndex); return (it != m_supportsMementoPattern.end()) && it->second; } -} + + std::any Coordinator::saveComponent(const std::any& component) const + { + auto typeId = std::type_index(component.type()); + auto it = m_saveComponentFunctions.find(typeId); + if (it != m_saveComponentFunctions.end()) + return it->second(component); + return std::any(); + } + + std::any Coordinator::restoreComponent(const std::any& memento, const std::type_index& componentType) const + { + auto it = m_restoreComponentFunctions.find(componentType); + if (it != m_restoreComponentFunctions.end()) + return it->second(memento); + return std::any(); + } + + void Coordinator::addComponentAny(Entity entity, const std::type_index& typeIndex, const std::any& component) + { + auto it = m_addComponentFunctions.find(typeIndex); + if (it != m_addComponentFunctions.end()) + it->second(entity, component); + } +} \ No newline at end of file diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 3483d8387..43feeeaa2 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -509,7 +509,7 @@ namespace nexo::ecs { } - bool supportsMementoPattern(const std::any& component) const; + bool supportsMementoPattern(const std::type_index typeIndex) 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); @@ -536,6 +536,9 @@ namespace nexo::ecs { 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; }; From 636fb00016fe7279f23926d2e09f2a31c8f70c6b Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Sun, 22 Jun 2025 01:51:25 +0200 Subject: [PATCH 422/450] fix(dcripting-api): bad behavior of cpp api scripting --- engine/src/ecs/Coordinator.cpp | 6 ++++-- engine/src/ecs/Coordinator.hpp | 30 ++++++++++++++++++------------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index 12f556af3..5eae9746e 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -76,6 +76,7 @@ namespace nexo::ecs { return components; } + Entity Coordinator::duplicateEntity(const Entity sourceEntity) const { const Entity newEntity = createEntity(); @@ -93,9 +94,10 @@ namespace nexo::ecs { return newEntity; } - bool Coordinator::supportsMementoPattern(const std::type_index typeIndex) const + bool Coordinator::supportsMementoPattern(const std::any& component) const { - auto it = m_supportsMementoPattern.find(typeIndex); + auto typeId = std::type_index(component.type()); + auto it = m_supportsMementoPattern.find(typeId); return (it != m_supportsMementoPattern.end()) && it->second; } diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 43feeeaa2..f8c5c3864 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -70,13 +70,14 @@ namespace nexo::ecs { std::void_t().save())>> : std::is_same().save()), typename T::Memento> {}; + // Check if T::Memento has a restore() method that returns T template struct has_restore_method : std::false_type {}; template struct has_restore_method().restore(std::declval()))>> - : std::true_type {}; + std::void_t().restore())>> + : std::is_same().restore()), T> {}; // Combined check for full memento pattern support template @@ -153,17 +154,23 @@ namespace nexo::ecs { }; m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); - m_getComponentPointers[typeid(T)] = [this](Entity entity) -> std::any { - auto opt = this->tryGetComponent(entity); - if (!opt.has_value()) - return std::any(); - T* ptr = &opt.value().get(); - return std::any(static_cast(ptr)); + m_addComponentFunctions[typeid(T)] = [this](Entity entity, const std::any& componentAny) { + T component = std::any_cast(componentAny); + this->addComponent(entity, component); }; - m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); 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); + return std::any(memento.restore()); + }; } else { m_supportsMementoPattern.emplace(typeid(T), false); } @@ -509,13 +516,12 @@ namespace nexo::ecs { } - bool supportsMementoPattern(const std::type_index typeIndex) const; + 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; - private: template void processComponentSignature(Signature& required, Signature& excluded) const { @@ -542,4 +548,4 @@ namespace nexo::ecs { std::unordered_map> m_getComponentFunctions; std::unordered_map> m_getComponentPointers; }; -} +} \ No newline at end of file From 340a27d23ac76b1b4fb879294f179425578b175d Mon Sep 17 00:00:00 2001 From: Thomas Parenteau Date: Sun, 22 Jun 2025 02:05:20 +0200 Subject: [PATCH 423/450] fix(scipring-api): bad behavior of the HasComponent exposed fuinction --- engine/src/scripting/native/NativeApi.cpp | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 06fbb9076..abcd93488 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -130,25 +130,19 @@ namespace nexo::scripting { const auto& typeMap = coordinator.getTypeIdToTypeIndex(); auto it = typeMap.find(typeId); if (it == typeMap.end()) + { + LOG(NEXO_WARN, "HasComponent: Unknown typeId {}", typeId); return false; + } - const auto& typeIndex = it->second; + ecs::ComponentType bitIndex = typeId; - ecs::ComponentType matchedType = 0; - bool found = false; - for (const auto& [compType, idx] : typeMap) { - if (idx == typeIndex) { - matchedType = compType; - found = true; - break; - } - } + ecs::Signature signature = coordinator.getSignature(entity); - if (!found) - return false; + // LOG(NEXO_WARN, "HasComponent: entity = {}, typeId = {}, bitIndex = {}, signature = {}", + // entity, typeId, bitIndex, signature.to_string()); - ecs::Signature sig = coordinator.getSignature(entity); - return sig.test(matchedType); + return signature.test(bitIndex); } ComponentTypeIds GetComponentTypeIds() From c470ce86025eef6c5ac144ed3206c800576a24af Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 22 Jun 2025 05:43:10 +0900 Subject: [PATCH 424/450] feat(scripting-system): add WorldState in native and managed --- engine/src/WorldState.hpp | 33 ++++++++++++++++ .../native/systems/ManagedWorldState.hpp | 39 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 engine/src/WorldState.hpp create mode 100644 engine/src/scripting/native/systems/ManagedWorldState.hpp diff --git a/engine/src/WorldState.hpp b/engine/src/WorldState.hpp new file mode 100644 index 000000000..a2a5f4b4d --- /dev/null +++ b/engine/src/WorldState.hpp @@ -0,0 +1,33 @@ +//// WorldState.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: Guillaume HEIN +// Date: 21/06/2025 +// Description: Header file for the WorldState class, +// which manages the state of the world in the game engine +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "Application.hpp" + +namespace nexo { + + struct WorldState { + struct WorldTime { + double deltaTime = 0.0; // Time since last update + double totalTime = 0.0; // Total time since the start of the world + } time; + + struct WorldStats { + int frameCount = 0; // Number of frames rendered + } stats; + }; + +} // namespace nexo::scripting \ No newline at end of file diff --git a/engine/src/scripting/native/systems/ManagedWorldState.hpp b/engine/src/scripting/native/systems/ManagedWorldState.hpp new file mode 100644 index 000000000..86650c210 --- /dev/null +++ b/engine/src/scripting/native/systems/ManagedWorldState.hpp @@ -0,0 +1,39 @@ +//// WorldState.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: Guillaume HEIN +// Date: 21/06/2025 +// Description: Header file for the WorldState class, +// which manages the state of the world in the game engine +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "Application.hpp" + +namespace nexo::scripting { + + struct WorldState { + struct WorldTime { + double deltaTime = 0.0; // Time since last update + double totalTime = 0.0; // Total time since the start of the world + } time; + + struct WorldStats { + int frameCount = 0; // Number of frames rendered + } stats; + + void updateTime() + { + const auto &app = Application::getInstance(); + time.deltaTime = app. + } + }; + +} // namespace nexo::scripting \ No newline at end of file From 286a2d626632fe241def491c47774808a84e0d5c Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 22 Jun 2025 05:50:22 +0900 Subject: [PATCH 425/450] feat(scripting-system): add more WorldState files --- common/Timestep.hpp | 11 +-- engine/src/scripting/managed/CMakeLists.txt | 6 +- .../scripting/managed/Systems/WorldState.cs | 79 +++++++++++++++++++ .../native/systems/ManagedWorldState.hpp | 24 ++++-- 4 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 engine/src/scripting/managed/Systems/WorldState.cs diff --git a/common/Timestep.hpp b/common/Timestep.hpp index f1f61eb03..4349d1e46 100644 --- a/common/Timestep.hpp +++ b/common/Timestep.hpp @@ -16,14 +16,15 @@ namespace nexo { class Timestep { public: - Timestep(const float time = 0.0f) : m_time(time) {}; + explicit(false) Timestep(const double time = 0.0f) : m_time(time) {} - operator float() const {return m_time; }; + explicit operator float() const { return m_time; } + explicit operator double() const { return m_time; } - [[nodiscard]] float getSeconds() const {return m_time; }; - [[nodiscard]] float getMilliseconds() const { return m_time * 1000.0f; }; + [[nodiscard]] double getSeconds() const {return m_time; } + [[nodiscard]] double getMilliseconds() const { return m_time * 1000.0f; } private: - float m_time = 0.0f; + double m_time = 0.0f; }; } diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index e2fc43bec..a7c90fce2 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -28,13 +28,17 @@ set(SOURCES NativeInterop.cs Logger.cs NativeComponents.cs + Components/Camera.cs + Components/Light.cs + Components/Render.cs + Components/Scene.cs Components/Transform.cs Components/Camera.cs Components/Render.cs Components/Scene.cs Components/Light.cs Components/Uuid.cs - + Systems/WorldState.cs ) # Locate the dotnet executable diff --git a/engine/src/scripting/managed/Systems/WorldState.cs b/engine/src/scripting/managed/Systems/WorldState.cs new file mode 100644 index 000000000..8dd7cddd9 --- /dev/null +++ b/engine/src/scripting/managed/Systems/WorldState.cs @@ -0,0 +1,79 @@ +//// SystemBase.cs /////////////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 11/06/2025 +// Description: Interface for the user's systems in NEXO's ECS framework +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Runtime.InteropServices; + +namespace Nexo.Systems; + +// public class WorldState +// { +// [StructLayout(LayoutKind.Sequential)] +// public struct NativeWorldState +// { +// [StructLayout(LayoutKind.Sequential)] +// public struct WorldTime { +// public Double DeltaTime; // Time since last update +// public Double TotalTime; // Total time since the start of the world +// } +// +// [StructLayout(LayoutKind.Sequential)] +// public struct WorldStats +// { +// public UInt64 frameCount; // Number of frames rendered +// } +// +// public WorldTime Time; +// public WorldStats Stats; +// } +// +// public NativeWorldState State; +// } + +public unsafe class WorldState +{ + [StructLayout(LayoutKind.Sequential)] + public struct NativeWorldState + { + [StructLayout(LayoutKind.Sequential)] + public struct WorldTime { + public Double DeltaTime; // Time since last update + public Double TotalTime; // Total time since the start of the world + } + + [StructLayout(LayoutKind.Sequential)] + public struct WorldStats + { + public UInt64 FrameCount; // Number of frames rendered + } + + public WorldTime Time; + public WorldStats Stats; + } + + private readonly NativeWorldState* _nativePtr; + + internal WorldState(IntPtr nativePtr) + { + _nativePtr = (NativeWorldState*)nativePtr.ToPointer(); + } + + internal WorldState(NativeWorldState* nativePtr) + { + _nativePtr = nativePtr; + } + + // Direct access to native structs (no copying) + public ref NativeWorldState.WorldTime Time => ref _nativePtr->Time; + public ref NativeWorldState.WorldStats Stats => ref _nativePtr->Stats; +} diff --git a/engine/src/scripting/native/systems/ManagedWorldState.hpp b/engine/src/scripting/native/systems/ManagedWorldState.hpp index 86650c210..019bf99d9 100644 --- a/engine/src/scripting/native/systems/ManagedWorldState.hpp +++ b/engine/src/scripting/native/systems/ManagedWorldState.hpp @@ -17,22 +17,30 @@ #include "Application.hpp" +#include "scripting/native/ManagedTypedef.hpp" + namespace nexo::scripting { - struct WorldState { + /** + * @brief Represents the state of the world in the game engine. + * This structure is 1 to 1 equivalent to the WorldState class in the C# managed code. + * @warning If you modify this structure, ensure that the C# managed code is updated accordingly. + */ + struct ManagedWorldState { struct WorldTime { - double deltaTime = 0.0; // Time since last update - double totalTime = 0.0; // Total time since the start of the world + Double deltaTime = 0.0; // Time since last update + Double totalTime = 0.0; // Total time since the start of the world } time; struct WorldStats { - int frameCount = 0; // Number of frames rendered + UInt64 frameCount = 0; // Number of frames rendered } stats; - void updateTime() - { - const auto &app = Application::getInstance(); - time.deltaTime = app. + void update(const WorldState& worldState) { + // Update the world state with the current time and stats + time.deltaTime = worldState.time.deltaTime; + time.totalTime = worldState.time.totalTime; + stats.frameCount = worldState.stats.frameCount; } }; From 62695a3e9c561a8a335a7149cac8c1eff4da6f62 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 22 Jun 2025 06:05:40 +0900 Subject: [PATCH 426/450] feat(scripting-system): remove update from Scripting host class --- editor/main.cpp | 18 +--------------- engine/src/scripting/native/Scripting.cpp | 26 +++++++++++++++++------ engine/src/scripting/native/Scripting.hpp | 5 ++++- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/editor/main.cpp b/editor/main.cpp index 689d45921..1759e738a 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -48,17 +48,6 @@ try { editor.init(); - auto &scriptHost = nexo::scripting::HostHandler::getInstance(); - - if (scriptHost.runScriptExample() == EXIT_FAILURE) { - LOG(NEXO_ERROR, "Error in runScriptExample"); - } else { - LOG(NEXO_INFO, "Successfully ran runScriptExample"); - } - - auto clock = std::chrono::high_resolution_clock::now(); - auto scriptDetlaStart = clock; - std::chrono::duration scriptDeltaElapsed = clock - scriptDetlaStart; while (editor.isOpen()) { auto start = std::chrono::high_resolution_clock::now(); @@ -68,14 +57,9 @@ try { auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = end - start; - scriptDeltaElapsed = std::chrono::high_resolution_clock::now() - scriptDetlaStart; - scriptHost.update(scriptDeltaElapsed.count() / 1000.0); - scriptDetlaStart = std::chrono::high_resolution_clock::now(); - std::this_thread::sleep_for(std::chrono::milliseconds(16) - elapsed); - - } + editor.shutdown(); return 0; } catch (const nexo::Exception &e) { diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 513e348dc..76f72fe17 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -77,12 +77,6 @@ namespace nexo::scripting { return m_status = SUCCESS; } - HostHandler::Status HostHandler::update(Double deltaTime) - { - m_managedApi.NativeInterop.Update(deltaTime); - return m_status; - } - void *HostHandler::getManagedFptrVoid(const char_t* typeName, const char_t* methodName, const char_t* delegateTypeName) const { @@ -226,7 +220,7 @@ namespace nexo::scripting { HostHandler::Status HostHandler::initManagedApi() try { m_managedApi.NativeInterop = { - .Initialize = getManagedFptr( + .Initialize = getManagedFptr( "Nexo.NativeInterop, Nexo", "Initialize", UNMANAGEDCALLERSONLY @@ -270,6 +264,24 @@ namespace nexo::scripting { ) }; + m_managedApi.SystemBase = { + .InitializeSystems = getManagedFptr( + "Nexo.Systems.SystemBase, Nexo", + "InitializeSystems", + UNMANAGEDCALLERSONLY + ), + .ShutdownSystems = getManagedFptr( + "Nexo.Systems.SystemBase, Nexo", + "ShutdownSystems", + UNMANAGEDCALLERSONLY + ), + .UpdateSystems = getManagedFptr( + "Nexo.Systems.SystemBase, Nexo", + "UpdateSystems", + UNMANAGEDCALLERSONLY + ) + }; + return m_status = checkManagedApi(); } catch (const std::exception& e) { diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index c6d352e73..61e936b1a 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -130,7 +130,10 @@ namespace nexo::scripting { Status initialize(Parameters parameters); - Status update(Double deltaTime); + const ManagedApi& getManagedApi() const + { + return m_managedApi; + } inline void *getManagedFptrVoid(const char_t *typeName, const char_t *methodName, const char_t *delegateTypeName) const; From 878f84f4b6eb7a1a83becae715fdb8afc1c4f1b4 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 22 Jun 2025 06:08:01 +0900 Subject: [PATCH 427/450] feat(scripting-system): add Scripting system, user defined C# systems --- editor/src/Editor.cpp | 7 + engine/CMakeLists.txt | 1 + engine/src/Application.cpp | 40 ++--- engine/src/Application.hpp | 19 ++- engine/src/scripting/managed/CMakeLists.txt | 1 + engine/src/scripting/managed/NativeInterop.cs | 20 ++- .../scripting/managed/Systems/SystemBase.cs | 149 ++++++++++++++++++ engine/src/scripting/native/ManagedApi.hpp | 11 +- engine/src/scripting/native/NativeApi.cpp | 4 +- engine/src/systems/ScriptingSystem.cpp | 93 +++++++++++ engine/src/systems/ScriptingSystem.hpp | 41 +++++ 11 files changed, 352 insertions(+), 34 deletions(-) create mode 100644 engine/src/scripting/managed/Systems/SystemBase.cs create mode 100644 engine/src/systems/ScriptingSystem.cpp create mode 100644 engine/src/systems/ScriptingSystem.hpp diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index b7dfabf3f..4ee146b9d 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -35,6 +35,9 @@ namespace nexo::editor { void Editor::shutdown() const { + Application& app = Application::getInstance(); + + app.shutdownScripting(); LOG(NEXO_INFO, "Closing editor"); LOG(NEXO_INFO, "All windows destroyed"); m_windowRegistry.shutdown(); @@ -220,6 +223,9 @@ namespace nexo::editor { 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 } bool Editor::isOpen() const @@ -542,6 +548,7 @@ namespace nexo::editor { void Editor::update() const { m_windowRegistry.update(); + Application& app = Application::getInstance(); getApp().endFrame(); } } diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 47e548a93..4b14518d6 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -49,6 +49,7 @@ set(COMMON_SOURCES engine/src/systems/CameraSystem.cpp engine/src/systems/RenderSystem.cpp engine/src/systems/LightSystem.cpp + engine/src/systems/ScriptingSystem.cpp engine/src/systems/lights/AmbientLightSystem.cpp engine/src/systems/lights/PointLightsSystem.cpp engine/src/systems/lights/DirectionalLightsSystem.cpp diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index 421aa8e4f..5418216e2 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -34,6 +34,7 @@ #include "scripting/native/Scripting.hpp" #include "systems/CameraSystem.hpp" #include "systems/RenderSystem.hpp" +#include "systems/ScriptingSystem.hpp" #include "systems/lights/DirectionalLightsSystem.hpp" #include "systems/lights/PointLightsSystem.hpp" @@ -195,6 +196,18 @@ namespace nexo { auto spotLightSystem = m_coordinator->registerGroupSystem(); auto ambientLightSystem = m_coordinator->registerGroupSystem(); m_lightSystem = std::make_shared(ambientLightSystem, directionalLightSystem, pointLightSystem, spotLightSystem); + + m_scriptingSystem = std::make_shared(); + } + + int Application::initScripting() const + { + return m_scriptingSystem->init(); + } + + int Application::shutdownScripting() const + { + return m_scriptingSystem->shutdown(); } Application::Application() @@ -256,36 +269,23 @@ namespace nexo { for (auto& [typeId, typeIndex] : map) std::cout << "Registered: " << typeId << " => " << typeIndex.name() << std::endl; - - nexo::scripting::HostHandler::Parameters params; - params.errorCallback = [this](const nexo::scripting::HostString& message) { - LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); - m_latestScriptingError = message.to_utf8(); - }; - - - nexo::scripting::HostHandler& host = nexo::scripting::HostHandler::getInstance(); - - // Initialize the host - if (host.initialize(params) != nexo::scripting::HostHandler::SUCCESS) { - LOG(NEXO_ERROR, "Failed to initialize host"); - THROW_EXCEPTION(scripting::ScriptingBackendInitFailed, m_latestScriptingError); - } - LOG(NEXO_DEV, "Application initialized"); } void Application::beginFrame() { - const auto time = static_cast(glfwGetTime()); - m_currentTimestep = time - m_lastFrameTime; - m_lastFrameTime = time; + const auto time = glfwGetTime(); + m_worldState.time.deltaTime = time - m_worldState.time.totalTime; + m_worldState.time.totalTime = time; + m_worldState.stats.frameCount += 1; } void Application::run(const SceneInfo &sceneInfo) { auto &renderContext = m_coordinator->getSingletonComponent(); + m_scriptingSystem->update(); + if (!m_isMinimized) { renderContext.sceneRendered = static_cast(sceneInfo.id); @@ -303,7 +303,7 @@ namespace nexo { } if (m_SceneManager.getScene(sceneInfo.id).isActive()) { - m_perspectiveCameraControllerSystem->update(m_currentTimestep); + m_perspectiveCameraControllerSystem->update(m_worldState.time.deltaTime); } } diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index cf5399f0f..9bb22334a 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -27,6 +27,7 @@ #include "core/scene/SceneManager.hpp" #include "Logger.hpp" #include "Timer.hpp" +#include "WorldState.hpp" #include "components/Light.hpp" #include "systems/CameraSystem.hpp" @@ -37,6 +38,10 @@ namespace nexo { + namespace system { + class ScriptingSystem; + } + enum EventDebugFlags { DEBUG_LOG_RESIZE_EVENT = 1 << 0, DEBUG_LOG_KEYBOARD_EVENT = 1 << 1, @@ -222,8 +227,12 @@ namespace nexo { 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]] 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; static std::shared_ptr m_coordinator; protected: @@ -248,8 +257,7 @@ namespace nexo { bool m_displayProfileResult = true; std::shared_ptr m_window; - float m_lastFrameTime = 0.0f; - Timestep m_currentTimestep; + WorldState m_worldState; int m_eventDebugFlags{}; @@ -258,9 +266,8 @@ namespace nexo { std::shared_ptr m_lightSystem; std::shared_ptr m_perspectiveCameraControllerSystem; std::shared_ptr m_perspectiveCameraTargetSystem; + std::shared_ptr m_scriptingSystem; std::vector m_profilesResults; - - std::string m_latestScriptingError; }; } diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index a7c90fce2..e54ac78f1 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -38,6 +38,7 @@ set(SOURCES Components/Scene.cs Components/Light.cs Components/Uuid.cs + Systems/SystemBase.cs Systems/WorldState.cs ) diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 6e0f769d0..bac5b9fab 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -102,7 +102,7 @@ private struct NativeApiCallbacks /// Pointer to the struct /// Size of the struct [UnmanagedCallersOnly] - public static Int32 Initialize(IntPtr structPtr, Int32 structSize) + public static Int32 Initialize(IntPtr structPtr, UInt32 structSize) { if (structSize != Marshal.SizeOf()) { @@ -113,13 +113,17 @@ public static Int32 Initialize(IntPtr structPtr, Int32 structSize) // Marshal the struct from the IntPtr to the managed struct s_callbacks = Marshal.PtrToStructure(structPtr); _componentTypeIds = s_callbacks.NxGetComponentTypeIds.Invoke(); - InitializeTypeMap(); + if (InitializeTypeMap() != 0) + { + Logger.Log(LogLevel.Fatal, "Failed to initialize type map for component type IDs."); + return 1; + } Logger.Log(LogLevel.Info, "Native API initialized."); return 0; } - private static void InitializeTypeMap() + private static Int32 InitializeTypeMap() { var fields = typeof(ComponentTypeIds).GetFields(); foreach (var field in fields) @@ -129,8 +133,13 @@ private static void InitializeTypeMap() if (type != null) { - var value = (UInt32)field.GetValue(_componentTypeIds); - _typeToNativeIdMap[type] = value; + var value = field.GetValue(_componentTypeIds); + if (value == null) + { + Logger.Log(LogLevel.Warn, $"Field {field.Name} in ComponentTypeIds is null"); + return 1; + } + _typeToNativeIdMap[type] = (UInt32)value; Logger.Log(LogLevel.Debug, $"[Interop] Mapped {expectedTypeName} => {value}"); } else @@ -139,6 +148,7 @@ private static void InitializeTypeMap() } } + return 0; } /// diff --git a/engine/src/scripting/managed/Systems/SystemBase.cs b/engine/src/scripting/managed/Systems/SystemBase.cs new file mode 100644 index 000000000..07822ce60 --- /dev/null +++ b/engine/src/scripting/managed/Systems/SystemBase.cs @@ -0,0 +1,149 @@ +//// SystemBase.cs /////////////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 11/06/2025 +// Description: Interface for the user's systems in NEXO's ECS framework +// +/////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.InteropServices; + +namespace Nexo.Systems +{ + + public abstract class SystemBase + { + private static readonly List AllSystems = []; + private static Boolean _isInitialized = false; + + protected String Name => GetType().Name; + protected Boolean IsActive { get; set; } = true; + + [UnmanagedCallersOnly] + public static unsafe Int32 InitializeSystems(WorldState.NativeWorldState *nativeWorldState, UInt32 size) + { + if (_isInitialized) return 0; + + if (size != Marshal.SizeOf()) + { + Logger.Log(LogLevel.Fatal, $"Struct size mismatch between C++ and C# for {nameof(WorldState.NativeWorldState)}, expected {Marshal.SizeOf()}, got {size}"); + return 1; + } + + try + { + // Find all types that derive from SystemBase + var systemTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsSubclassOf(typeof(SystemBase)) && + !type.IsAbstract) + .ToList(); + + // Create instances of all system types + foreach (var systemType in systemTypes) + { + var instance = Activator.CreateInstance(systemType); + if (instance == null) + { + Logger.Log(LogLevel.Error, $"Failed to create instance of {systemType.Name}"); + return 1; + } + AllSystems.Add((SystemBase)instance); + } + + // Initialize all systems + foreach (var system in AllSystems) + { + system.OnInitialize(new WorldState(nativeWorldState)); + } + + _isInitialized = true; + return 0; + } + catch (Exception ex) + { + Logger.Log(LogLevel.Error, ex.Message); + return 1; + } + } + + [UnmanagedCallersOnly] + public static unsafe Int32 UpdateSystems(WorldState.NativeWorldState *nativeWorldState, UInt32 size) + { + if (!_isInitialized) + { + Logger.Log(LogLevel.Error, "Systems not initialized. Call InitializeSystems first."); + return 1; + } + if (size != Marshal.SizeOf()) + { + Logger.Log(LogLevel.Fatal, $"Struct size mismatch between C++ and C# for {nameof(WorldState.NativeWorldState)}, expected {Marshal.SizeOf()}, got {size}"); + return 1; + } + + try + { + // Update all active systems + foreach (var system in AllSystems.Where(s => s.IsActive)) + { + system.OnUpdate(new WorldState(nativeWorldState)); + } + } + catch (Exception ex) + { + Logger.Log(LogLevel.Error, ex.Message); + return 1; + } + + return 0; + } + + [UnmanagedCallersOnly] + public static unsafe Int32 ShutdownSystems(WorldState.NativeWorldState *nativeWorldState, UInt32 size) + { + if (size != Marshal.SizeOf()) + { + Logger.Log(LogLevel.Fatal, $"Struct size mismatch between C++ and C# for {nameof(WorldState.NativeWorldState)}, expected {Marshal.SizeOf()}, got {size}"); + return 1; + } + + foreach (var system in AllSystems) + { + system.OnShutdown(new WorldState(nativeWorldState)); + } + + AllSystems.Clear(); + _isInitialized = false; + return 0; + } + + public static T? GetSystem() where T : SystemBase + { + return AllSystems.OfType().FirstOrDefault(); + } + + public static IEnumerable GetAllSystems() + { + return AllSystems.AsReadOnly(); + } + + protected virtual void OnInitialize(WorldState worldState) + { + } + + protected abstract void OnUpdate(WorldState worldState); + + protected virtual void OnShutdown(WorldState worldState) + { + } + + } + +} \ No newline at end of file diff --git a/engine/src/scripting/native/ManagedApi.hpp b/engine/src/scripting/native/ManagedApi.hpp index 4f1ba585a..ec4fe88fd 100644 --- a/engine/src/scripting/native/ManagedApi.hpp +++ b/engine/src/scripting/native/ManagedApi.hpp @@ -20,6 +20,7 @@ #include "Exception.hpp" #include "ManagedTypedef.hpp" #include "components/Transform.hpp" +#include "systems/ManagedWorldState.hpp" namespace nexo::scripting { @@ -79,7 +80,7 @@ namespace nexo::scripting { struct ManagedApi { struct NativeInteropApi { - ManagedApiFn Initialize; + ManagedApiFn Initialize; ManagedApiFn DemonstrateNativeCalls; @@ -102,6 +103,14 @@ namespace nexo::scripting { ManagedApiFn AddToPtr; } Lib; + struct SystemBaseApi { + ManagedApiFn InitializeSystems; + + ManagedApiFn ShutdownSystems; + + ManagedApiFn UpdateSystems; + } SystemBase; + }; diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index abcd93488..5251b3b0d 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -70,8 +70,8 @@ namespace nexo::scripting { LOG(static_cast(level), "[Scripting] {}", message); } - ecs::Entity CreateCube(const Vector3 pos, const Vector3 size, const Vector3 rotation, const Vector4 color) { - + ecs::Entity CreateCube(const Vector3 pos, const Vector3 size, const Vector3 rotation, const Vector4 color) + { auto &app = getApp(); const ecs::Entity basicCube = EntityFactory3D::createCube(std::move(pos), std::move(size), std::move(rotation), std::move(color)); app.getSceneManager().getScene(0).addEntity(basicCube); diff --git a/engine/src/systems/ScriptingSystem.cpp b/engine/src/systems/ScriptingSystem.cpp new file mode 100644 index 000000000..043cac8de --- /dev/null +++ b/engine/src/systems/ScriptingSystem.cpp @@ -0,0 +1,93 @@ +//// ScriptingSystem.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: Guillaume HEIN +// Date: 21/06/2025 +// Description: Source file for the scripting system +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ScriptingSystem.hpp" + +#include "scripting/native/Scripting.hpp" + +namespace nexo::system { + + ScriptingSystem::ScriptingSystem() + { + nexo::scripting::HostHandler::Parameters params; + params.errorCallback = [this](const nexo::scripting::HostString& message) { + LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); + m_latestScriptingError = message.to_utf8(); + }; + + nexo::scripting::HostHandler& host = nexo::scripting::HostHandler::getInstance(); + + // Initialize the host + if (host.initialize(params) != nexo::scripting::HostHandler::SUCCESS) { + LOG(NEXO_ERROR, "Failed to initialize host"); + THROW_EXCEPTION(scripting::ScriptingBackendInitFailed, m_latestScriptingError); + } + } + + int ScriptingSystem::init() + { + + auto &scriptHost = scripting::HostHandler::getInstance(); + + if (scriptHost.runScriptExample() == EXIT_FAILURE) { + LOG(NEXO_ERROR, "Error in runScriptExample"); + return 1; + } + + LOG(NEXO_INFO, "Successfully ran runScriptExample"); + + updateWorldState(); + if (auto ret = scriptHost.getManagedApi().SystemBase.InitializeSystems(&m_worldState, sizeof(m_worldState)); ret != 0) { + LOG(NEXO_ERROR, "Failed to initialize scripting systems: {}", ret); + return ret; + } + LOG(NEXO_INFO, "Scripting systems initialized successfully"); + return 0; + } + + int ScriptingSystem::update() + { + const auto &scriptHost = scripting::HostHandler::getInstance(); + auto &api = scriptHost.getManagedApi(); + + updateWorldState(); + if (auto ret = api.SystemBase.UpdateSystems(&m_worldState, sizeof(m_worldState)); ret != 0) { + LOG_ONCE(NEXO_ERROR, "Failed to update scripting systems"); + return ret; + } + Logger::resetOnce(NEXO_LOG_ONCE_KEY("Failed to update scripting systems")); + return 0; + } + + int ScriptingSystem::shutdown() + { + const auto &scriptHost = scripting::HostHandler::getInstance(); + auto &api = scriptHost.getManagedApi(); + + updateWorldState(); + if (auto ret = api.SystemBase.ShutdownSystems(&m_worldState, sizeof(m_worldState)); ret != 0) { + LOG(NEXO_ERROR, "Failed to shutdown scripting systems: {}", ret); + return ret; + } + LOG(NEXO_INFO, "Scripting systems shutdown successfully"); + return 0; + } + + void ScriptingSystem::updateWorldState() + { + Application& app = Application::getInstance(); + m_worldState.update(app.getWorldState()); + } + +} // namespace nexo::system \ No newline at end of file diff --git a/engine/src/systems/ScriptingSystem.hpp b/engine/src/systems/ScriptingSystem.hpp new file mode 100644 index 000000000..4c12109d7 --- /dev/null +++ b/engine/src/systems/ScriptingSystem.hpp @@ -0,0 +1,41 @@ +//// ScriptingSystem.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: Guillaume HEIN +// Date: 21/06/2025 +// Description: Header file for the scripting system +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +#include "scripting/native/systems/ManagedWorldState.hpp" + +namespace nexo::system { + + class ScriptingSystem { + public: + ScriptingSystem(); + ~ScriptingSystem() = default; + + int init(); + + int update(); + + int shutdown(); + + private: + void updateWorldState(); + + std::string m_latestScriptingError; + scripting::ManagedWorldState m_worldState = {}; + }; + +} \ No newline at end of file From 30737dd35961a66b754c137c0d679b326bc8abf5 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 22 Jun 2025 06:08:49 +0900 Subject: [PATCH 428/450] feat(scripting-system): add example C# CubeSystem (spawn + move cubes) --- engine/src/scripting/managed/CMakeLists.txt | 1 + .../scripting/managed/Scripts/CubeSystem.cs | 134 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 engine/src/scripting/managed/Scripts/CubeSystem.cs diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index e54ac78f1..c07106095 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -40,6 +40,7 @@ set(SOURCES Components/Uuid.cs Systems/SystemBase.cs Systems/WorldState.cs + Scripts/CubeSystem.cs ) # Locate the dotnet executable diff --git a/engine/src/scripting/managed/Scripts/CubeSystem.cs b/engine/src/scripting/managed/Scripts/CubeSystem.cs new file mode 100644 index 000000000..4614aedff --- /dev/null +++ b/engine/src/scripting/managed/Scripts/CubeSystem.cs @@ -0,0 +1,134 @@ +//// CubeSystem.cs //////////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 21/06/2025 +// Description: Source file for the Cube system in NEXO's ECS framework +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Numerics; +using Nexo.Components; +using Nexo.Systems; + +namespace Nexo.Scripts; + +public class CubeSystem : SystemBase +{ + private struct CubeAnimationState + { + public Single Angle; + public Single BreathingPhase; + } + + private readonly Dictionary _cubeStates = []; + + protected override void OnInitialize(WorldState worldState) + { + Logger.Log(LogLevel.Info, $"Initializing {Name} system"); + } + + private void MoveCube(UInt32 cubeId, Single deltaTime) + { + ref Transform transform = ref NativeInterop.GetComponent(cubeId); + + // Get or create animation state for this cube + if (!_cubeStates.TryGetValue(cubeId, out var state)) + { + state = new CubeAnimationState + { + Angle = 0.0f, + BreathingPhase = 0.0f + }; + } + + // Rotating cube effect + float rotationSpeed = 1.0f; + transform.quat = Quaternion.CreateFromAxisAngle(Vector3.UnitY, deltaTime * rotationSpeed) * transform.quat; + + // Circling cube effect + float speed = 1.0f; + float radius = 7.0f; + Vector3 origin = new Vector3(0, 5, 0); + + 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 + ); + + // Breathing cube effect + float startScale = 1.0f; + float endScale = 2.0f; + float breathingSpeed = 0.5f; + + state.BreathingPhase += breathingSpeed * deltaTime * MathF.PI * 2.0f; + if (state.BreathingPhase > MathF.PI * 2.0f) + { + state.BreathingPhase -= MathF.PI * 2.0f; + } + + transform.size.Z = startScale + ((MathF.Sin(state.BreathingPhase) * 0.5f + 0.5f) * (endScale - startScale)); + + // Update the stored state + _cubeStates[cubeId] = state; + } + + private void SpawnCube(Vector3 position, Vector3 size, Vector3 rotation, Vector4 color) + { + var cubeId = NativeInterop.CreateCube(position, size, rotation, color); + _cubeStates[cubeId] = new CubeAnimationState + { + Angle = Random.Shared.NextSingle() * MathF.PI * 2.0f, + BreathingPhase = Random.Shared.NextSingle() * MathF.PI * 2.0f + }; + } + + protected override void OnUpdate(WorldState worldState) + { + Single deltaTime = (Single)worldState.Time.DeltaTime; + + // If 2 seconds have passed since last spawn, spawn a new cube + if (worldState.Time.TotalTime % 2.0 < deltaTime) + { + Vector3 position = new Vector3(1, 4.2f, 3); + Vector3 size = new Vector3(1, 1, 1); + Vector3 rotation = new Vector3(7, 8, 9); + Vector4 color = new Vector4( + Random.Shared.NextSingle(), + Random.Shared.NextSingle(), + Random.Shared.NextSingle(), + 1.0f + ); + SpawnCube(position, size, rotation, color); + } + + foreach (var cubeId in _cubeStates.Keys) + { + MoveCube(cubeId, deltaTime); + } + } + + protected override void OnShutdown(WorldState worldState) + { + _cubeStates.Clear(); + Logger.Log(LogLevel.Info, $"Shutting down {Name} system"); + } + + // Helper method to clean up destroyed cubes + public void RemoveCubeState(uint cubeId) + { + _cubeStates.Remove(cubeId); + } +} \ No newline at end of file From 015801dd785ea951345ce8551586419e4f0ad852 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 05:49:32 +0900 Subject: [PATCH 429/450] feat(scripting-system): remove raw values in print ringing bell --- engine/src/Application.cpp | 4 ---- engine/src/scripting/managed/CMakeLists.txt | 4 ---- 2 files changed, 8 deletions(-) diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index 5418216e2..fdb268c06 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -265,10 +265,6 @@ namespace nexo { // std::cout << "Application m_coordinator: " << m_coordinator.get() << std::endl; m_SceneManager.setCoordinator(m_coordinator); - const auto& map = m_coordinator->getTypeIdToTypeIndex(); - for (auto& [typeId, typeIndex] : map) - std::cout << "Registered: " << typeId << " => " << typeIndex.name() << std::endl; - LOG(NEXO_DEV, "Application initialized"); } diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index c07106095..2e36a801d 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -33,10 +33,6 @@ set(SOURCES Components/Render.cs Components/Scene.cs Components/Transform.cs - Components/Camera.cs - Components/Render.cs - Components/Scene.cs - Components/Light.cs Components/Uuid.cs Systems/SystemBase.cs Systems/WorldState.cs From d7fd7eda5ee62edd7d8bc0da71976ac2fdb0f1b2 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 04:53:08 +0900 Subject: [PATCH 430/450] feat(scripting-component): add TypeErasedComponentArray TypeErasedComponentArray will be used for custom Components that do not use the templated ComponentArray class. For example, the C# scripting can register any components just by specifying the size of its structure. --- engine/src/ecs/ComponentArray.hpp | 373 +++++++++++++++++++++++++++++- engine/src/ecs/Components.hpp | 57 ++++- engine/src/ecs/Coordinator.hpp | 30 +-- engine/src/ecs/Definitions.hpp | 12 +- 4 files changed, 431 insertions(+), 41 deletions(-) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 52559659e..f83eeb1e4 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -51,6 +51,50 @@ namespace nexo::ecs { virtual void entityDestroyed(Entity entity) = 0; virtual void duplicateComponent(Entity sourceEntity, Entity destEntity) = 0; + + /** + * @brief Gets the size of each component in bytes + * @return Size of individual component in bytes + */ + [[nodiscard]] virtual size_t getComponentSize() const = 0; + + /** + * @brief Gets the total number of components in the array + * @return The number of active components + */ + [[nodiscard]] virtual size_t size() const = 0; + + /** + * @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]] virtual void* getRawComponent(Entity entity) = 0; + + /** + * @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]] virtual const void* getRawComponent(Entity entity) const = 0; + + /** + * @brief Gets raw pointer to all component data + * @return Raw pointer to contiguous component data + */ + [[nodiscard]] virtual void* getRawData() = 0; + + /** + * @brief Gets const raw pointer to all component data + * @return Const raw pointer to contiguous component data + */ + [[nodiscard]] virtual const void* getRawData() const = 0; + + /** + * @brief Gets a span of all entities with this component + * @return Span of entity IDs + */ + [[nodiscard]] virtual std::span entities() const = 0; }; /** @@ -67,7 +111,7 @@ namespace nexo::ecs { * @note This class is not thread-safe. Access should be synchronized externally when * used in multi-threaded contexts. */ - template + template requires (capacity >= 1) class alignas(64) ComponentArray final : public IComponentArray { public: @@ -89,6 +133,35 @@ namespace nexo::ecs { m_componentArray.reserve(capacity); } + [[nodiscard]] size_t getComponentSize() const override + { + return sizeof(T); + } + + [[nodiscard]] void* getRawComponent(Entity entity) override + { + if (!hasComponent(entity)) + return nullptr; + return &m_componentArray[m_sparse[entity]]; + } + + [[nodiscard]] const void* getRawComponent(Entity entity) const override + { + if (!hasComponent(entity)) + return nullptr; + return &m_componentArray[m_sparse[entity]]; + } + + [[nodiscard]] void* getRawData() override + { + return m_componentArray.data(); + } + + [[nodiscard]] const void* getRawData() const override + { + return m_componentArray.data(); + } + /** * @brief Inserts a new component for the given entity. * @@ -233,7 +306,7 @@ namespace nexo::ecs { * * @return The number of active components */ - [[nodiscard]] constexpr size_t size() const + [[nodiscard]] constexpr size_t size() const override { return m_size; } @@ -279,7 +352,7 @@ namespace nexo::ecs { * * @return Const span of entity IDs */ - [[nodiscard]] std::span entities() const + [[nodiscard]] std::span entities() const override { return {m_dense.data(), m_size}; } @@ -484,4 +557,298 @@ namespace nexo::ecs { } } }; + + /** + * @class TypeErasedComponentArray + * @brief A type-erased component array that can store components of any size. + * + * This class allows you to create component arrays at runtime without knowing + * the component type at compile time. You only need to specify the size of + * each component. + */ + class alignas(64) TypeErasedComponentArray final : public IComponentArray { + public: + /** + * @brief Constructs a new type-erased component array + * @param componentSize Size of each component in bytes + * @param initialCapacity Initial capacity for the array + */ + explicit TypeErasedComponentArray(const size_t componentSize, const size_t initialCapacity = 1024) + : m_componentSize(componentSize), m_capacity(initialCapacity) + { + if (componentSize == 0) { + throw std::invalid_argument("Component size cannot be zero"); + } + + m_sparse.resize(m_capacity, INVALID_ENTITY); + m_dense.reserve(m_capacity); + m_componentData.reserve(m_capacity * m_componentSize); + } + + /** + * @brief Inserts a new component for the given entity + * @param entity The entity to add the component to + * @param componentData Raw pointer to the component data to copy + */ + void insert(Entity entity, const void* componentData) + { + if (entity >= MAX_ENTITIES) + THROW_EXCEPTION(OutOfRange, entity); + + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "Entity {} already has component", entity); + return; + } + + const size_t newIndex = m_size; + m_sparse[entity] = newIndex; + m_dense.push_back(entity); + + // Resize component data vector if needed + size_t requiredSize = (m_size + 1) * m_componentSize; + if (m_componentData.size() < requiredSize) { + m_componentData.resize(requiredSize); + } + + // Copy component data + std::memcpy(m_componentData.data() + newIndex * m_componentSize, + componentData, m_componentSize); + + ++m_size; + } + + /** + * @brief Removes the component for the given entity + * @param entity The entity to remove the component from + */ + void remove(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t indexToRemove = m_sparse[entity]; + + // Handle grouped components + if (indexToRemove < m_groupSize) { + size_t groupLastIndex = m_groupSize - 1; + if (indexToRemove != groupLastIndex) { + swapComponents(indexToRemove, groupLastIndex); + std::swap(m_dense[indexToRemove], m_dense[groupLastIndex]); + m_sparse[m_dense[indexToRemove]] = indexToRemove; + m_sparse[m_dense[groupLastIndex]] = groupLastIndex; + } + --m_groupSize; + indexToRemove = groupLastIndex; + } + + // Standard removal + const size_t lastIndex = m_size - 1; + if (indexToRemove != lastIndex) { + swapComponents(indexToRemove, lastIndex); + std::swap(m_dense[indexToRemove], m_dense[lastIndex]); + m_sparse[m_dense[indexToRemove]] = indexToRemove; + } + + m_sparse[entity] = INVALID_ENTITY; + m_dense.pop_back(); + --m_size; + + shrinkIfNeeded(); + } + + [[nodiscard]] bool hasComponent(Entity entity) const override + { + return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); + } + + void entityDestroyed(Entity entity) override + { + if (hasComponent(entity)) + remove(entity); + } + + void duplicateComponent(Entity sourceEntity, Entity destEntity) override + { + if (!hasComponent(sourceEntity)) + THROW_EXCEPTION(ComponentNotFound, sourceEntity); + + const void* sourceData = getRawComponent(sourceEntity); + insert(destEntity, sourceData); + } + + [[nodiscard]] size_t getComponentSize() const override + { + return m_componentSize; + } + + [[nodiscard]] size_t size() const override + { + return m_size; + } + + [[nodiscard]] void* getRawComponent(Entity entity) override + { + if (!hasComponent(entity)) + return nullptr; + return m_componentData.data() + m_sparse[entity] * m_componentSize; + } + + [[nodiscard]] const void* getRawComponent(Entity entity) const override + { + if (!hasComponent(entity)) + return nullptr; + return m_componentData.data() + m_sparse[entity] * m_componentSize; + } + + [[nodiscard]] void* getRawData() override + { + return m_componentData.data(); + } + + [[nodiscard]] const void* getRawData() const override + { + return m_componentData.data(); + } + + [[nodiscard]] std::span entities() const override + { + return {m_dense.data(), m_size}; + } + + /** + * @brief Gets the entity at the given index in the dense array + * @param index The index to look up + * @return The entity at that index + */ + [[nodiscard]] Entity getEntityAtIndex(size_t index) const + { + if (index >= m_size) + THROW_EXCEPTION(OutOfRange, index); + return m_dense[index]; + } + + /** + * @brief Adds an entity to the group region + * @param entity The entity to add to the group + */ + void addToGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + if (index < m_groupSize) + return; + + if (index != m_groupSize) { + swapComponents(index, m_groupSize); + std::swap(m_dense[index], m_dense[m_groupSize]); + m_sparse[m_dense[index]] = index; + m_sparse[m_dense[m_groupSize]] = m_groupSize; + } + ++m_groupSize; + } + + /** + * @brief Removes an entity from the group region + * @param entity The entity to remove from the group + */ + void removeFromGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + if (index >= m_groupSize) + return; + + --m_groupSize; + if (index != m_groupSize) { + swapComponents(index, m_groupSize); + std::swap(m_dense[index], m_dense[m_groupSize]); + m_sparse[m_dense[index]] = index; + m_sparse[m_dense[m_groupSize]] = m_groupSize; + } + } + + /** + * @brief Gets the number of entities in the group region + * @return Number of grouped entities + */ + [[nodiscard]] constexpr size_t groupSize() const + { + return m_groupSize; + } + + /** + * @brief Get the estimated memory usage of this component array + * @return Size in bytes of memory used by this component array + */ + [[nodiscard]] size_t memoryUsage() const + { + return m_componentData.capacity() + + sizeof(size_t) * m_sparse.capacity() + + sizeof(Entity) * m_dense.capacity(); + } + + private: + // Component data storage + std::vector m_componentData; + // Sparse mapping: maps entity ID to index in the dense arrays + std::vector m_sparse; + // Dense storage for entity IDs + std::vector m_dense; + // Size of each component in bytes + size_t m_componentSize; + // Initial capacity + size_t m_capacity; + // Current number of active components + size_t m_size = 0; + // Group size for component grouping + size_t m_groupSize = 0; + + void ensureSparseCapacity(Entity entity) + { + if (entity >= m_sparse.size()) { + size_t newSize = m_sparse.size(); + if (newSize == 0) + newSize = m_capacity; + while (entity >= newSize) + newSize *= 2; + m_sparse.resize(newSize, INVALID_ENTITY); + } + } + + void swapComponents(size_t index1, size_t index2) + { + if (index1 == index2) return; + + std::byte* data1 = m_componentData.data() + index1 * m_componentSize; + std::byte* data2 = m_componentData.data() + index2 * m_componentSize; + + // Use a temporary buffer for swapping + std::vector temp(m_componentSize); + std::memcpy(temp.data(), data1, m_componentSize); + std::memcpy(data1, data2, m_componentSize); + std::memcpy(temp.data(), data2, m_componentSize); + } + + void shrinkIfNeeded() + { + if (m_size < m_componentData.capacity() / 4 && m_componentData.capacity() > m_capacity * m_componentSize * 2) { + size_t newCapacity = std::max(m_size * 2, static_cast(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(); + + m_componentData.reserve(newCapacity); + m_dense.reserve(newCapacity / m_componentSize); + } + } + }; + } diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 2ca96ab0a..c3ebec2be 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -227,6 +227,7 @@ namespace nexo::ecs { { 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; @@ -235,6 +236,16 @@ namespace nexo::ecs { 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 * @@ -380,6 +391,22 @@ namespace nexo::ecs { } } + /** + * @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 * @@ -391,12 +418,7 @@ namespace nexo::ecs { [[nodiscard]] std::shared_ptr> getComponentArray() { const ComponentType typeID = getComponentTypeID(); - - const auto& componentArray = m_componentArrays[typeID]; - if (componentArray == nullptr) - THROW_EXCEPTION(ComponentNotRegistered); - - return std::static_pointer_cast>(componentArray); + return std::static_pointer_cast>(getComponentArray(typeID)); } /** @@ -410,12 +432,7 @@ namespace nexo::ecs { [[nodiscard]] std::shared_ptr> getComponentArray() const { const ComponentType typeID = getComponentTypeID(); - - const auto& componentArray = m_componentArrays[typeID]; - if (componentArray == nullptr) - THROW_EXCEPTION(ComponentNotRegistered); - - return std::static_pointer_cast>(componentArray); + return std::static_pointer_cast>(getComponentArray(typeID)); } /** @@ -435,6 +452,22 @@ namespace nexo::ecs { 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 * diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index f8c5c3864..fbe65019b 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -176,6 +176,12 @@ namespace nexo::ecs { } } + ComponentType registerComponent(const size_t componentSize, const size_t initialCapacity = 1024) + { + auto typeID = m_componentManager->registerComponent(componentSize, initialCapacity); + return typeID; + } + /** * @brief Registers a new singleton component * @@ -296,29 +302,9 @@ namespace nexo::ecs { return m_componentManager->tryGetComponent(entity); } - void* tryGetComponentById(ComponentType componentType, Entity entity) + void *tryGetComponentById(const ComponentType componentType, const Entity entity)const { - auto itType = m_typeIDtoTypeIndex.find(componentType); - if (itType == m_typeIDtoTypeIndex.end()) { - return nullptr; - } - - const std::type_index& typeIndex = itType->second; - auto itGetter = m_getComponentPointers.find(typeIndex); - if (itGetter == m_getComponentPointers.end()) { - return nullptr; - } - - std::any componentAny = itGetter->second(entity); - if (!componentAny.has_value()) { - return nullptr; - } - - if (componentAny.type() != typeid(void*)) { - return nullptr; - } - - return std::any_cast(componentAny); + return m_componentManager->tryGetComponent(entity, componentType); } const std::unordered_map& getTypeIdToTypeIndex() const { diff --git a/engine/src/ecs/Definitions.hpp b/engine/src/ecs/Definitions.hpp index 99e8a1f63..0d1fe0a7b 100644 --- a/engine/src/ecs/Definitions.hpp +++ b/engine/src/ecs/Definitions.hpp @@ -58,6 +58,12 @@ namespace nexo::ecs { */ inline ComponentType globalComponentCounter = 0; + 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 * @@ -73,10 +79,8 @@ namespace nexo::ecs { { // This static variable is instantiated once per type T, // but it will be assigned a unique value from the shared global counter. - static const ComponentType id = []() { - assert(globalComponentCounter < MAX_COMPONENT_TYPE && "Maximum number of component types exceeded"); - return globalComponentCounter++; - }(); + // TODO: Warning! This is not thread-safe. (and it's a crappy implementation, but it works for now) + static const ComponentType id = generateComponentTypeID(); return id; } From 1515109b72265afb7c59c959236d77da8d927f2f Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 04:55:15 +0900 Subject: [PATCH 431/450] feat(scripting-component): component registration to scripting Implements the registration of user-defined components from managed code to the native ECS. This enables dynamic component definition and registration within the C# scripting environment. It iterates through the assemblies, finds components, and registers them, making them available for use in the ECS. Includes associated native API changes to support component registration and lookup. --- engine/src/scripting/managed/CMakeLists.txt | 1 + .../managed/Components/ComponentBase.cs | 61 +++++++++++++++++++ engine/src/scripting/managed/NativeInterop.cs | 30 +++++++++ .../scripting/managed/Scripts/CubeSystem.cs | 6 ++ engine/src/scripting/native/ManagedApi.hpp | 1 + engine/src/scripting/native/NativeApi.cpp | 16 +++-- engine/src/scripting/native/NativeApi.hpp | 7 ++- engine/src/scripting/native/Scripting.cpp | 5 ++ engine/src/systems/ScriptingSystem.cpp | 7 ++- 9 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 engine/src/scripting/managed/Components/ComponentBase.cs diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 2e36a801d..6092a1725 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -34,6 +34,7 @@ set(SOURCES Components/Scene.cs Components/Transform.cs Components/Uuid.cs + Components/ComponentBase.cs Systems/SystemBase.cs Systems/WorldState.cs Scripts/CubeSystem.cs diff --git a/engine/src/scripting/managed/Components/ComponentBase.cs b/engine/src/scripting/managed/Components/ComponentBase.cs new file mode 100644 index 000000000..3a44183cb --- /dev/null +++ b/engine/src/scripting/managed/Components/ComponentBase.cs @@ -0,0 +1,61 @@ +//// ComponentBase.cs ///////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 22/06/2025 +// Description: Interface for the user's components in NEXO's ECS framework +// +/////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using Nexo; + +namespace Nexo.Components; + +public interface IComponentBase +{ + private static readonly List AllComponents = []; + + [UnmanagedCallersOnly] + public static Int32 InitializeComponents() + { + try + { + // Find all types that derive from IComponentBase + var componentTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => typeof(IComponentBase).IsAssignableFrom(type) && + !type.IsAbstract && + !type.IsInterface && + type != typeof(IComponentBase)) // Exclude the interface itself + .ToList(); + + // Register each component type + foreach (var componentType in componentTypes) + { + if (NativeInterop.RegisterComponent(componentType) < 0) + { + Logger.Log(LogLevel.Error, $"Failed to register component {componentType.Name}"); + return 1; + } + + return 0; + } + + return 0; + } + catch (Exception ex) + { + Logger.Log(LogLevel.Fatal, $"Error initializing components: {ex.Message}"); + return 1; + } + } + +} \ No newline at end of file diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index bac5b9fab..3bb6a46d5 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -70,6 +70,9 @@ private struct NativeApiCallbacks [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate IntPtr NxGetComponentDelegate(UInt32 typeId, UInt32 entityId); + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate Int64 NxRegisterComponentDelegate(String name, UInt64 size); + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); @@ -87,6 +90,7 @@ private struct NativeApiCallbacks public CreateCubeDelegate CreateCube; public GetTransformDelegate GetTransform; public NxGetComponentDelegate NxGetComponent; + public NxRegisterComponentDelegate NxRegisterComponent; public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; public NxAddComponentDelegate NxAddComponent; public NxHasComponentDelegate NxHasComponent; @@ -287,6 +291,32 @@ public static bool HasComponent(UInt32 entityId) } + public static Int64 RegisterComponent(Type componentType) + { + var name = componentType.Name; + try + { + var size = (UInt32)Marshal.SizeOf(componentType); + + Logger.Log(LogLevel.Info, $"Registering component {name}"); + + var typeId = s_callbacks.NxRegisterComponent.Invoke(name, size); + if (typeId < 0) + { + Console.WriteLine($"Failed to register component {name}, returned: {typeId}"); + return typeId; + } + _typeToNativeIdMap[componentType] = (UInt32)typeId; + Logger.Log(LogLevel.Info, $"Registered component {name} with type ID {typeId}"); + return typeId; + } + catch (Exception ex) + { + Console.WriteLine($"Error calling NxRegisterComponent for {name}: {ex.Message}"); + return -1; + } + } + private static UInt32 _cubeId = 0; /// diff --git a/engine/src/scripting/managed/Scripts/CubeSystem.cs b/engine/src/scripting/managed/Scripts/CubeSystem.cs index 4614aedff..d7d8173f3 100644 --- a/engine/src/scripting/managed/Scripts/CubeSystem.cs +++ b/engine/src/scripting/managed/Scripts/CubeSystem.cs @@ -18,6 +18,12 @@ namespace Nexo.Scripts; +public struct TestComponent : IComponentBase +{ + public String Name; + public Int32 Value; +} + public class CubeSystem : SystemBase { private struct CubeAnimationState diff --git a/engine/src/scripting/native/ManagedApi.hpp b/engine/src/scripting/native/ManagedApi.hpp index ec4fe88fd..6ae8d14fb 100644 --- a/engine/src/scripting/native/ManagedApi.hpp +++ b/engine/src/scripting/native/ManagedApi.hpp @@ -105,6 +105,7 @@ namespace nexo::scripting { struct SystemBaseApi { ManagedApiFn InitializeSystems; + ManagedApiFn InitializeComponents; ManagedApiFn ShutdownSystems; diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 5251b3b0d..329e44b3a 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -89,9 +89,9 @@ namespace nexo::scripting { return &opt.value().get(); } - void* GetComponent(UInt32 componentTypeId, ecs::Entity entity) + void* GetComponent(const UInt32 componentTypeId, const ecs::Entity entity) { - auto& coordinator = *getApp().m_coordinator; + auto& coordinator = *Application::m_coordinator; const auto opt = coordinator.tryGetComponentById(componentTypeId, entity); return opt; } @@ -145,9 +145,16 @@ namespace nexo::scripting { return signature.test(bitIndex); } + Int64 NxRegisterComponent(const char* name, const UInt64 size) + { + (void)name; // TODO: unused for now + auto& coordinator = *Application::m_coordinator; + return coordinator.registerComponent(size); + } + ComponentTypeIds GetComponentTypeIds() { - auto& coordinator = *getApp().m_coordinator; + auto& coordinator = *Application::m_coordinator; return ComponentTypeIds { .Transform = coordinator.getComponentType(), @@ -163,9 +170,6 @@ namespace nexo::scripting { .PerspectiveCameraTarget = coordinator.getComponentType(), }; } - - } - } // namespace nexo::scripting diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index 5b901b00d..2ab0a3acd 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -87,7 +87,8 @@ namespace nexo::scripting { NEXO_RET(ecs::Entity) CreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); NEXO_RET(components::TransformComponent *) GetTransformComponent(ecs::Entity entity); - NEXO_RET(void *) GetComponent(UInt32 typeId, ecs::Entity entity); + NEXO_RET(void *) GetComponent(UInt32 componentTypeId, ecs::Entity entity); + NEXO_RET(Int64) NxRegisterComponent(const char *name, UInt64 size); NEXO_RET(ComponentTypeIds) GetComponentTypeIds(); NEXO_RET(void) AddComponent(UInt32 typeId, ecs::Entity entity); NEXO_RET(bool) HasComponent(UInt32 typeId, ecs::Entity entity); @@ -103,10 +104,10 @@ namespace nexo::scripting { ApiCallback CreateCube{&scripting::CreateCube}; ApiCallback GetTransformComponent{&scripting::GetTransformComponent}; ApiCallback GetComponent{&scripting::GetComponent}; - ApiCallback GetComponentTypeIds{&scripting::GetComponentTypeIds}; ApiCallback AddComponent{&scripting::AddComponent}; ApiCallback HasComponent{&scripting::HasComponent}; - + ApiCallback NxRegisterComponent{&scripting::NxRegisterComponent}; + ApiCallback GetComponentTypeIds{&scripting::GetComponentTypeIds}; }; inline NativeApiCallbacks nativeApiCallbacks; diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 76f72fe17..abc75c470 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -270,6 +270,11 @@ namespace nexo::scripting { "InitializeSystems", UNMANAGEDCALLERSONLY ), + .InitializeComponents = getManagedFptr( + "Nexo.Components.IComponentBase, Nexo", + "InitializeComponents", + UNMANAGEDCALLERSONLY + ), .ShutdownSystems = getManagedFptr( "Nexo.Systems.SystemBase, Nexo", "ShutdownSystems", diff --git a/engine/src/systems/ScriptingSystem.cpp b/engine/src/systems/ScriptingSystem.cpp index 043cac8de..43b5e30d2 100644 --- a/engine/src/systems/ScriptingSystem.cpp +++ b/engine/src/systems/ScriptingSystem.cpp @@ -48,8 +48,13 @@ namespace nexo::system { LOG(NEXO_INFO, "Successfully ran runScriptExample"); updateWorldState(); + if (auto ret = scriptHost.getManagedApi().SystemBase.InitializeComponents(); ret != 0) { + LOG(NEXO_ERROR, "Failed to initialize scripting components, returned: {}", ret); + return ret; + } + LOG(NEXO_INFO, "Scripting components initialized successfully"); if (auto ret = scriptHost.getManagedApi().SystemBase.InitializeSystems(&m_worldState, sizeof(m_worldState)); ret != 0) { - LOG(NEXO_ERROR, "Failed to initialize scripting systems: {}", ret); + LOG(NEXO_ERROR, "Failed to initialize scripting systems, returned: {}", ret); return ret; } LOG(NEXO_INFO, "Scripting systems initialized successfully"); From 771a0be0cf834f4e4ad296d48eed42193ac896ec Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 20:25:36 +0900 Subject: [PATCH 432/450] feat(scripting-component): add TypeErased addComponent --- engine/src/ecs/ComponentArray.hpp | 84 +++++++++++++++++++++++++++++++ engine/src/ecs/Components.hpp | 29 +++++++++++ engine/src/ecs/Coordinator.hpp | 24 +++++++++ 3 files changed, 137 insertions(+) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index f83eeb1e4..16e0347cc 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -90,6 +90,18 @@ namespace nexo::ecs { */ [[nodiscard]] virtual const void* getRawData() const = 0; + /** + * @brief Inserts a raw new component for the given entity. + * + * @param entity The entity to add the component to + * @param componentData Pointer to the raw component data + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + * + * @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; + /** * @brief Gets a span of all entities with this component * @return Span of entity IDs @@ -192,6 +204,39 @@ namespace nexo::ecs { ++m_size; } + /** + * @brief Inserts a raw new component for the given entity. + * + * @param entity The entity to add the component to + * @param componentData Pointer to the raw component data + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + * + * @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 + { + if (entity >= MAX_ENTITIES) + THROW_EXCEPTION(OutOfRange, entity); + + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "Entity {} already has component: {}", entity, typeid(T).name()); + return; + } + + const size_t newIndex = m_size; + 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 + std::memcpy(&m_componentArray[newIndex], componentData, sizeof(T)); + + ++m_size; + } + /** * @brief Removes the component for the given entity. * @@ -619,6 +664,45 @@ namespace nexo::ecs { ++m_size; } + /** + * @brief Inserts a raw new component for the given entity. + * + * @param entity The entity to add the component to + * @param componentData Pointer to the raw component data + * @throws OutOfRange if entity ID exceeds MAX_ENTITIES + * + * @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 + { + if (entity >= MAX_ENTITIES) + THROW_EXCEPTION(OutOfRange, entity); + + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "Entity {} already has component", entity); + return; + } + + const size_t newIndex = m_size; + m_sparse[entity] = newIndex; + m_dense.push_back(entity); + + // Resize component data vector if needed + size_t requiredSize = (m_size + 1) * m_componentSize; + if (m_componentData.size() < requiredSize) { + m_componentData.resize(requiredSize); + } + + // Copy component data + std::memcpy(m_componentData.data() + newIndex * m_componentSize, + componentData, m_componentSize); + + ++m_size; + } + /** * @brief Removes the component for the given entity * @param entity The entity to remove the component from diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index c3ebec2be..9edb48770 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -290,6 +290,35 @@ namespace nexo::ecs { } } + /** + * @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 * diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index fbe65019b..3897a92b9 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -213,6 +213,30 @@ namespace nexo::ecs { 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, updates its signature, and notifies systems. * From b078f0a7dbbc0ea0bb0cceeb0ab5f6f64636f9c7 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 20:26:24 +0900 Subject: [PATCH 433/450] feat(scripting-component): update API with new addComponent --- .../managed/Components/ComponentBase.cs | 8 ++- engine/src/scripting/managed/NativeInterop.cs | 33 ++++-------- engine/src/scripting/native/NativeApi.cpp | 52 ++----------------- engine/src/scripting/native/NativeApi.hpp | 7 +-- 4 files changed, 25 insertions(+), 75 deletions(-) diff --git a/engine/src/scripting/managed/Components/ComponentBase.cs b/engine/src/scripting/managed/Components/ComponentBase.cs index 3a44183cb..228918611 100644 --- a/engine/src/scripting/managed/Components/ComponentBase.cs +++ b/engine/src/scripting/managed/Components/ComponentBase.cs @@ -37,6 +37,12 @@ public static Int32 InitializeComponents() type != typeof(IComponentBase)) // Exclude the interface itself .ToList(); + Logger.Log(LogLevel.Info, $"Found {componentTypes.Count} component types to register."); + foreach (var componentType in componentTypes) + { + Logger.Log(LogLevel.Info, $"Component: {componentType.Name}"); + } + // Register each component type foreach (var componentType in componentTypes) { @@ -45,8 +51,6 @@ public static Int32 InitializeComponents() Logger.Log(LogLevel.Error, $"Failed to register component {componentType.Name}"); return 1; } - - return 0; } return 0; diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 3bb6a46d5..5e78c5c35 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -47,7 +47,7 @@ public static class NativeInterop /// Native API struct that matches the C++ struct /// [StructLayout(LayoutKind.Sequential)] - private struct NativeApiCallbacks + private unsafe struct NativeApiCallbacks { [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate void HelloFromNativeDelegate(); @@ -71,16 +71,16 @@ private struct NativeApiCallbacks public delegate IntPtr NxGetComponentDelegate(UInt32 typeId, UInt32 entityId); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate Int64 NxRegisterComponentDelegate(String name, UInt64 size); - + public delegate void NxAddComponentDelegate(UInt32 typeId, UInt32 entityId, void *componentData); + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); + public delegate bool NxHasComponentDelegate(UInt32 typeId, UInt32 entityId); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate void NxAddComponentDelegate(UInt32 typeId, UInt32 entityId); - + public delegate Int64 NxRegisterComponentDelegate(String name, UInt64 size); + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate bool NxHasComponentDelegate(UInt32 typeId, UInt32 entityId); + public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); // Function pointers public HelloFromNativeDelegate HelloFromNative; @@ -90,10 +90,10 @@ private struct NativeApiCallbacks public CreateCubeDelegate CreateCube; public GetTransformDelegate GetTransform; public NxGetComponentDelegate NxGetComponent; - public NxRegisterComponentDelegate NxRegisterComponent; - public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; public NxAddComponentDelegate NxAddComponent; public NxHasComponentDelegate NxHasComponent; + public NxRegisterComponentDelegate NxRegisterComponent; + public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; } private static NativeApiCallbacks s_callbacks; @@ -259,14 +259,14 @@ public static unsafe ref T GetComponent(UInt32 entityId) where T : unmanaged return ref Unsafe.AsRef((void*)ptr); } - public static void AddComponent(UInt32 entityId) + public static unsafe void AddComponent(UInt32 entityId, ref T componentData) where T : unmanaged { if (!_typeToNativeIdMap.TryGetValue(typeof(T), out var typeId)) throw new InvalidOperationException($"Unsupported component type: {typeof(T)}"); try { - s_callbacks.NxAddComponent.Invoke(typeId, entityId); + s_callbacks.NxAddComponent.Invoke(typeId, entityId, Unsafe.AsPointer(ref componentData)); } catch (Exception ex) { @@ -349,17 +349,6 @@ public static void DemonstrateNativeCalls() _cubeId = cubeId; Console.WriteLine($"Created cube with ID: {cubeId}"); - // AddComponent test - AddComponent(cubeId); - - try { - ref CameraComponent cam = ref GetComponent(cubeId); - Console.WriteLine($"CameraComponent added: active={cam.active}, fov={cam.fov}"); - } - catch (Exception e) { - Console.WriteLine($"Failed to get CameraComponent: {e.Message}"); - } - // HasComponent test if (HasComponent(cubeId)) Console.WriteLine("Entity has a camera!"); diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 329e44b3a..1604b76d7 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -25,30 +25,6 @@ namespace nexo::scripting { // Static message to return to C# static const char* nativeMessage = "Hello from C++ native code!"; - ecs::ComponentType getComponentTypeFromNativeId(UInt32 typeId) - { - using enum scripting::NativeComponents; - auto& coordinator = *getApp().m_coordinator; - - switch (static_cast(typeId)) - { - case Transform: - return coordinator.getComponentType(); - case AmbientLight: - return coordinator.getComponentType(); - case DirectionalLight: - return coordinator.getComponentType(); - case PointLight: - return coordinator.getComponentType(); - case SpotLight: - return coordinator.getComponentType(); - default: - LOG(NEXO_ERROR, "Unknown NativeComponent ID: {}", typeId); - return -1; - } - } - - // Implementation of the native functions extern "C" { @@ -96,36 +72,16 @@ namespace nexo::scripting { return opt; } - void AddComponent(UInt32 typeId, ecs::Entity entity) + void AddComponent(const UInt32 typeId, const ecs::Entity entity, const void *componentData) { - auto& coordinator = *getApp().m_coordinator; - - const auto& map = coordinator.getTypeIdToTypeIndex(); - auto it = map.find(typeId); - - if (it == map.end()) { - LOG(NEXO_ERROR, "AddComponent: Unknown typeId {}", typeId); - return; - } - - const std::type_index& typeIndex = it->second; - const auto& addFn = coordinator.getAddComponentFunctions().find(typeIndex); - if (addFn == coordinator.getAddComponentFunctions().end()) { - LOG(NEXO_ERROR, "AddComponent: No add function registered for component {}", typeIndex.name()); - return; - } + auto& coordinator = *Application::m_coordinator; - try { - std::any defaultConstructed = coordinator.restoreComponent(std::any{}, typeIndex); - addFn->second(entity, defaultConstructed); - } catch (const std::bad_any_cast& e) { - LOG(NEXO_ERROR, "AddComponent: bad_any_cast for component {}: {}", typeIndex.name(), e.what()); - } + coordinator.addComponent(entity, typeId, componentData); } bool HasComponent(UInt32 typeId, ecs::Entity entity) { - auto& coordinator = *getApp().m_coordinator; + auto& coordinator = *Application::m_coordinator; const auto& typeMap = coordinator.getTypeIdToTypeIndex(); auto it = typeMap.find(typeId); diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index 2ab0a3acd..b865e8bba 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -88,10 +88,11 @@ namespace nexo::scripting { NEXO_RET(ecs::Entity) CreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); NEXO_RET(components::TransformComponent *) GetTransformComponent(ecs::Entity entity); NEXO_RET(void *) GetComponent(UInt32 componentTypeId, ecs::Entity entity); + NEXO_RET(void) AddComponent(UInt32 typeId, ecs::Entity entity, const void *componentData); + NEXO_RET(bool) HasComponent(UInt32 typeId, ecs::Entity entity); NEXO_RET(Int64) NxRegisterComponent(const char *name, UInt64 size); NEXO_RET(ComponentTypeIds) GetComponentTypeIds(); - NEXO_RET(void) AddComponent(UInt32 typeId, ecs::Entity entity); - NEXO_RET(bool) HasComponent(UInt32 typeId, ecs::Entity entity); + } @@ -104,7 +105,7 @@ namespace nexo::scripting { ApiCallback CreateCube{&scripting::CreateCube}; ApiCallback GetTransformComponent{&scripting::GetTransformComponent}; ApiCallback GetComponent{&scripting::GetComponent}; - ApiCallback AddComponent{&scripting::AddComponent}; + ApiCallback AddComponent{&scripting::AddComponent}; ApiCallback HasComponent{&scripting::HasComponent}; ApiCallback NxRegisterComponent{&scripting::NxRegisterComponent}; ApiCallback GetComponentTypeIds{&scripting::GetComponentTypeIds}; From d7c4be500780c5964fb0e662a86224bb2497993b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 20:26:42 +0900 Subject: [PATCH 434/450] feat(scripting-component): make use of Components in CubeSystem example --- .../scripting/managed/Scripts/CubeSystem.cs | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/engine/src/scripting/managed/Scripts/CubeSystem.cs b/engine/src/scripting/managed/Scripts/CubeSystem.cs index d7d8173f3..e182d0743 100644 --- a/engine/src/scripting/managed/Scripts/CubeSystem.cs +++ b/engine/src/scripting/managed/Scripts/CubeSystem.cs @@ -26,13 +26,13 @@ public struct TestComponent : IComponentBase public class CubeSystem : SystemBase { - private struct CubeAnimationState + private struct CubeAnimationState : IComponentBase { public Single Angle; public Single BreathingPhase; } - private readonly Dictionary _cubeStates = []; + private readonly List _cubes = []; protected override void OnInitialize(WorldState worldState) { @@ -42,17 +42,8 @@ protected override void OnInitialize(WorldState worldState) private void MoveCube(UInt32 cubeId, Single deltaTime) { ref Transform transform = ref NativeInterop.GetComponent(cubeId); - - // Get or create animation state for this cube - if (!_cubeStates.TryGetValue(cubeId, out var state)) - { - state = new CubeAnimationState - { - Angle = 0.0f, - BreathingPhase = 0.0f - }; - } - + ref CubeAnimationState state = ref NativeInterop.GetComponent(cubeId); + // Rotating cube effect float rotationSpeed = 1.0f; transform.quat = Quaternion.CreateFromAxisAngle(Vector3.UnitY, deltaTime * rotationSpeed) * transform.quat; @@ -86,19 +77,18 @@ private void MoveCube(UInt32 cubeId, Single deltaTime) } transform.size.Z = startScale + ((MathF.Sin(state.BreathingPhase) * 0.5f + 0.5f) * (endScale - startScale)); - - // Update the stored state - _cubeStates[cubeId] = state; } private void SpawnCube(Vector3 position, Vector3 size, Vector3 rotation, Vector4 color) { var cubeId = NativeInterop.CreateCube(position, size, rotation, color); - _cubeStates[cubeId] = new CubeAnimationState + var state = new CubeAnimationState { - Angle = Random.Shared.NextSingle() * MathF.PI * 2.0f, + Angle = Random.Shared.NextSingle() * MathF.PI * 2.0f, BreathingPhase = Random.Shared.NextSingle() * MathF.PI * 2.0f }; + NativeInterop.AddComponent(cubeId, ref state); + _cubes.Add(cubeId); } protected override void OnUpdate(WorldState worldState) @@ -118,9 +108,10 @@ protected override void OnUpdate(WorldState worldState) 1.0f ); SpawnCube(position, size, rotation, color); + Logger.Log(LogLevel.Info, $"Spawned a new cube at {position} with size {size} and color {color}"); } - foreach (var cubeId in _cubeStates.Keys) + foreach (var cubeId in _cubes) { MoveCube(cubeId, deltaTime); } @@ -128,13 +119,13 @@ protected override void OnUpdate(WorldState worldState) protected override void OnShutdown(WorldState worldState) { - _cubeStates.Clear(); + _cubes.Clear(); Logger.Log(LogLevel.Info, $"Shutting down {Name} system"); } // Helper method to clean up destroyed cubes public void RemoveCubeState(uint cubeId) { - _cubeStates.Remove(cubeId); + _cubes.Remove(cubeId); } } \ No newline at end of file From 0a488390a6356a30cf57fea1af89be8c7c7764a4 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 21:09:37 +0900 Subject: [PATCH 435/450] feat(scripting-component): invert Entity and TypeID for API for cohesion --- engine/src/scripting/managed/NativeInterop.cs | 12 ++++++------ engine/src/scripting/native/NativeApi.cpp | 6 +++--- engine/src/scripting/native/NativeApi.hpp | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 5e78c5c35..82d3ca34f 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -68,13 +68,13 @@ private unsafe struct NativeApiCallbacks public delegate ref Transform GetTransformDelegate(UInt32 entityId); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate IntPtr NxGetComponentDelegate(UInt32 typeId, UInt32 entityId); + public delegate IntPtr NxGetComponentDelegate(UInt32 entityId, UInt32 typeId); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate void NxAddComponentDelegate(UInt32 typeId, UInt32 entityId, void *componentData); + public delegate void NxAddComponentDelegate(UInt32 entityId, UInt32 typeId, void *componentData); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate bool NxHasComponentDelegate(UInt32 typeId, UInt32 entityId); + public delegate bool NxHasComponentDelegate(UInt32 entityId, UInt32 typeId); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate Int64 NxRegisterComponentDelegate(String name, UInt64 size); @@ -252,7 +252,7 @@ public static unsafe ref T GetComponent(UInt32 entityId) where T : unmanaged if (!_typeToNativeIdMap.TryGetValue(typeof(T), out var typeId)) throw new InvalidOperationException($"Unsupported component type: {typeof(T)}"); - IntPtr ptr = s_callbacks.NxGetComponent(typeId, entityId); + IntPtr ptr = s_callbacks.NxGetComponent(entityId, typeId); if (ptr == IntPtr.Zero) throw new InvalidOperationException($"Component {typeof(T)} not found on entity {entityId}"); @@ -266,7 +266,7 @@ public static unsafe void AddComponent(UInt32 entityId, ref T componentData) try { - s_callbacks.NxAddComponent.Invoke(typeId, entityId, Unsafe.AsPointer(ref componentData)); + s_callbacks.NxAddComponent.Invoke(entityId, typeId, Unsafe.AsPointer(ref componentData)); } catch (Exception ex) { @@ -281,7 +281,7 @@ public static bool HasComponent(UInt32 entityId) try { - return s_callbacks.NxHasComponent.Invoke(typeId, entityId); + return s_callbacks.NxHasComponent.Invoke(entityId, typeId); } catch (Exception ex) { diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 1604b76d7..cd7382138 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -65,21 +65,21 @@ namespace nexo::scripting { return &opt.value().get(); } - void* GetComponent(const UInt32 componentTypeId, const ecs::Entity entity) + void* GetComponent(const ecs::Entity entity, const UInt32 componentTypeId) { auto& coordinator = *Application::m_coordinator; const auto opt = coordinator.tryGetComponentById(componentTypeId, entity); return opt; } - void AddComponent(const UInt32 typeId, const ecs::Entity entity, const void *componentData) + void AddComponent(const ecs::Entity entity, const UInt32 typeId, const void *componentData) { auto& coordinator = *Application::m_coordinator; coordinator.addComponent(entity, typeId, componentData); } - bool HasComponent(UInt32 typeId, ecs::Entity entity) + bool HasComponent(ecs::Entity entity, UInt32 typeId) { auto& coordinator = *Application::m_coordinator; diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index b865e8bba..611e65494 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -87,9 +87,9 @@ namespace nexo::scripting { NEXO_RET(ecs::Entity) CreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); NEXO_RET(components::TransformComponent *) GetTransformComponent(ecs::Entity entity); - NEXO_RET(void *) GetComponent(UInt32 componentTypeId, ecs::Entity entity); - NEXO_RET(void) AddComponent(UInt32 typeId, ecs::Entity entity, const void *componentData); - NEXO_RET(bool) HasComponent(UInt32 typeId, ecs::Entity entity); + NEXO_RET(void *) GetComponent(ecs::Entity entity, UInt32 componentTypeId); + NEXO_RET(void) AddComponent(ecs::Entity entity, UInt32 typeId, const void *componentData); + NEXO_RET(bool) HasComponent(ecs::Entity entity, UInt32 typeId); NEXO_RET(Int64) NxRegisterComponent(const char *name, UInt64 size); NEXO_RET(ComponentTypeIds) GetComponentTypeIds(); @@ -104,9 +104,9 @@ namespace nexo::scripting { ApiCallback CreateCube{&scripting::CreateCube}; ApiCallback GetTransformComponent{&scripting::GetTransformComponent}; - ApiCallback GetComponent{&scripting::GetComponent}; - ApiCallback AddComponent{&scripting::AddComponent}; - ApiCallback HasComponent{&scripting::HasComponent}; + ApiCallback GetComponent{&scripting::GetComponent}; + ApiCallback AddComponent{&scripting::AddComponent}; + ApiCallback HasComponent{&scripting::HasComponent}; ApiCallback NxRegisterComponent{&scripting::NxRegisterComponent}; ApiCallback GetComponentTypeIds{&scripting::GetComponentTypeIds}; }; From 47bbc3caa94428e276b9b3332ac9451247c3a1d6 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 21:13:23 +0900 Subject: [PATCH 436/450] feat(scripting-component): add missing cstring header for std::memcpy --- engine/src/ecs/ComponentArray.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 16e0347cc..1682dcefb 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace nexo::ecs { /** From dabbbd983cdbd6765492f349e25b744ceb7aec26 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 21:16:56 +0900 Subject: [PATCH 437/450] feat(scripting-component): remove useless log for cubes --- engine/src/scripting/managed/Scripts/CubeSystem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/src/scripting/managed/Scripts/CubeSystem.cs b/engine/src/scripting/managed/Scripts/CubeSystem.cs index e182d0743..20dd90c01 100644 --- a/engine/src/scripting/managed/Scripts/CubeSystem.cs +++ b/engine/src/scripting/managed/Scripts/CubeSystem.cs @@ -108,7 +108,6 @@ protected override void OnUpdate(WorldState worldState) 1.0f ); SpawnCube(position, size, rotation, color); - Logger.Log(LogLevel.Info, $"Spawned a new cube at {position} with size {size} and color {color}"); } foreach (var cubeId in _cubes) From f9e25990ecfacbaa532ac59f652da62194176521 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 21:34:45 +0900 Subject: [PATCH 438/450] chore(scripting-component): cleanup useless code in scripts --- .../scripting/managed/Scripts/CubeSystem.cs | 11 --------- .../scripting/managed/Systems/SystemBase.cs | 10 -------- .../scripting/managed/Systems/WorldState.cs | 24 ------------------- 3 files changed, 45 deletions(-) diff --git a/engine/src/scripting/managed/Scripts/CubeSystem.cs b/engine/src/scripting/managed/Scripts/CubeSystem.cs index 20dd90c01..01c68016f 100644 --- a/engine/src/scripting/managed/Scripts/CubeSystem.cs +++ b/engine/src/scripting/managed/Scripts/CubeSystem.cs @@ -18,12 +18,6 @@ namespace Nexo.Scripts; -public struct TestComponent : IComponentBase -{ - public String Name; - public Int32 Value; -} - public class CubeSystem : SystemBase { private struct CubeAnimationState : IComponentBase @@ -122,9 +116,4 @@ protected override void OnShutdown(WorldState worldState) Logger.Log(LogLevel.Info, $"Shutting down {Name} system"); } - // Helper method to clean up destroyed cubes - public void RemoveCubeState(uint cubeId) - { - _cubes.Remove(cubeId); - } } \ No newline at end of file diff --git a/engine/src/scripting/managed/Systems/SystemBase.cs b/engine/src/scripting/managed/Systems/SystemBase.cs index 07822ce60..0177fa4d0 100644 --- a/engine/src/scripting/managed/Systems/SystemBase.cs +++ b/engine/src/scripting/managed/Systems/SystemBase.cs @@ -124,16 +124,6 @@ public static unsafe Int32 ShutdownSystems(WorldState.NativeWorldState *nativeWo return 0; } - public static T? GetSystem() where T : SystemBase - { - return AllSystems.OfType().FirstOrDefault(); - } - - public static IEnumerable GetAllSystems() - { - return AllSystems.AsReadOnly(); - } - protected virtual void OnInitialize(WorldState worldState) { } diff --git a/engine/src/scripting/managed/Systems/WorldState.cs b/engine/src/scripting/managed/Systems/WorldState.cs index 8dd7cddd9..cc18ab32d 100644 --- a/engine/src/scripting/managed/Systems/WorldState.cs +++ b/engine/src/scripting/managed/Systems/WorldState.cs @@ -16,30 +16,6 @@ namespace Nexo.Systems; -// public class WorldState -// { -// [StructLayout(LayoutKind.Sequential)] -// public struct NativeWorldState -// { -// [StructLayout(LayoutKind.Sequential)] -// public struct WorldTime { -// public Double DeltaTime; // Time since last update -// public Double TotalTime; // Total time since the start of the world -// } -// -// [StructLayout(LayoutKind.Sequential)] -// public struct WorldStats -// { -// public UInt64 frameCount; // Number of frames rendered -// } -// -// public WorldTime Time; -// public WorldStats Stats; -// } -// -// public NativeWorldState State; -// } - public unsafe class WorldState { [StructLayout(LayoutKind.Sequential)] From 14ade0db2aa6154fa968c245a68f0ba19c0bbd0b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 21:35:07 +0900 Subject: [PATCH 439/450] feat(scripting-component): move function definitions to cpp ComponentArray --- engine/CMakeLists.txt | 1 + engine/src/ecs/ComponentArray.cpp | 278 ++++++++++++++++++++++++++++++ engine/src/ecs/ComponentArray.hpp | 260 +++------------------------- 3 files changed, 301 insertions(+), 238 deletions(-) create mode 100644 engine/src/ecs/ComponentArray.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 4b14518d6..54ea96057 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -44,6 +44,7 @@ set(COMMON_SOURCES engine/src/core/scene/SceneManager.cpp engine/src/ecs/Entity.cpp engine/src/ecs/Components.cpp + engine/src/ecs/ComponentArray.cpp engine/src/ecs/Coordinator.cpp engine/src/ecs/System.cpp engine/src/systems/CameraSystem.cpp diff --git a/engine/src/ecs/ComponentArray.cpp b/engine/src/ecs/ComponentArray.cpp new file mode 100644 index 000000000..37c065cf3 --- /dev/null +++ b/engine/src/ecs/ComponentArray.cpp @@ -0,0 +1,278 @@ +//// ComponentArray.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: Guillaume HEIN +// Date: 24/06/2025 +// Description: Source file for the component array class +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ComponentArray.hpp" + +namespace nexo::ecs { + + 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"); + } + + m_sparse.resize(m_capacity, INVALID_ENTITY); + m_dense.reserve(m_capacity); + m_componentData.reserve(m_capacity * m_componentSize); + } + + void TypeErasedComponentArray::insert(Entity entity, const void* componentData) + { + if (entity >= MAX_ENTITIES) + THROW_EXCEPTION(OutOfRange, entity); + + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "Entity {} already has component", entity); + return; + } + + const size_t newIndex = m_size; + m_sparse[entity] = newIndex; + m_dense.push_back(entity); + + // Resize component data vector if needed + size_t requiredSize = (m_size + 1) * m_componentSize; + if (m_componentData.size() < requiredSize) { + m_componentData.resize(requiredSize); + } + + // Copy component data + std::memcpy(m_componentData.data() + newIndex * m_componentSize, + componentData, m_componentSize); + + ++m_size; + } + + void TypeErasedComponentArray::insertRaw(Entity entity, const void* componentData) + { + if (entity >= MAX_ENTITIES) + THROW_EXCEPTION(OutOfRange, entity); + + ensureSparseCapacity(entity); + + if (hasComponent(entity)) { + LOG(NEXO_WARN, "Entity {} already has component", entity); + return; + } + + const size_t newIndex = m_size; + m_sparse[entity] = newIndex; + m_dense.push_back(entity); + + // Resize component data vector if needed + size_t requiredSize = (m_size + 1) * m_componentSize; + if (m_componentData.size() < requiredSize) { + m_componentData.resize(requiredSize); + } + + // Copy component data + std::memcpy(m_componentData.data() + newIndex * m_componentSize, + componentData, m_componentSize); + + ++m_size; + } + + void TypeErasedComponentArray::remove(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t indexToRemove = m_sparse[entity]; + + // Handle grouped components + if (indexToRemove < m_groupSize) { + size_t groupLastIndex = m_groupSize - 1; + if (indexToRemove != groupLastIndex) { + swapComponents(indexToRemove, groupLastIndex); + std::swap(m_dense[indexToRemove], m_dense[groupLastIndex]); + m_sparse[m_dense[indexToRemove]] = indexToRemove; + m_sparse[m_dense[groupLastIndex]] = groupLastIndex; + } + --m_groupSize; + indexToRemove = groupLastIndex; + } + + // Standard removal + const size_t lastIndex = m_size - 1; + if (indexToRemove != lastIndex) { + swapComponents(indexToRemove, lastIndex); + std::swap(m_dense[indexToRemove], m_dense[lastIndex]); + m_sparse[m_dense[indexToRemove]] = indexToRemove; + } + + m_sparse[entity] = INVALID_ENTITY; + m_dense.pop_back(); + --m_size; + + shrinkIfNeeded(); + } + + bool TypeErasedComponentArray::hasComponent(Entity entity) const + { + return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); + } + + void TypeErasedComponentArray::entityDestroyed(Entity entity) + { + if (hasComponent(entity)) + remove(entity); + } + + void TypeErasedComponentArray::duplicateComponent(Entity sourceEntity, Entity destEntity) + { + if (!hasComponent(sourceEntity)) + THROW_EXCEPTION(ComponentNotFound, sourceEntity); + + const void* sourceData = getRawComponent(sourceEntity); + insert(destEntity, sourceData); + } + + size_t TypeErasedComponentArray::getComponentSize() const + { + return m_componentSize; + } + + size_t TypeErasedComponentArray::size() const + { + return m_size; + } + + void* TypeErasedComponentArray::getRawComponent(Entity entity) + { + if (!hasComponent(entity)) + return nullptr; + return m_componentData.data() + m_sparse[entity] * m_componentSize; + } + + const void* TypeErasedComponentArray::getRawComponent(Entity entity) const + { + if (!hasComponent(entity)) + return nullptr; + return m_componentData.data() + m_sparse[entity] * m_componentSize; + } + + void* TypeErasedComponentArray::getRawData() + { + return m_componentData.data(); + } + + const void* TypeErasedComponentArray::getRawData() const + { + return m_componentData.data(); + } + + std::span TypeErasedComponentArray::entities() const + { + return {m_dense.data(), m_size}; + } + + Entity TypeErasedComponentArray::getEntityAtIndex(size_t index) const + { + if (index >= m_size) + THROW_EXCEPTION(OutOfRange, index); + return m_dense[index]; + } + + void TypeErasedComponentArray::addToGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + if (index < m_groupSize) + return; + + if (index != m_groupSize) { + swapComponents(index, m_groupSize); + std::swap(m_dense[index], m_dense[m_groupSize]); + m_sparse[m_dense[index]] = index; + m_sparse[m_dense[m_groupSize]] = m_groupSize; + } + ++m_groupSize; + } + + void TypeErasedComponentArray::removeFromGroup(Entity entity) + { + if (!hasComponent(entity)) + THROW_EXCEPTION(ComponentNotFound, entity); + + size_t index = m_sparse[entity]; + if (index >= m_groupSize) + return; + + --m_groupSize; + if (index != m_groupSize) { + swapComponents(index, m_groupSize); + std::swap(m_dense[index], m_dense[m_groupSize]); + m_sparse[m_dense[index]] = index; + m_sparse[m_dense[m_groupSize]] = m_groupSize; + } + } + + constexpr size_t TypeErasedComponentArray::groupSize() const + { + return m_groupSize; + } + + size_t TypeErasedComponentArray::memoryUsage() const + { + return m_componentData.capacity() + + sizeof(size_t) * m_sparse.capacity() + + sizeof(Entity) * m_dense.capacity(); + } + + void TypeErasedComponentArray::ensureSparseCapacity(Entity entity) + { + if (entity >= m_sparse.size()) { + size_t newSize = m_sparse.size(); + if (newSize == 0) + newSize = m_capacity; + while (entity >= newSize) + newSize *= 2; + m_sparse.resize(newSize, INVALID_ENTITY); + } + } + + void TypeErasedComponentArray::swapComponents(size_t index1, size_t index2) + { + if (index1 == index2) return; + + std::byte* data1 = m_componentData.data() + index1 * m_componentSize; + std::byte* data2 = m_componentData.data() + index2 * m_componentSize; + + // Use a temporary buffer for swapping + std::vector temp(m_componentSize); + std::memcpy(temp.data(), data1, m_componentSize); + std::memcpy(data1, data2, m_componentSize); + std::memcpy(temp.data(), data2, m_componentSize); + } + + void TypeErasedComponentArray::shrinkIfNeeded() + { + if (m_size < m_componentData.capacity() / 4 && m_componentData.capacity() > m_capacity * m_componentSize * 2) { + size_t newCapacity = std::max(m_size * 2, static_cast(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(); + + m_componentData.reserve(newCapacity); + m_dense.reserve(newCapacity / m_componentSize); + } + } + +} // namespace nexo::ecs \ No newline at end of file diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index 1682dcefb..9dbde9bc1 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -619,51 +619,14 @@ namespace nexo::ecs { * @param componentSize Size of each component in bytes * @param initialCapacity Initial capacity for the array */ - explicit TypeErasedComponentArray(const size_t componentSize, const size_t initialCapacity = 1024) - : m_componentSize(componentSize), m_capacity(initialCapacity) - { - if (componentSize == 0) { - throw std::invalid_argument("Component size cannot be zero"); - } - - m_sparse.resize(m_capacity, INVALID_ENTITY); - m_dense.reserve(m_capacity); - m_componentData.reserve(m_capacity * m_componentSize); - } + explicit TypeErasedComponentArray(size_t componentSize, size_t initialCapacity = 1024); /** * @brief Inserts a new component for the given entity * @param entity The entity to add the component to * @param componentData Raw pointer to the component data to copy */ - void insert(Entity entity, const void* componentData) - { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); - - ensureSparseCapacity(entity); - - if (hasComponent(entity)) { - LOG(NEXO_WARN, "Entity {} already has component", entity); - return; - } - - const size_t newIndex = m_size; - m_sparse[entity] = newIndex; - m_dense.push_back(entity); - - // Resize component data vector if needed - size_t requiredSize = (m_size + 1) * m_componentSize; - if (m_componentData.size() < requiredSize) { - m_componentData.resize(requiredSize); - } - - // Copy component data - std::memcpy(m_componentData.data() + newIndex * m_componentSize, - componentData, m_componentSize); - - ++m_size; - } + void insert(Entity entity, const void* componentData); /** * @brief Inserts a raw new component for the given entity. @@ -675,208 +638,64 @@ 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 - { - if (entity >= MAX_ENTITIES) - THROW_EXCEPTION(OutOfRange, entity); - - ensureSparseCapacity(entity); - - if (hasComponent(entity)) { - LOG(NEXO_WARN, "Entity {} already has component", entity); - return; - } - - const size_t newIndex = m_size; - m_sparse[entity] = newIndex; - m_dense.push_back(entity); - - // Resize component data vector if needed - size_t requiredSize = (m_size + 1) * m_componentSize; - if (m_componentData.size() < requiredSize) { - m_componentData.resize(requiredSize); - } - - // Copy component data - std::memcpy(m_componentData.data() + newIndex * m_componentSize, - componentData, m_componentSize); - - ++m_size; - } + void insertRaw(Entity entity, const void* componentData) override; /** * @brief Removes the component for the given entity * @param entity The entity to remove the component from */ - void remove(Entity entity) - { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - - size_t indexToRemove = m_sparse[entity]; + void remove(Entity entity); - // Handle grouped components - if (indexToRemove < m_groupSize) { - size_t groupLastIndex = m_groupSize - 1; - if (indexToRemove != groupLastIndex) { - swapComponents(indexToRemove, groupLastIndex); - std::swap(m_dense[indexToRemove], m_dense[groupLastIndex]); - m_sparse[m_dense[indexToRemove]] = indexToRemove; - m_sparse[m_dense[groupLastIndex]] = groupLastIndex; - } - --m_groupSize; - indexToRemove = groupLastIndex; - } + [[nodiscard]] bool hasComponent(Entity entity) const override; - // Standard removal - const size_t lastIndex = m_size - 1; - if (indexToRemove != lastIndex) { - swapComponents(indexToRemove, lastIndex); - std::swap(m_dense[indexToRemove], m_dense[lastIndex]); - m_sparse[m_dense[indexToRemove]] = indexToRemove; - } + void entityDestroyed(Entity entity) override; - m_sparse[entity] = INVALID_ENTITY; - m_dense.pop_back(); - --m_size; + void duplicateComponent(Entity sourceEntity, Entity destEntity) override; - shrinkIfNeeded(); - } - - [[nodiscard]] bool hasComponent(Entity entity) const override - { - return (entity < m_sparse.size() && m_sparse[entity] != INVALID_ENTITY); - } - - void entityDestroyed(Entity entity) override - { - if (hasComponent(entity)) - remove(entity); - } - - void duplicateComponent(Entity sourceEntity, Entity destEntity) override - { - if (!hasComponent(sourceEntity)) - THROW_EXCEPTION(ComponentNotFound, sourceEntity); - - const void* sourceData = getRawComponent(sourceEntity); - insert(destEntity, sourceData); - } - - [[nodiscard]] size_t getComponentSize() const override - { - return m_componentSize; - } + [[nodiscard]] size_t getComponentSize() const override; - [[nodiscard]] size_t size() const override - { - return m_size; - } + [[nodiscard]] size_t size() const override; - [[nodiscard]] void* getRawComponent(Entity entity) override - { - if (!hasComponent(entity)) - return nullptr; - return m_componentData.data() + m_sparse[entity] * m_componentSize; - } + [[nodiscard]] void* getRawComponent(Entity entity) override; - [[nodiscard]] const void* getRawComponent(Entity entity) const override - { - if (!hasComponent(entity)) - return nullptr; - return m_componentData.data() + m_sparse[entity] * m_componentSize; - } + [[nodiscard]] const void* getRawComponent(Entity entity) const override; - [[nodiscard]] void* getRawData() override - { - return m_componentData.data(); - } + [[nodiscard]] void* getRawData() override; - [[nodiscard]] const void* getRawData() const override - { - return m_componentData.data(); - } + [[nodiscard]] const void* getRawData() const override; - [[nodiscard]] std::span entities() const override - { - return {m_dense.data(), m_size}; - } + [[nodiscard]] std::span entities() const override; /** * @brief Gets the entity at the given index in the dense array * @param index The index to look up * @return The entity at that index */ - [[nodiscard]] Entity getEntityAtIndex(size_t index) const - { - if (index >= m_size) - THROW_EXCEPTION(OutOfRange, index); - return m_dense[index]; - } + [[nodiscard]] Entity getEntityAtIndex(size_t index) const; /** * @brief Adds an entity to the group region * @param entity The entity to add to the group */ - void addToGroup(Entity entity) - { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - - size_t index = m_sparse[entity]; - if (index < m_groupSize) - return; - - if (index != m_groupSize) { - swapComponents(index, m_groupSize); - std::swap(m_dense[index], m_dense[m_groupSize]); - m_sparse[m_dense[index]] = index; - m_sparse[m_dense[m_groupSize]] = m_groupSize; - } - ++m_groupSize; - } + void addToGroup(Entity entity); /** * @brief Removes an entity from the group region * @param entity The entity to remove from the group */ - void removeFromGroup(Entity entity) - { - if (!hasComponent(entity)) - THROW_EXCEPTION(ComponentNotFound, entity); - - size_t index = m_sparse[entity]; - if (index >= m_groupSize) - return; - - --m_groupSize; - if (index != m_groupSize) { - swapComponents(index, m_groupSize); - std::swap(m_dense[index], m_dense[m_groupSize]); - m_sparse[m_dense[index]] = index; - m_sparse[m_dense[m_groupSize]] = m_groupSize; - } - } + void removeFromGroup(Entity entity); /** * @brief Gets the number of entities in the group region * @return Number of grouped entities */ - [[nodiscard]] constexpr size_t groupSize() const - { - return m_groupSize; - } + [[nodiscard]] constexpr size_t groupSize() const; /** * @brief Get the estimated memory usage of this component array * @return Size in bytes of memory used by this component array */ - [[nodiscard]] size_t memoryUsage() const - { - return m_componentData.capacity() - + sizeof(size_t) * m_sparse.capacity() - + sizeof(Entity) * m_dense.capacity(); - } + [[nodiscard]] size_t memoryUsage() const; private: // Component data storage @@ -894,46 +713,11 @@ namespace nexo::ecs { // Group size for component grouping size_t m_groupSize = 0; - void ensureSparseCapacity(Entity entity) - { - if (entity >= m_sparse.size()) { - size_t newSize = m_sparse.size(); - if (newSize == 0) - newSize = m_capacity; - while (entity >= newSize) - newSize *= 2; - m_sparse.resize(newSize, INVALID_ENTITY); - } - } + void ensureSparseCapacity(Entity entity); - void swapComponents(size_t index1, size_t index2) - { - if (index1 == index2) return; + void swapComponents(size_t index1, size_t index2); - std::byte* data1 = m_componentData.data() + index1 * m_componentSize; - std::byte* data2 = m_componentData.data() + index2 * m_componentSize; - - // Use a temporary buffer for swapping - std::vector temp(m_componentSize); - std::memcpy(temp.data(), data1, m_componentSize); - std::memcpy(data1, data2, m_componentSize); - std::memcpy(temp.data(), data2, m_componentSize); - } - - void shrinkIfNeeded() - { - if (m_size < m_componentData.capacity() / 4 && m_componentData.capacity() > m_capacity * m_componentSize * 2) { - size_t newCapacity = std::max(m_size * 2, static_cast(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(); - - m_componentData.reserve(newCapacity); - m_dense.reserve(newCapacity / m_componentSize); - } - } + void shrinkIfNeeded(); }; } From c41bfbbf586770b712c5f2a57e0fe5be37e7b276 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 24 Jun 2025 22:07:12 +0900 Subject: [PATCH 440/450] chore(scripting-component): rename API to respect Nx convention --- engine/src/ecs/Coordinator.hpp | 17 +++++++- engine/src/scripting/managed/Logger.cs | 2 +- engine/src/scripting/managed/NativeInterop.cs | 22 +++++----- engine/src/scripting/native/NativeApi.cpp | 42 ++++++------------- engine/src/scripting/native/NativeApi.hpp | 36 ++++++++-------- 5 files changed, 58 insertions(+), 61 deletions(-) diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 3897a92b9..fde661ecd 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -511,10 +511,23 @@ namespace nexo::ecs { * @return false Otherwise. */ template - bool entityHasComponent(const Entity entity) const + [[nodiscard]] bool entityHasComponent(const Entity entity) const { - const Signature signature = m_entityManager->getSignature(entity); 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); } diff --git a/engine/src/scripting/managed/Logger.cs b/engine/src/scripting/managed/Logger.cs index c47d1c408..2a233f757 100644 --- a/engine/src/scripting/managed/Logger.cs +++ b/engine/src/scripting/managed/Logger.cs @@ -34,6 +34,6 @@ public static class Logger /// /// Specifies the log level (e.g., Fatal, Error, Warn, Info, Debug, Dev, User). /// The message to be logged. - public static void Log(LogLevel level, String message) => NativeInterop.NxLog((UInt32)level, message); + public static void Log(LogLevel level, String message) => NativeInterop.Log((UInt32)level, message); } diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 82d3ca34f..4a12720fd 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -83,12 +83,12 @@ private unsafe struct NativeApiCallbacks public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); // Function pointers - public HelloFromNativeDelegate HelloFromNative; - public AddNumbersDelegate AddNumbers; - public GetNativeMessageDelegate GetNativeMessage; + public HelloFromNativeDelegate NxHelloFromNative; + public AddNumbersDelegate NxAddNumbers; + public GetNativeMessageDelegate NxGetNativeMessage; public NxLogDelegate NxLog; - public CreateCubeDelegate CreateCube; - public GetTransformDelegate GetTransform; + public CreateCubeDelegate NxCreateCube; + public GetTransformDelegate NxGetTransform; public NxGetComponentDelegate NxGetComponent; public NxAddComponentDelegate NxAddComponent; public NxHasComponentDelegate NxHasComponent; @@ -162,7 +162,7 @@ public static void HelloFromNative() { try { - s_callbacks.HelloFromNative.Invoke(); + s_callbacks.NxHelloFromNative.Invoke(); } catch (Exception ex) { @@ -177,7 +177,7 @@ public static Int32 AddNumbers(Int32 a, Int32 b) { try { - return s_callbacks.AddNumbers.Invoke(a, b); + return s_callbacks.NxAddNumbers.Invoke(a, b); } catch (Exception ex) { @@ -193,7 +193,7 @@ public static String GetNativeMessage() { try { - IntPtr messagePtr = s_callbacks.GetNativeMessage.Invoke(); + IntPtr messagePtr = s_callbacks.NxGetNativeMessage.Invoke(); return messagePtr != IntPtr.Zero ? Marshal.PtrToStringAnsi(messagePtr) ?? string.Empty : string.Empty; } catch (Exception ex) @@ -208,7 +208,7 @@ public static String GetNativeMessage() /// /// The level of the log message /// The message to log - public static void NxLog(UInt32 level, String message) + public static void Log(UInt32 level, String message) { try { @@ -225,7 +225,7 @@ public static UInt32 CreateCube(in Vector3 position, in Vector3 size, in Vector3 { try { - return s_callbacks.CreateCube.Invoke(position, size, rotation, color); + return s_callbacks.NxCreateCube.Invoke(position, size, rotation, color); } catch (Exception ex) { @@ -238,7 +238,7 @@ public static ref Transform GetTransform(UInt32 entityId) { try { - return ref s_callbacks.GetTransform.Invoke(entityId); + return ref s_callbacks.NxGetTransform.Invoke(entityId); } catch (Exception ex) { diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index cd7382138..6197d6e21 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -28,16 +28,16 @@ namespace nexo::scripting { // Implementation of the native functions extern "C" { - void HelloFromNative() { + void NxHelloFromNative() { std::cout << "Hello World from C++ native code!" << std::endl; } - Int32 AddNumbers(const Int32 a, const Int32 b) { + Int32 NxAddNumbers(const Int32 a, const Int32 b) { std::cout << "Native AddNumbers called with " << a << " and " << b << std::endl; return a + b; } - const char* GetNativeMessage() { + const char* NxGetNativeMessage() { std::cout << "GetNativeMessage called from C#" << std::endl; return nativeMessage; } @@ -46,7 +46,7 @@ namespace nexo::scripting { LOG(static_cast(level), "[Scripting] {}", message); } - ecs::Entity CreateCube(const Vector3 pos, const Vector3 size, const Vector3 rotation, const Vector4 color) + ecs::Entity NxCreateCube(const Vector3 pos, const Vector3 size, const Vector3 rotation, const Vector4 color) { auto &app = getApp(); const ecs::Entity basicCube = EntityFactory3D::createCube(std::move(pos), std::move(size), std::move(rotation), std::move(color)); @@ -54,10 +54,9 @@ namespace nexo::scripting { return basicCube; } - components::TransformComponent *GetTransformComponent(ecs::Entity entity) + components::TransformComponent *NxGetTransformComponent(ecs::Entity entity) { - const auto &app = getApp(); - const auto opt = app.m_coordinator->tryGetComponent(entity); + const auto opt = Application::m_coordinator->tryGetComponent(entity); if (!opt.has_value()) { LOG(NEXO_WARN, "GetTransformComponent: Entity {} does not have a TransformComponent", entity); return nullptr; @@ -65,40 +64,25 @@ namespace nexo::scripting { return &opt.value().get(); } - void* GetComponent(const ecs::Entity entity, const UInt32 componentTypeId) + void* NxGetComponent(const ecs::Entity entity, const UInt32 componentTypeId) { auto& coordinator = *Application::m_coordinator; const auto opt = coordinator.tryGetComponentById(componentTypeId, entity); return opt; } - void AddComponent(const ecs::Entity entity, const UInt32 typeId, const void *componentData) + void NxAddComponent(const ecs::Entity entity, const UInt32 typeId, const void *componentData) { - auto& coordinator = *Application::m_coordinator; + const auto& coordinator = *Application::m_coordinator; coordinator.addComponent(entity, typeId, componentData); } - bool HasComponent(ecs::Entity entity, UInt32 typeId) + bool NxHasComponent(const ecs::Entity entity, const UInt32 typeId) { - auto& coordinator = *Application::m_coordinator; - - const auto& typeMap = coordinator.getTypeIdToTypeIndex(); - auto it = typeMap.find(typeId); - if (it == typeMap.end()) - { - LOG(NEXO_WARN, "HasComponent: Unknown typeId {}", typeId); - return false; - } - - ecs::ComponentType bitIndex = typeId; - - ecs::Signature signature = coordinator.getSignature(entity); - - // LOG(NEXO_WARN, "HasComponent: entity = {}, typeId = {}, bitIndex = {}, signature = {}", - // entity, typeId, bitIndex, signature.to_string()); + const auto& coordinator = *Application::m_coordinator; - return signature.test(bitIndex); + return coordinator.entityHasComponent(entity, typeId); } Int64 NxRegisterComponent(const char* name, const UInt64 size) @@ -108,7 +92,7 @@ namespace nexo::scripting { return coordinator.registerComponent(size); } - ComponentTypeIds GetComponentTypeIds() + ComponentTypeIds NxGetComponentTypeIds() { auto& coordinator = *Application::m_coordinator; diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index 611e65494..04f4ace9b 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -80,35 +80,35 @@ namespace nexo::scripting { UInt32 PerspectiveCameraTarget; }; - NEXO_RET(void) HelloFromNative(void); - NEXO_RET(Int32) AddNumbers(Int32 a, Int32 b); - NEXO_RET(const char*) GetNativeMessage(void); + NEXO_RET(void) NxHelloFromNative(void); + NEXO_RET(Int32) NxAddNumbers(Int32 a, Int32 b); + NEXO_RET(const char*) NxGetNativeMessage(void); NEXO_RET(void) NxLog(UInt32 level, const char *message); - NEXO_RET(ecs::Entity) CreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); - NEXO_RET(components::TransformComponent *) GetTransformComponent(ecs::Entity entity); - NEXO_RET(void *) GetComponent(ecs::Entity entity, UInt32 componentTypeId); - NEXO_RET(void) AddComponent(ecs::Entity entity, UInt32 typeId, const void *componentData); - NEXO_RET(bool) HasComponent(ecs::Entity entity, UInt32 typeId); + NEXO_RET(ecs::Entity) NxCreateCube(Vector3 pos, Vector3 size, Vector3 rotation, Vector4 color); + NEXO_RET(components::TransformComponent *) NxGetTransformComponent(ecs::Entity entity); + NEXO_RET(void *) NxGetComponent(ecs::Entity entity, UInt32 componentTypeId); + NEXO_RET(void) NxAddComponent(ecs::Entity entity, UInt32 typeId, const void *componentData); + NEXO_RET(bool) NxHasComponent(ecs::Entity entity, UInt32 typeId); NEXO_RET(Int64) NxRegisterComponent(const char *name, UInt64 size); - NEXO_RET(ComponentTypeIds) GetComponentTypeIds(); + NEXO_RET(ComponentTypeIds) NxGetComponentTypeIds(); } struct NativeApiCallbacks { - ApiCallback HelloFromNative{&scripting::HelloFromNative}; - ApiCallback AddNumbers{&scripting::AddNumbers}; - ApiCallback GetNativeMessage{&scripting::GetNativeMessage}; + ApiCallback NxHelloFromNative{&scripting::NxHelloFromNative}; + ApiCallback NxAddNumbers{&scripting::NxAddNumbers}; + ApiCallback NxGetNativeMessage{&scripting::NxGetNativeMessage}; ApiCallback NxLog{&scripting::NxLog}; - ApiCallback CreateCube{&scripting::CreateCube}; - ApiCallback GetTransformComponent{&scripting::GetTransformComponent}; - ApiCallback GetComponent{&scripting::GetComponent}; - ApiCallback AddComponent{&scripting::AddComponent}; - ApiCallback HasComponent{&scripting::HasComponent}; + ApiCallback NxCreateCube{&scripting::NxCreateCube}; + ApiCallback NxGetTransformComponent{&scripting::NxGetTransformComponent}; + ApiCallback NxGetComponent{&scripting::NxGetComponent}; + ApiCallback NxAddComponent{&scripting::NxAddComponent}; + ApiCallback NxHasComponent{&scripting::NxHasComponent}; ApiCallback NxRegisterComponent{&scripting::NxRegisterComponent}; - ApiCallback GetComponentTypeIds{&scripting::GetComponentTypeIds}; + ApiCallback NxGetComponentTypeIds{&scripting::NxGetComponentTypeIds}; }; inline NativeApiCallbacks nativeApiCallbacks; From cdaf9c00ad08ace9f1f56e1d8b8cf36c51062ad0 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 25 Jun 2025 08:30:50 +0900 Subject: [PATCH 441/450] feat(scripting-ui): add component description and field types Introduces structures for describing components and their fields, including field types and metadata. This allows for dynamic UI generation and component manipulation based on data provided from C#. Removes the NativeComponents enum in C# as it is replaced by the more generic ComponentDescription. --- .../ComponentDescription.hpp | 31 ++++++++++++ engine/src/ecs/TypeErasedComponent/Field.hpp | 33 +++++++++++++ .../src/ecs/TypeErasedComponent/FieldType.hpp | 47 +++++++++++++++++++ .../src/scripting/managed/NativeComponents.cs | 36 -------------- engine/src/scripting/native/ui/Field.hpp | 33 +++++++++++++ engine/src/scripting/native/ui/FieldType.hpp | 47 +++++++++++++++++++ 6 files changed, 191 insertions(+), 36 deletions(-) create mode 100644 engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp create mode 100644 engine/src/ecs/TypeErasedComponent/Field.hpp create mode 100644 engine/src/ecs/TypeErasedComponent/FieldType.hpp delete mode 100644 engine/src/scripting/managed/NativeComponents.cs create mode 100644 engine/src/scripting/native/ui/Field.hpp create mode 100644 engine/src/scripting/native/ui/FieldType.hpp diff --git a/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp b/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp new file mode 100644 index 000000000..9d5ce75cb --- /dev/null +++ b/engine/src/ecs/TypeErasedComponent/ComponentDescription.hpp @@ -0,0 +1,31 @@ +//// ComponentDescription.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: Guillaume HEIN +// Date: 25/06/2025 +// Description: Header file for the field struct used in UI scripting, +// which represents a field in the UI with its properties +// this struct is passed by the C# code to the native code +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +#include "Field.hpp" + +namespace nexo::ecs { + + struct ComponentDescription { + 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 new file mode 100644 index 000000000..a0d7ae9ec --- /dev/null +++ b/engine/src/ecs/TypeErasedComponent/Field.hpp @@ -0,0 +1,33 @@ +//// Field.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: Guillaume HEIN +// Date: 25/06/2025 +// Description: Header file for the field struct used in UI scripting, +// which represents a field in the UI with its properties +// this struct is passed by the C# code to the native code +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +#include "FieldType.hpp" + +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 + }; + +} // namespace nexo::ecs diff --git a/engine/src/ecs/TypeErasedComponent/FieldType.hpp b/engine/src/ecs/TypeErasedComponent/FieldType.hpp new file mode 100644 index 000000000..9a3c0d95a --- /dev/null +++ b/engine/src/ecs/TypeErasedComponent/FieldType.hpp @@ -0,0 +1,47 @@ +//// FieldType.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: Guillaume HEIN +// Date: 25/06/2025 +// Description: Header file for the field type enumeration +// used in UI components +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +namespace nexo::ecs { + + enum class FieldType : uint64_t { + // Special type, if blank, the field is not used + Blank, + Section, // Used to create a section with title in the UI + + // Primitive types + Bool, + Int8, + Int16, + Int32, + Int64, + UInt8, + UInt16, + UInt32, + UInt64, + Float, + Double, + + // Widgets + Vector3, + Vector4, + + _Count // Count of the number of field types, used for validation + }; + +} // namespace nexo::scripting diff --git a/engine/src/scripting/managed/NativeComponents.cs b/engine/src/scripting/managed/NativeComponents.cs deleted file mode 100644 index 2e2c9eac9..000000000 --- a/engine/src/scripting/managed/NativeComponents.cs +++ /dev/null @@ -1,36 +0,0 @@ -//// NativeComponents.cs ////////////////////////////////////////////////////// -// -// 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: Thomas PARENTEAU -// Date: 20/05/2025 -// Description: Enum for NativeComponents, used in GetComponent -// -/////////////////////////////////////////////////////////////////////////////// - -namespace Nexo; - -/// -/// Enum representing the components available in C++ accessible via interop. -/// Must exactly match the NativeComponents enum defined in ManagedTypedef.hpp on the C++ side. -/// - -public enum NativeComponents : uint -{ - Transform = 0, - AmbientLight = 1, - DirectionalLight = 2, - PointLight = 3, - SpotLight = 4, - RenderComponent = 5, - SceneTag = 6, - CameraComponent = 7, - UuidComponent = 8, - PerspectiveCameraController = 9, - PerspectiveCameraTarget = 10, - -} \ No newline at end of file diff --git a/engine/src/scripting/native/ui/Field.hpp b/engine/src/scripting/native/ui/Field.hpp new file mode 100644 index 000000000..72ba24c10 --- /dev/null +++ b/engine/src/scripting/native/ui/Field.hpp @@ -0,0 +1,33 @@ +//// Field.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: Guillaume HEIN +// Date: 25/06/2025 +// Description: Header file for the field struct used in UI scripting, +// which represents a field in the UI with its properties +// this struct is passed by the C# code to the native code +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +#include "FieldType.hpp" +#include "scripting/native/ManagedTypedef.hpp" + +namespace nexo::scripting { + + struct Field { + IntPtr name; // Pointer to the name of the field + FieldType type; // Type of the field (e.g., Int, Float, String, etc.) + UInt64 size; // Size of the field in bytes + UInt64 offset; // Offset of the field in the component + }; + +} // namespace nexo::ecs diff --git a/engine/src/scripting/native/ui/FieldType.hpp b/engine/src/scripting/native/ui/FieldType.hpp new file mode 100644 index 000000000..3e678dfcb --- /dev/null +++ b/engine/src/scripting/native/ui/FieldType.hpp @@ -0,0 +1,47 @@ +//// FieldType.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: Guillaume HEIN +// Date: 25/06/2025 +// Description: Header file for the field type enumeration +// used in UI components +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +namespace nexo::scripting { + + enum class FieldType : uint64_t { + // Special type, if blank, the field is not used + Blank, + Section, // Used to create a section with title in the UI + + // Primitive types + Bool, + Int8, + Int16, + Int32, + Int64, + UInt8, + UInt16, + UInt32, + UInt64, + Float, + Double, + + // Widgets + Vector3, + Vector4, + + _Count // Count of the number of field types, used for validation + }; + +} // namespace nexo::scripting From 9dc9262657cd308b7bd38f7cac68bfb446966bbf Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 25 Jun 2025 08:32:30 +0900 Subject: [PATCH 442/450] refactor(scripting-ui): component type retrieval in ECS Changes component type retrieval to use ComponentType directly. This removes the reliance on type indices during retrieval, simplifying the process and potentially improving performance. Also provides function to retrieve type indices if needed. --- engine/src/Application.hpp | 7 +------ engine/src/ecs/Coordinator.cpp | 22 ++++++++++++++++++---- engine/src/ecs/Coordinator.hpp | 19 +++++++++++++++++-- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index 9bb22334a..8ff6ff73d 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -215,16 +215,11 @@ namespace nexo { return m_coordinator->getComponent(entity); } - static std::vector getAllEntityComponentTypes(const ecs::Entity entity) + static std::vector getAllEntityComponentTypes(const ecs::Entity entity) { return m_coordinator->getAllComponentTypes(entity); } - static std::vector> getAllEntityComponents(const ecs::Entity entity) - { - return m_coordinator->getAllComponents(entity); - } - scene::SceneManager &getSceneManager() { return m_SceneManager; } [[nodiscard]] const std::shared_ptr &getWindow() const { return m_window; } diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index 5eae9746e..a061195e6 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -43,22 +43,36 @@ namespace nexo::ecs { m_systemManager->entityDestroyed(entity, signature); } - std::vector Coordinator::getAllComponentTypes(const Entity entity) const + std::vector Coordinator::getAllComponentTypes(const Entity entity) const { - std::vector types; + std::vector types; Signature signature = m_entityManager->getSignature(entity); // We have a mapping from component type IDs to type_index for (ComponentType type = 0; type < MAX_COMPONENT_TYPE; ++type) { - if (signature.test(type) && m_typeIDtoTypeIndex.contains(type)) { - types.emplace_back(m_typeIDtoTypeIndex.at(type)); + if (signature.test(type)) { + types.emplace_back(type); } } return types; } + std::vector Coordinator::getAllComponentTypeIndices(Entity entity) const + { + const std::vector& types = getAllComponentTypes(entity); + std::vector typeIndices; + typeIndices.reserve(types.size()); + + for (const auto& type : types) + { + typeIndices.push_back(m_typeIDtoTypeIndex.at(type)); + } + + return typeIndices; + } + std::vector> Coordinator::getAllComponents(const Entity entity) { std::vector> components; diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index fde661ecd..b7ff44ac8 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -22,6 +22,7 @@ #include "SingletonComponent.hpp" #include "Entity.hpp" #include "Logger.hpp" +#include "TypeErasedComponent/ComponentDescription.hpp" namespace nexo::ecs { @@ -176,6 +177,11 @@ namespace nexo::ecs { } } + 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) { auto typeID = m_componentManager->registerComponent(componentSize, initialCapacity); @@ -373,7 +379,9 @@ namespace nexo::ecs { * @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 getAllComponentTypes(Entity entity) const; + + std::vector getAllComponentTypeIndices(Entity entity) const; /** * @brief Retrieves all components associated with an entity. @@ -432,6 +440,11 @@ namespace nexo::ecs { return m_componentManager->getComponentType(); } + const std::unordered_map>& getComponentDescriptions() const + { + return m_componentDescriptions; + } + /** * @brief Registers a new query system * @@ -570,5 +583,7 @@ namespace nexo::ecs { std::unordered_map> m_addComponentFunctions; std::unordered_map> m_getComponentFunctions; std::unordered_map> m_getComponentPointers; + + std::unordered_map> m_componentDescriptions; }; -} \ No newline at end of file +} From 22bee833ccdeda7ffeeb33d62ceb8673928f0858 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 25 Jun 2025 08:33:48 +0900 Subject: [PATCH 443/450] refactor(scripting-ui): addtype-erased property support to inspector Adds the ability to display and edit properties of C# components within the inspector window. This allows for dynamic inspection of components defined outside of the core engine. It achieves this by creating a `TypeErasedProperty` class that handles the display of type-erased fields, using component descriptions from the coordinator. Also fixes an issue with undo/redo actions. --- editor/CMakeLists.txt | 1 + .../EntityProperties/TypeErasedProperty.cpp | 111 ++++++++++++++++++ .../EntityProperties/TypeErasedProperty.hpp | 47 ++++++++ .../DocumentWindows/InspectorWindow/Init.cpp | 15 +++ .../InspectorWindow/InspectorWindow.hpp | 13 +- .../DocumentWindows/InspectorWindow/Show.cpp | 2 +- editor/src/Editor.cpp | 5 + editor/src/context/actions/EntityActions.cpp | 4 +- 8 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp create mode 100644 editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.hpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 38a190f12..a614bb773 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -84,6 +84,7 @@ set(SRCS editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp editor/src/DocumentWindows/EntityProperties/CameraController.cpp editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp + editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp editor/src/DocumentWindows/TestWindow/Init.cpp editor/src/DocumentWindows/TestWindow/Parser.cpp editor/src/DocumentWindows/TestWindow/Show.cpp diff --git a/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp b/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp new file mode 100644 index 000000000..d893efd30 --- /dev/null +++ b/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp @@ -0,0 +1,111 @@ +//// TypeErasedProperty.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: Guillaume HEIN +// Date: 25/06/2025 +// Description: Implementation file for the type erased property class +// used to display and edit entity properties +// for C# defined components +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "TypeErasedProperty.hpp" + +#include "ImNexo/Components.hpp" +#include "ImNexo/Elements.hpp" + +namespace nexo::editor { + + void showField(const ecs::Field& field, void *data) + { + switch (field.type) { + case ecs::FieldType::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: + ImGui::InputScalar(field.name.c_str(), ImGuiDataType_S8, data); + break; + case ecs::FieldType::Int16: + ImGui::InputScalar(field.name.c_str(), ImGuiDataType_S16, data); + break; + case ecs::FieldType::Int32: + ImGui::InputScalar(field.name.c_str(), ImGuiDataType_S32, data); + break; + case ecs::FieldType::Int64: + ImGui::InputScalar(field.name.c_str(), ImGuiDataType_S64, data); + break; + case ecs::FieldType::UInt8: + ImGui::InputScalar(field.name.c_str(), ImGuiDataType_U8, data); + case ecs::FieldType::UInt16: + ImGui::InputScalar(field.name.c_str(), ImGuiDataType_U16, data); + case ecs::FieldType::UInt32: + ImGui::InputScalar(field.name.c_str(), ImGuiDataType_U32, data); + case ecs::FieldType::UInt64: + ImGui::InputScalar(field.name.c_str(), ImGuiDataType_U64, data); + break; + case ecs::FieldType::Float: + ImGui::InputFloat(field.name.c_str(), static_cast(data)); + break; + case ecs::FieldType::Double: + ImGui::InputDouble(field.name.c_str(), static_cast(data)); + break; + + // Widgets + case ecs::FieldType::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); + + 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 + default: return; + } + } + + void TypeErasedProperty::show(ecs::Entity entity) + { + const auto& coordinator = Application::m_coordinator; + const auto& componentDescriptions = coordinator->getComponentDescriptions(); + + // Check if the entity has any type erased components + if (componentDescriptions.empty()) { + ImGui::Text("No type erased components available for this entity."); + return; + } + + auto componentData = static_cast(coordinator->tryGetComponentById(m_componentType, entity)); + 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 + auto currentComponentData = componentData + field.offset; + // Show the field in the UI + showField(field, currentComponentData); + } + + ImGui::TreePop(); + } + + + } + + +} diff --git a/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.hpp b/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.hpp new file mode 100644 index 000000000..ff6cd5314 --- /dev/null +++ b/editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.hpp @@ -0,0 +1,47 @@ +//// TypeErasedProperty.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: Guillaume HEIN +// Date: 25/06/2025 +// Description: Header file for the type erased property class +// used to display and edit entity properties +// for C# defined components +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "AEntityProperty.hpp" + +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) + { + } + + /** + * @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; + + private: + const ecs::ComponentType m_componentType; // Type of the component being displayed + const std::shared_ptr m_description; // Description of the component being displayed + }; + +} diff --git a/editor/src/DocumentWindows/InspectorWindow/Init.cpp b/editor/src/DocumentWindows/InspectorWindow/Init.cpp index 87f3d27be..4ac5a91c7 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Init.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Init.cpp @@ -22,6 +22,7 @@ #include "../EntityProperties/CameraProperty.hpp" #include "../EntityProperties/CameraController.hpp" #include "../EntityProperties/CameraTarget.hpp" +#include "DocumentWindows/EntityProperties/TypeErasedProperty.hpp" #include "components/Camera.hpp" namespace nexo::editor { @@ -37,5 +38,19 @@ namespace nexo::editor { registerProperty(); registerProperty(); registerProperty(); + + registerTypeErasedProperties(); + } + + void InspectorWindow::registerTypeErasedProperties() + { + // Register TypeErased components + const auto& coordinator = Application::m_coordinator; + const auto& componentDescriptions = coordinator->getComponentDescriptions(); + + for (const auto& [componentType, description] : componentDescriptions) { + registerProperty(componentType, std::make_shared(*this, componentType, description)); + } } + } diff --git a/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp index f745ed7a5..8c44e8760 100644 --- a/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp +++ b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp @@ -37,6 +37,8 @@ namespace nexo::editor { */ void setup() override; + void registerTypeErasedProperties(); + // No-op method in this class void shutdown() override; @@ -139,7 +141,7 @@ namespace nexo::editor { return nullptr; } private: - std::unordered_map> m_entityProperties; + std::unordered_map> m_entityProperties; std::unordered_map m_subInspectorVisibility; std::unordered_map m_subInspectorData; @@ -181,7 +183,14 @@ namespace nexo::editor { requires std::derived_from void registerProperty() { - m_entityProperties[std::type_index(typeid(Component))] = std::make_shared(*this); + const auto type = Application::m_coordinator->getComponentType(); + m_entityProperties[type] = std::make_shared(*this); } + + void registerProperty(const ecs::ComponentType type, std::shared_ptr property) + { + m_entityProperties[type] = std::move(property); + } + }; }; diff --git a/editor/src/DocumentWindows/InspectorWindow/Show.cpp b/editor/src/DocumentWindows/InspectorWindow/Show.cpp index e95d90566..853d5ca99 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Show.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Show.cpp @@ -58,7 +58,7 @@ namespace nexo::editor { void InspectorWindow::showEntityProperties(const ecs::Entity entity) { - const std::vector componentsType = Application::getAllEntityComponentTypes(entity); + const std::vector& componentsType = Application::getAllEntityComponentTypes(entity); for (auto& type : componentsType) { if (m_entityProperties.contains(type)) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 4ee146b9d..790531b74 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -31,6 +31,9 @@ #include #include +#include "DocumentWindows/EditorScene/EditorScene.hpp" +#include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" + namespace nexo::editor { void Editor::shutdown() const @@ -226,6 +229,8 @@ namespace nexo::editor { 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 } bool Editor::isOpen() const diff --git a/editor/src/context/actions/EntityActions.cpp b/editor/src/context/actions/EntityActions.cpp index 0f9061f59..455ec79a4 100644 --- a/editor/src/context/actions/EntityActions.cpp +++ b/editor/src/context/actions/EntityActions.cpp @@ -29,7 +29,7 @@ namespace nexo::editor { void EntityCreationAction::undo() { const auto &coordinator = Application::m_coordinator; - std::vector componentsTypeIndex = coordinator->getAllComponentTypes(m_entityId); + const std::vector& componentsTypeIndex = coordinator->getAllComponentTypeIndices(m_entityId); for (const auto typeIndex : componentsTypeIndex) { if (!coordinator->supportsMementoPattern(typeIndex)) continue; @@ -41,7 +41,7 @@ namespace nexo::editor { EntityDeletionAction::EntityDeletionAction(const ecs::Entity entityId) : m_entityId(entityId) { const auto &coordinator = Application::m_coordinator; - std::vector componentsTypeIndex = coordinator->getAllComponentTypes(entityId); + std::vector componentsTypeIndex = coordinator->getAllComponentTypeIndices(entityId); for (const auto typeIndex : componentsTypeIndex) { if (!coordinator->supportsMementoPattern(typeIndex)) continue; From 93da859d58791ed69e4d5adb3ec0968832aefbdb Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 25 Jun 2025 08:34:33 +0900 Subject: [PATCH 444/450] refactor(scripting-ui): add UI component reflection api in C++ and C# Introduces a mechanism to reflect component fields for UI generation. This includes defining Field, FieldArray, and FieldType to describe component fields. The changes enable dynamic UI creation based on component structure. Removes unused NativeComponents.cs --- engine/src/scripting/managed/CMakeLists.txt | 4 +- .../managed/Components/ComponentBase.cs | 1 + .../scripting/managed/Components/Ui/Field.cs | 96 ++++++++++++ .../managed/Components/Ui/FieldArray.cs | 144 ++++++++++++++++++ .../managed/Components/Ui/FieldType.cs | 41 +++++ engine/src/scripting/managed/NativeInterop.cs | 16 +- .../scripting/managed/Scripts/CubeSystem.cs | 13 ++ engine/src/scripting/native/NativeApi.cpp | 33 +++- engine/src/scripting/native/NativeApi.hpp | 5 +- 9 files changed, 344 insertions(+), 9 deletions(-) create mode 100644 engine/src/scripting/managed/Components/Ui/Field.cs create mode 100644 engine/src/scripting/managed/Components/Ui/FieldArray.cs create mode 100644 engine/src/scripting/managed/Components/Ui/FieldType.cs diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 6092a1725..2c9cee71f 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -27,7 +27,6 @@ set(SOURCES ObjectFactory.cs NativeInterop.cs Logger.cs - NativeComponents.cs Components/Camera.cs Components/Light.cs Components/Render.cs @@ -35,6 +34,9 @@ set(SOURCES Components/Transform.cs Components/Uuid.cs Components/ComponentBase.cs + Components/Ui/Field.cs + Components/Ui/FieldArray.cs + Components/Ui/FieldType.cs Systems/SystemBase.cs Systems/WorldState.cs Scripts/CubeSystem.cs diff --git a/engine/src/scripting/managed/Components/ComponentBase.cs b/engine/src/scripting/managed/Components/ComponentBase.cs index 228918611..02d6649f0 100644 --- a/engine/src/scripting/managed/Components/ComponentBase.cs +++ b/engine/src/scripting/managed/Components/ComponentBase.cs @@ -16,6 +16,7 @@ using System.Reflection; using System.Runtime.InteropServices; using Nexo; +using Nexo.Components.Ui; namespace Nexo.Components; diff --git a/engine/src/scripting/managed/Components/Ui/Field.cs b/engine/src/scripting/managed/Components/Ui/Field.cs new file mode 100644 index 000000000..f308dc535 --- /dev/null +++ b/engine/src/scripting/managed/Components/Ui/Field.cs @@ -0,0 +1,96 @@ +//// Field.cs ///////////////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 24/06/2025 +// Description: Source file for the Field struct in C#. +// +/////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; + +namespace Nexo.Components.Ui; + +[StructLayout(LayoutKind.Sequential)] +public unsafe struct Field +{ + // Use IntPtr instead of char* for better marshaling + public IntPtr Name; + public FieldType Type; + public UInt64 Size; // For blank fields or additional size information + public UInt64 Offset; + + public static readonly Dictionary TypeMap = new() + { + { typeof(Boolean), FieldType.Bool }, + { typeof(SByte), FieldType.Int8 }, + { typeof(Int16), FieldType.Int16 }, + { typeof(Int32), FieldType.Int32 }, + { typeof(Int64), FieldType.Int64 }, + { typeof(Byte), FieldType.UInt8 }, + { typeof(UInt16), FieldType.UInt16 }, + { typeof(UInt32), FieldType.UInt32 }, + { typeof(UInt64), FieldType.UInt64 }, + { typeof(Single), FieldType.Float }, + { typeof(Double), FieldType.Double }, + { typeof(Vector3), FieldType.Vector3 }, + { typeof(Vector4), FieldType.Vector4 } + }; + + public static Field CreateSectionField(String sectionName) + { + var field = new Field + { + Name = Marshal.StringToHGlobalAnsi(sectionName), + Type = FieldType.Section, + Size = 0 + }; + return field; + } + + public static Field CreateFieldFromFieldInfo(Type declaringType, System.Reflection.FieldInfo fieldInfo) + { + var fieldType = TypeMap.GetValueOrDefault(fieldInfo.FieldType, FieldType.Blank); + var fieldName = fieldInfo.Name; + var offset = Marshal.OffsetOf(fieldInfo.DeclaringType ?? throw new InvalidOperationException(), fieldName); + + var size = Marshal.SizeOf(fieldInfo.FieldType); + Logger.Log(LogLevel.Info, $"Creating field: {fieldName}, Type: {fieldType}, Size: {Marshal.SizeOf(fieldInfo.FieldType)}"); + + return new Field + { + Name = Marshal.StringToHGlobalAnsi(fieldName), + Type = fieldType, + Size = Convert.ToUInt64(size), + Offset = Convert.ToUInt64(offset) + }; + } + + // Property to safely get the name as a string + public String NameString => Name != IntPtr.Zero ? Marshal.PtrToStringAnsi(Name) ?? string.Empty : string.Empty; + + // Clean up allocated memory (call this when done with the struct) + public void Dispose() + { + if (Name != IntPtr.Zero) + { + Marshal.FreeHGlobal(Name); + Name = IntPtr.Zero; + } + } + + // Override ToString for debugging + public override string ToString() + { + return $"Field {{ Name = \"{NameString}\", Type = {Type}, Size = {Size} }}"; + } +} diff --git a/engine/src/scripting/managed/Components/Ui/FieldArray.cs b/engine/src/scripting/managed/Components/Ui/FieldArray.cs new file mode 100644 index 000000000..80c4caef8 --- /dev/null +++ b/engine/src/scripting/managed/Components/Ui/FieldArray.cs @@ -0,0 +1,144 @@ +//// FieldArray.cs //////////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 24/06/2025 +// Description: Source file for the FieldArray struct in C#. +// +/////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Nexo.Components.Ui; + +public unsafe class FieldArray : IDisposable +{ + private Field* _fields; + private readonly int _count; + private bool _disposed; + + public FieldArray(int capacity) + { + _count = capacity; + _fields = (Field*)Marshal.AllocHGlobal(capacity * sizeof(Field)); + + for (int i = 0; i < capacity; i++) + { + _fields[i] = default; + } + } + + public FieldArray(Field[] fields) + { + _count = fields.Length; + _fields = (Field*)Marshal.AllocHGlobal(_count * sizeof(Field)); + + for (int i = 0; i < _count; i++) + { + _fields[i] = fields[i]; + } + } + + public Field* GetPointer() => _fields; + public int Count => _count; + + public Field this[int index] + { + get + { + if (index < 0 || index >= _count) + throw new IndexOutOfRangeException(); + return _fields[index]; + } + set + { + if (index < 0 || index >= _count) + throw new IndexOutOfRangeException(); + _fields[index] = value; + } + } + + public void Dispose() + { + if (!_disposed && _fields != null) + { + for (int i = 0; i < _count; i++) + { + _fields[i].Dispose(); + } + + Marshal.FreeHGlobal((IntPtr)_fields); + _fields = null; + _disposed = true; + } + } + + public static FieldArray CreateFieldArrayFromType(Type type, Boolean flatten = true) + { + var flattenedFields = flatten ? GetFlattenedFields(type) : GetDirectFields(type); + var fieldArray = new FieldArray(flattenedFields.Count); + + for (int i = 0; i < flattenedFields.Count; i++) + { + fieldArray[i] = Field.CreateFieldFromFieldInfo(type, flattenedFields[i].FieldInfo); + } + + return fieldArray; + } + + private static List<(FieldInfo FieldInfo, String Name)> GetDirectFields(Type type) + { + return type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(f => !f.IsStatic && !f.IsLiteral) + .Select(f => (f, f.Name)) + .ToList(); + } + + private static List<(FieldInfo FieldInfo, String Name)> GetFlattenedFields(Type type, String prefix = "") + { + var result = new List<(FieldInfo, String)>(); + + var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(f => !f.IsStatic && !f.IsLiteral); + + foreach (var field in fields) + { + String fieldName = String.IsNullOrEmpty(prefix) ? field.Name : $"{prefix}.{field.Name}"; + + if (Field.TypeMap.ContainsKey(field.FieldType)) + { + // If the field type is a known primitive or struct, add it directly + result.Add((field, fieldName)); + continue; + } + + // If it's a struct (but not a primitive or string), flatten it + if (field.FieldType.IsValueType && + !field.FieldType.IsPrimitive && + !field.FieldType.IsEnum && + field.FieldType != typeof(IntPtr) && + field.FieldType != typeof(UIntPtr) && + !field.FieldType.IsPointer) + { + // Recursively flatten nested struct + result.AddRange(GetFlattenedFields(field.FieldType, fieldName)); + } + else + { + // It's unknown, let Field handle it + result.Add((field, fieldName)); + } + } + + return result; + } +} diff --git a/engine/src/scripting/managed/Components/Ui/FieldType.cs b/engine/src/scripting/managed/Components/Ui/FieldType.cs new file mode 100644 index 000000000..cc6ce9f33 --- /dev/null +++ b/engine/src/scripting/managed/Components/Ui/FieldType.cs @@ -0,0 +1,41 @@ +//// FieldType.cs ///////////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 24/06/2025 +// Description: Source file for the FieldType enum in C#. +// +/////////////////////////////////////////////////////////////////////////////// + +namespace Nexo.Components.Ui; + +public enum FieldType : UInt64 +{ + // Special type, if blank, the field is not used + Blank, + Section, // Used to create a section with title in the UI + + // Primitive types + Bool, + Int8, + Int16, + Int32, + Int64, + UInt8, + UInt16, + UInt32, + UInt64, + Float, + Double, + + // Widgets + Vector3, + Vector4, + + _Count // Count of the number of field types, used for validation +} diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 4a12720fd..0533e134a 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -19,6 +19,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Nexo.Components; +using Nexo.Components.Ui; namespace Nexo { @@ -77,7 +78,7 @@ private unsafe struct NativeApiCallbacks public delegate bool NxHasComponentDelegate(UInt32 entityId, UInt32 typeId); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] - public delegate Int64 NxRegisterComponentDelegate(String name, UInt64 size); + public delegate Int64 NxRegisterComponentDelegate(String name, UInt64 componentSize, Field *fields, UInt64 fieldCount); [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); @@ -291,7 +292,7 @@ public static bool HasComponent(UInt32 entityId) } - public static Int64 RegisterComponent(Type componentType) + public static unsafe Int64 RegisterComponent(Type componentType) { var name = componentType.Name; try @@ -299,8 +300,10 @@ public static Int64 RegisterComponent(Type componentType) var size = (UInt32)Marshal.SizeOf(componentType); Logger.Log(LogLevel.Info, $"Registering component {name}"); + + var fieldArray = FieldArray.CreateFieldArrayFromType(componentType); - var typeId = s_callbacks.NxRegisterComponent.Invoke(name, size); + var typeId = s_callbacks.NxRegisterComponent.Invoke(name, size, fieldArray.GetPointer(), (UInt64)fieldArray.Count); if (typeId < 0) { Console.WriteLine($"Failed to register component {name}, returned: {typeId}"); @@ -308,11 +311,16 @@ public static Int64 RegisterComponent(Type componentType) } _typeToNativeIdMap[componentType] = (UInt32)typeId; Logger.Log(LogLevel.Info, $"Registered component {name} with type ID {typeId}"); + for (int i = 0; i < fieldArray.Count; i++) + { + var field = fieldArray[i]; + Logger.Log(LogLevel.Info, $"Registered field {field.Name} of type {field.Type} for component {componentType.Name}"); + } return typeId; } catch (Exception ex) { - Console.WriteLine($"Error calling NxRegisterComponent for {name}: {ex.Message}"); + Console.WriteLine($"Error calling NxRegisterComponent for {name}: {ex.Message} {ex.StackTrace}"); return -1; } } diff --git a/engine/src/scripting/managed/Scripts/CubeSystem.cs b/engine/src/scripting/managed/Scripts/CubeSystem.cs index 01c68016f..f41af769e 100644 --- a/engine/src/scripting/managed/Scripts/CubeSystem.cs +++ b/engine/src/scripting/managed/Scripts/CubeSystem.cs @@ -18,6 +18,17 @@ namespace Nexo.Scripts; +public struct TestComponent() : IComponentBase +{ + public struct Nested() + { + public Int32 NestedValue = 42; + public Vector3 NestedVector = new Vector3(1, 2, 3); + } + public Nested NestedComponent = new Nested(); + public Double RootValue = 84; +} + public class CubeSystem : SystemBase { private struct CubeAnimationState : IComponentBase @@ -82,6 +93,8 @@ private void SpawnCube(Vector3 position, Vector3 size, Vector3 rotation, Vector4 BreathingPhase = Random.Shared.NextSingle() * MathF.PI * 2.0f }; NativeInterop.AddComponent(cubeId, ref state); + var testComponent = new TestComponent(); + NativeInterop.AddComponent(cubeId, ref testComponent); _cubes.Add(cubeId); } diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 6197d6e21..2aaf89313 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -19,6 +19,7 @@ #include "Logger.hpp" #include "Nexo.hpp" #include "components/Uuid.hpp" +#include "ui/Field.hpp" namespace nexo::scripting { @@ -85,11 +86,39 @@ namespace nexo::scripting { return coordinator.entityHasComponent(entity, typeId); } - Int64 NxRegisterComponent(const char* name, const UInt64 size) + Int64 NxRegisterComponent(const char *name, const UInt64 componentSize, const Field *fields, const UInt64 fieldCount) { (void)name; // TODO: unused for now auto& coordinator = *Application::m_coordinator; - return coordinator.registerComponent(size); + + for (UInt64 i = 0; i < fieldCount; ++i) { + LOG(NEXO_DEV, "Registering field {}: {} of type {}", i, static_cast(fields[i].name), static_cast(fields[i].type)); + } + + const auto componentType = coordinator.registerComponent(componentSize); + + std::vector fieldVector; + fieldVector.reserve(fieldCount); + static_assert(sizeof(ecs::FieldType) == sizeof(FieldType), "FieldType enum size mismatch"); + static_assert(static_cast(ecs::FieldType::_Count) == static_cast(FieldType::_Count), "FieldType enum value count mismatch"); + for (UInt64 i = 0; i < fieldCount; ++i) { + fieldVector.emplace_back(ecs::Field { + .name = static_cast(fields[i].name), + .type = static_cast(fields[i].type), + .size = fields[i].size, + .offset = fields[i].offset, + }); + } + + coordinator.addComponentDescription( + componentType, + ecs::ComponentDescription { + .name = name, + .fields = std::move(fieldVector), + } + ); + + return componentType; } ComponentTypeIds NxGetComponentTypeIds() diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index 04f4ace9b..7c8db08b1 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -45,6 +45,7 @@ #include "components/Transform.hpp" namespace nexo::scripting { + struct Field; template struct ApiCallback; @@ -90,7 +91,7 @@ namespace nexo::scripting { NEXO_RET(void *) NxGetComponent(ecs::Entity entity, UInt32 componentTypeId); NEXO_RET(void) NxAddComponent(ecs::Entity entity, UInt32 typeId, const void *componentData); NEXO_RET(bool) NxHasComponent(ecs::Entity entity, UInt32 typeId); - NEXO_RET(Int64) NxRegisterComponent(const char *name, UInt64 size); + NEXO_RET(Int64) NxRegisterComponent(const char *name, UInt64 componentSize, const Field *fields, UInt64 fieldCount); NEXO_RET(ComponentTypeIds) NxGetComponentTypeIds(); @@ -107,7 +108,7 @@ namespace nexo::scripting { ApiCallback NxGetComponent{&scripting::NxGetComponent}; ApiCallback NxAddComponent{&scripting::NxAddComponent}; ApiCallback NxHasComponent{&scripting::NxHasComponent}; - ApiCallback NxRegisterComponent{&scripting::NxRegisterComponent}; + ApiCallback NxRegisterComponent{&scripting::NxRegisterComponent}; ApiCallback NxGetComponentTypeIds{&scripting::NxGetComponentTypeIds}; }; From c2dd199f62813ef33e01be4fb8f438ff512bc3cc Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 25 Jun 2025 08:40:32 +0900 Subject: [PATCH 445/450] ci(scripting-ui): fix dotnet install invalid permission on Linux runner Adds the DOTNET_INSTALL_DIR environment variable to the build and CodeQL workflows. This ensures that the .NET SDK is installed in a consistent and predictable location, improving the reliability of the build process. --- .github/workflows/build.yml | 1 + .github/workflows/codeql.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d80ee4da8..3ce7933c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,7 @@ jobs: VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg FEED_URL: https://nuget.pkg.github.com/NexoEngine/index.json VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/NexoEngine/index.json,readwrite" + DOTNET_INSTALL_DIR: "./.dotnet" permissions: contents: write packages: write diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 23b142b54..ac37b38d1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -9,6 +9,7 @@ jobs: VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg FEED_URL: https://nuget.pkg.github.com/NexoEngine/index.json VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/NexoEngine/index.json,readwrite" + DOTNET_INSTALL_DIR: "./.dotnet" runs-on: ${{ matrix.os }} permissions: # required for all workflows From a9c47de6f7e40f72c1214c086371c303e8ec1771 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 25 Jun 2025 08:43:40 +0900 Subject: [PATCH 446/450] ci(scripting-ui): fix dotnet install on test binary step --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ce7933c2..8e22e7e9f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -264,6 +264,8 @@ jobs: test-nsis-installer: name: Test NSIS installer runs-on: windows-latest + env: + DOTNET_INSTALL_DIR: "./.dotnet" needs: build steps: - name: Download NSIS installer @@ -334,6 +336,8 @@ jobs: test-deb-installer: name: Test DEB installer runs-on: ubuntu-latest + env: + DOTNET_INSTALL_DIR: "./.dotnet" needs: build steps: - name: Download DEB installer From 9991a003c1da963a00db2401c336e6174e1e0c14 Mon Sep 17 00:00:00 2001 From: Jean Cardonne Date: Wed, 25 Jun 2025 12:01:50 +0200 Subject: [PATCH 447/450] feat: input scripting --- engine/src/scripting/managed/Input.cs | 101 ++++++++++++++++++ engine/src/scripting/managed/KeyCode.cs | 83 ++++++++++++++ engine/src/scripting/managed/NativeInterop.cs | 87 +++++++++++++++ engine/src/scripting/native/NativeApi.cpp | 30 ++++++ engine/src/scripting/native/NativeApi.hpp | 10 ++ 5 files changed, 311 insertions(+) create mode 100644 engine/src/scripting/managed/Input.cs create mode 100644 engine/src/scripting/managed/KeyCode.cs diff --git a/engine/src/scripting/managed/Input.cs b/engine/src/scripting/managed/Input.cs new file mode 100644 index 000000000..cb2ef7566 --- /dev/null +++ b/engine/src/scripting/managed/Input.cs @@ -0,0 +1,101 @@ +//// Input.cs ////////////////////////////////////////////////////////////////// +// +// 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: Cardonne +// Date: 25/06/2025 +// Description: Static input class for easy keyboard and mouse input access +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Numerics; + +namespace Nexo +{ + /// + /// Provides static methods for querying keyboard and mouse input states. + /// Similar to Unity's Input class for familiar API usage. + /// + public static class Input + { + /// + /// Checks if the specified key is currently pressed. + /// + /// The key to check + /// true if the key is pressed, false otherwise + public static bool IsKeyPressed(KeyCode key) + { + return NativeInterop.IsKeyPressed((Int32)key); + } + + /// + /// Checks if the specified key is currently released. + /// + /// The key to check + /// true if the key is released, false otherwise + public static bool IsKeyReleased(KeyCode key) + { + return NativeInterop.IsKeyReleased((Int32)key); + } + + /// + /// Checks if the specified mouse button is currently pressed. + /// + /// The mouse button to check + /// true if the mouse button is pressed, false otherwise + public static bool IsMouseDown(MouseButton button) + { + return NativeInterop.IsMouseDown((Int32)button); + } + + /// + /// Checks if the specified mouse button is currently released. + /// + /// The mouse button to check + /// true if the mouse button is released, false otherwise + public static bool IsMouseReleased(MouseButton button) + { + return NativeInterop.IsMouseReleased((Int32)button); + } + + /// + /// Gets the current mouse position in screen coordinates. + /// + /// The current mouse position as a Vector2 + public static Vector2 GetMousePosition() + { + return NativeInterop.GetMousePosition(); + } + + /// + /// Checks if any key is currently pressed. + /// Useful for "Press any key to continue" scenarios. + /// + /// true if any key is pressed + public static bool IsAnyKeyPressed() + { + // Check common keys + foreach (KeyCode key in Enum.GetValues(typeof(KeyCode))) + { + if (IsKeyPressed(key)) + return true; + } + return false; + } + + /// + /// Checks if any mouse button is currently pressed. + /// + /// true if any mouse button is pressed + public static bool IsAnyMouseButtonPressed() + { + return IsMouseDown(MouseButton.Left) || + IsMouseDown(MouseButton.Right) || + IsMouseDown(MouseButton.Middle); + } + } +} \ No newline at end of file diff --git a/engine/src/scripting/managed/KeyCode.cs b/engine/src/scripting/managed/KeyCode.cs new file mode 100644 index 000000000..48ef1bc36 --- /dev/null +++ b/engine/src/scripting/managed/KeyCode.cs @@ -0,0 +1,83 @@ +//// KeyCode.cs /////////////////////////////////////////////////////////////// +// +// 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: Cardonne +// Date: 25/06/2025 +// Description: Key codes and mouse button definitions for input handling +// +/////////////////////////////////////////////////////////////////////////////// + +namespace Nexo +{ + /// + /// Keyboard key codes matching the C++ definitions + /// + public enum KeyCode + { + Space = 32, + + // Number keys + Key1 = 49, + Key2 = 50, + Key3 = 51, + + // Letter keys (using AZERTY layout codes) + Q = 65, + D = 68, + E = 69, + I = 73, + J = 74, + K = 75, + L = 76, + A = 81, + S = 83, + Z = 87, + + // Special keys + Tab = 258, + + // Arrow keys + Right = 262, + Left = 263, + Down = 264, + Up = 265, + + // Modifier keys + Shift = 340, + + // Additional common keys (can be extended as needed) + Escape = 256, + Enter = 257, + Backspace = 259, + Delete = 261, + + // Function keys + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + } + + /// + /// Mouse button codes matching the C++ definitions + /// + public enum MouseButton + { + Left = 0, + Right = 1, + Middle = 2, + } +} diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index 0533e134a..e8379602b 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -82,6 +82,21 @@ private unsafe struct NativeApiCallbacks [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate ComponentTypeIds NxGetComponentTypeIdsDelegate(); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate bool NxIsKeyPressedDelegate(Int32 keycode); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate bool NxIsKeyReleasedDelegate(Int32 keycode); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate bool NxIsMouseDownDelegate(Int32 button); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate bool NxIsMouseReleasedDelegate(Int32 button); + + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public unsafe delegate void NxGetMousePositionDelegate(Vector2* position); // Function pointers public HelloFromNativeDelegate NxHelloFromNative; @@ -95,6 +110,11 @@ private unsafe struct NativeApiCallbacks public NxHasComponentDelegate NxHasComponent; public NxRegisterComponentDelegate NxRegisterComponent; public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; + public NxIsKeyPressedDelegate NxIsKeyPressed; + public NxIsKeyReleasedDelegate NxIsKeyReleased; + public NxIsMouseDownDelegate NxIsMouseDown; + public NxIsMouseReleasedDelegate NxIsMouseReleased; + public NxGetMousePositionDelegate NxGetMousePosition; } private static NativeApiCallbacks s_callbacks; @@ -290,6 +310,73 @@ public static bool HasComponent(UInt32 entityId) return false; } } + + public static bool IsKeyPressed(Int32 keycode) + { + try + { + return s_callbacks.NxIsKeyPressed.Invoke(keycode); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling IsKeyPressed: {ex.Message}"); + return false; + } + } + + public static bool IsKeyReleased(Int32 keycode) + { + try + { + return s_callbacks.NxIsKeyReleased.Invoke(keycode); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling IsKeyReleased: {ex.Message}"); + return false; + } + } + + public static bool IsMouseDown(Int32 button) + { + try + { + return s_callbacks.NxIsMouseDown.Invoke(button); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling IsMouseDown: {ex.Message}"); + return false; + } + } + + public static bool IsMouseReleased(Int32 button) + { + try + { + return s_callbacks.NxIsMouseReleased.Invoke(button); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling IsMouseReleased: {ex.Message}"); + return false; + } + } + + public static unsafe Vector2 GetMousePosition() + { + try + { + Vector2 position = new Vector2(); + s_callbacks.NxGetMousePosition.Invoke(&position); + return position; + } + catch (Exception ex) + { + Console.WriteLine($"Error calling GetMousePosition: {ex.Message}"); + return Vector2.Zero; + } + } public static unsafe Int64 RegisterComponent(Type componentType) diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index 2aaf89313..d4cac7cd6 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -20,6 +20,7 @@ #include "Nexo.hpp" #include "components/Uuid.hpp" #include "ui/Field.hpp" +#include "core/event/Input.hpp" namespace nexo::scripting { @@ -139,6 +140,35 @@ namespace nexo::scripting { .PerspectiveCameraTarget = coordinator.getComponentType(), }; } + + bool NxIsKeyPressed(const Int32 keycode) + { + return event::isKeyPressed(keycode); + } + + bool NxIsKeyReleased(const Int32 keycode) + { + return event::isKeyReleased(keycode); + } + + bool NxIsMouseDown(const Int32 button) + { + return event::isMouseDown(button); + } + + bool NxIsMouseReleased(const Int32 button) + { + return event::isMouseReleased(button); + } + + void NxGetMousePosition(Vector2 *position) + { + if (position) { + const auto mousePos = event::getMousePosition(); + position->x = mousePos.x; + position->y = mousePos.y; + } + } } } // namespace nexo::scripting diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index 7c8db08b1..637904712 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -94,6 +94,11 @@ namespace nexo::scripting { NEXO_RET(Int64) NxRegisterComponent(const char *name, UInt64 componentSize, const Field *fields, UInt64 fieldCount); NEXO_RET(ComponentTypeIds) NxGetComponentTypeIds(); + NEXO_RET(bool) NxIsKeyPressed(Int32 keycode); + NEXO_RET(bool) NxIsKeyReleased(Int32 keycode); + NEXO_RET(bool) NxIsMouseDown(Int32 button); + NEXO_RET(bool) NxIsMouseReleased(Int32 button); + NEXO_RET(void) NxGetMousePosition(Vector2 *position); } @@ -110,6 +115,11 @@ namespace nexo::scripting { ApiCallback NxHasComponent{&scripting::NxHasComponent}; ApiCallback NxRegisterComponent{&scripting::NxRegisterComponent}; ApiCallback NxGetComponentTypeIds{&scripting::NxGetComponentTypeIds}; + ApiCallback NxIsKeyPressed{&scripting::NxIsKeyPressed}; + ApiCallback NxIsKeyReleased{&scripting::NxIsKeyReleased}; + ApiCallback NxIsMouseDown{&scripting::NxIsMouseDown}; + ApiCallback NxIsMouseReleased{&scripting::NxIsMouseReleased}; + ApiCallback NxGetMousePosition{&scripting::NxGetMousePosition}; }; inline NativeApiCallbacks nativeApiCallbacks; From a3cd9aa49c777843e6365ddc5195e263df860a83 Mon Sep 17 00:00:00 2001 From: Jean Cardonne Date: Wed, 25 Jun 2025 17:32:16 +0200 Subject: [PATCH 448/450] feat(scripting): add input handling API for keyboard and mouse --- CMakeLists.txt | 7 +- editor/main.cpp | 2 + editor/src/Editor.cpp | 2 + engine/CMakeLists.txt | 23 ++++-- engine/src/Application.cpp | 16 +++- engine/src/Application.hpp | 4 + engine/src/scripting/managed/CMakeLists.txt | 5 +- .../src/scripting/native/ManagedTypedef.hpp | 1 + engine/src/scripting/native/NativeApi.cpp | 30 +++++++ engine/src/scripting/native/NativeApi.hpp | 78 ++++++++++--------- engine/src/scripting/native/Scripting.cpp | 4 + 11 files changed, 125 insertions(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bf893e845..e808cec2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ set(NEXO_GIT_SUBMODULE OFF CACHE BOOL "Enable git submodules init and update") set(NEXO_BOOTSTRAP_VCPKG OFF CACHE BOOL "Enable vcpkg bootstrap") set(NEXO_BUILD_TESTS ON CACHE BOOL "Enable tests") set(NEXO_BUILD_EXAMPLES OFF CACHE BOOL "Enable examples") +set(NEXO_BUILD_SCRIPTING ON CACHE BOOL "Enable C# scripting support") set(NEXO_GRAPHICS_API "OpenGL" CACHE STRING "Graphics API to use") if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") @@ -97,8 +98,10 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/editor/CMakeLists.txt") # SETUP ENGINE include("${CMAKE_CURRENT_SOURCE_DIR}/engine/CMakeLists.txt") # SETUP MANAGED CSHARP LIB -include("${CMAKE_CURRENT_SOURCE_DIR}/engine/src/scripting/managed/CMakeLists.txt") -add_dependencies(nexoEditor nexoManaged) +if(NEXO_BUILD_SCRIPTING) + include("${CMAKE_CURRENT_SOURCE_DIR}/engine/src/scripting/managed/CMakeLists.txt") + add_dependencies(nexoEditor nexoManaged) +endif() # SETUP EXAMPLE include("${CMAKE_CURRENT_SOURCE_DIR}/examples/CMakeLists.txt") # SETUP TESTS diff --git a/editor/main.cpp b/editor/main.cpp index 1759e738a..dd5f6db66 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -25,8 +25,10 @@ #include #include "Path.hpp" +#ifdef NEXO_SCRIPTING_ENABLED #include "scripting/native/ManagedTypedef.hpp" #include "scripting/native/Scripting.hpp" +#endif int main(int argc, char **argv) try { diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 790531b74..2cf25c800 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -229,8 +229,10 @@ namespace nexo::editor { 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 +#ifdef NEXO_SCRIPTING_ENABLED 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 +#endif } bool Editor::isOpen() const diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 54ea96057..422343dbb 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -50,7 +50,6 @@ set(COMMON_SOURCES engine/src/systems/CameraSystem.cpp engine/src/systems/RenderSystem.cpp engine/src/systems/LightSystem.cpp - engine/src/systems/ScriptingSystem.cpp engine/src/systems/lights/AmbientLightSystem.cpp engine/src/systems/lights/PointLightsSystem.cpp engine/src/systems/lights/DirectionalLightsSystem.cpp @@ -63,11 +62,18 @@ set(COMMON_SOURCES engine/src/assets/AssetImporterContext.cpp engine/src/assets/Assets/Model/ModelImporter.cpp engine/src/assets/Assets/Texture/TextureImporter.cpp - engine/src/scripting/native/Scripting.cpp - engine/src/scripting/native/HostString.cpp - engine/src/scripting/native/NativeApi.cpp ) +# Add scripting sources if enabled +if(NEXO_BUILD_SCRIPTING) + list(APPEND COMMON_SOURCES + engine/src/systems/ScriptingSystem.cpp + engine/src/scripting/native/Scripting.cpp + engine/src/scripting/native/HostString.cpp + engine/src/scripting/native/NativeApi.cpp + ) +endif() + # Add API-specific sources if(NEXO_GRAPHICS_API STREQUAL "OpenGL") list(APPEND COMMON_SOURCES @@ -122,9 +128,12 @@ target_link_libraries(nexoRenderer PRIVATE Boost::dll) # Scripting ########################################### -# nethost -find_package(unofficial-nethost CONFIG REQUIRED) -target_link_libraries(nexoRenderer PRIVATE unofficial::nethost::nethost) +if(NEXO_BUILD_SCRIPTING) + # nethost + find_package(unofficial-nethost CONFIG REQUIRED) + target_link_libraries(nexoRenderer PRIVATE unofficial::nethost::nethost) + target_compile_definitions(nexoRenderer PUBLIC NEXO_SCRIPTING_ENABLED) +endif() if(NEXO_GRAPHICS_API STREQUAL "OpenGL") target_compile_definitions(nexoRenderer PRIVATE NX_GRAPHICS_API_OPENGL) diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index fdb268c06..151abd3ba 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -31,10 +31,12 @@ #include "exceptions/Exceptions.hpp" #include "renderer/RendererExceptions.hpp" #include "renderer/Renderer.hpp" +#ifdef NEXO_SCRIPTING_ENABLED #include "scripting/native/Scripting.hpp" +#include "systems/ScriptingSystem.hpp" +#endif #include "systems/CameraSystem.hpp" #include "systems/RenderSystem.hpp" -#include "systems/ScriptingSystem.hpp" #include "systems/lights/DirectionalLightsSystem.hpp" #include "systems/lights/PointLightsSystem.hpp" @@ -197,17 +199,27 @@ namespace nexo { auto ambientLightSystem = m_coordinator->registerGroupSystem(); m_lightSystem = std::make_shared(ambientLightSystem, directionalLightSystem, pointLightSystem, spotLightSystem); +#ifdef NEXO_SCRIPTING_ENABLED m_scriptingSystem = std::make_shared(); +#endif } int Application::initScripting() const { +#ifdef NEXO_SCRIPTING_ENABLED return m_scriptingSystem->init(); +#else + return 0; +#endif } int Application::shutdownScripting() const { +#ifdef NEXO_SCRIPTING_ENABLED return m_scriptingSystem->shutdown(); +#else + return 0; +#endif } Application::Application() @@ -280,7 +292,9 @@ namespace nexo { { auto &renderContext = m_coordinator->getSingletonComponent(); +#ifdef NEXO_SCRIPTING_ENABLED m_scriptingSystem->update(); +#endif if (!m_isMinimized) { diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index 8ff6ff73d..17e88d400 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -38,9 +38,11 @@ namespace nexo { +#ifdef NEXO_SCRIPTING_ENABLED namespace system { class ScriptingSystem; } +#endif enum EventDebugFlags { DEBUG_LOG_RESIZE_EVENT = 1 << 0, @@ -261,7 +263,9 @@ namespace nexo { std::shared_ptr m_lightSystem; std::shared_ptr m_perspectiveCameraControllerSystem; std::shared_ptr m_perspectiveCameraTargetSystem; +#ifdef NEXO_SCRIPTING_ENABLED std::shared_ptr m_scriptingSystem; +#endif std::vector m_profilesResults; }; diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt index 2c9cee71f..4e5fd0016 100644 --- a/engine/src/scripting/managed/CMakeLists.txt +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -27,6 +27,8 @@ set(SOURCES ObjectFactory.cs NativeInterop.cs Logger.cs + Input.cs + KeyCode.cs Components/Camera.cs Components/Light.cs Components/Render.cs @@ -40,6 +42,7 @@ set(SOURCES Systems/SystemBase.cs Systems/WorldState.cs Scripts/CubeSystem.cs + Scripts/InputDemoSystem.cs ) # Locate the dotnet executable @@ -69,7 +72,7 @@ endif() add_custom_target(nexoManaged ALL COMMAND ${DOTNET_EXECUTABLE} build Nexo.csproj ${ARCH_ARG} - -c $ # Matches Debug/Release configuration + -c "$,Debug,Release>" # Matches Debug/Release configuration WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} # Working directory for the build COMMENT "Building .NET managed project (Nexo.csproj)..." ) diff --git a/engine/src/scripting/native/ManagedTypedef.hpp b/engine/src/scripting/native/ManagedTypedef.hpp index 9c8187b09..41f5fe3a9 100644 --- a/engine/src/scripting/native/ManagedTypedef.hpp +++ b/engine/src/scripting/native/ManagedTypedef.hpp @@ -45,6 +45,7 @@ namespace nexo::scripting { // Decimal A decimal (128-bit) value. Unsupported in C++ using IntPtr = void*; // A pointer to an unspecified type. + using Vector2 = glm::vec2; using Vector3 = glm::vec3; using Vector4 = glm::vec4; diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index d4cac7cd6..accf3efb0 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -24,6 +24,9 @@ namespace nexo::scripting { + // Define the global callbacks instance + NativeApiCallbacks nativeApiCallbacks; + // Static message to return to C# static const char* nativeMessage = "Hello from C++ native code!"; @@ -171,4 +174,31 @@ namespace nexo::scripting { } } + // Initialize the callbacks with the extern "C" functions + void initializeNativeApiCallbacks() { + nativeApiCallbacks.NxHelloFromNative = &NxHelloFromNative; + nativeApiCallbacks.NxAddNumbers = &NxAddNumbers; + nativeApiCallbacks.NxGetNativeMessage = &NxGetNativeMessage; + nativeApiCallbacks.NxLog = &NxLog; + nativeApiCallbacks.NxCreateCube = &NxCreateCube; + nativeApiCallbacks.NxGetTransformComponent = &NxGetTransformComponent; + nativeApiCallbacks.NxGetComponent = &NxGetComponent; + nativeApiCallbacks.NxAddComponent = &NxAddComponent; + nativeApiCallbacks.NxHasComponent = &NxHasComponent; + nativeApiCallbacks.NxRegisterComponent = &NxRegisterComponent; + nativeApiCallbacks.NxGetComponentTypeIds = &NxGetComponentTypeIds; + nativeApiCallbacks.NxIsKeyPressed = &NxIsKeyPressed; + nativeApiCallbacks.NxIsKeyReleased = &NxIsKeyReleased; + nativeApiCallbacks.NxIsMouseDown = &NxIsMouseDown; + nativeApiCallbacks.NxIsMouseReleased = &NxIsMouseReleased; + nativeApiCallbacks.NxGetMousePosition = &NxGetMousePosition; + } + + // Static initialization + static struct NativeApiCallbacksInitializer { + NativeApiCallbacksInitializer() { + initializeNativeApiCallbacks(); + } + } nativeApiCallbacksInitializer; + } // namespace nexo::scripting diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index 637904712..5f52cc5de 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -54,33 +54,40 @@ namespace nexo::scripting { struct ApiCallback { using Type = Ret (NEXO_CALL *)(Args...); + // Default constructor + ApiCallback() : func(nullptr) {} + // Constructor explicitly accepting function pointers explicit ApiCallback(Type f) : func(f) {} - explicit ApiCallback(nullptr_t) = delete; + // Assignment operator for initialization + ApiCallback& operator=(Type f) { + func = f; + return *this; + } - // Delete the default constructor to enforce initialization - ApiCallback() = delete; + explicit ApiCallback(nullptr_t) = delete; private: Type func; }; - extern "C" { - struct ComponentTypeIds { - UInt32 Transform; - UInt32 AmbientLight; - UInt32 DirectionalLight; - UInt32 PointLight; - UInt32 SpotLight; - UInt32 RenderComponent; - UInt32 SceneTag; - UInt32 CameraComponent; - UInt32 UuidComponent; - UInt32 PerspectiveCameraController; - UInt32 PerspectiveCameraTarget; - }; + struct ComponentTypeIds { + UInt32 Transform; + UInt32 AmbientLight; + UInt32 DirectionalLight; + UInt32 PointLight; + UInt32 SpotLight; + UInt32 RenderComponent; + UInt32 SceneTag; + UInt32 CameraComponent; + UInt32 UuidComponent; + UInt32 PerspectiveCameraController; + UInt32 PerspectiveCameraTarget; + }; + // Forward declare the extern "C" functions within the namespace + extern "C" { NEXO_RET(void) NxHelloFromNative(void); NEXO_RET(Int32) NxAddNumbers(Int32 a, Int32 b); NEXO_RET(const char*) NxGetNativeMessage(void); @@ -99,29 +106,28 @@ namespace nexo::scripting { NEXO_RET(bool) NxIsMouseDown(Int32 button); NEXO_RET(bool) NxIsMouseReleased(Int32 button); NEXO_RET(void) NxGetMousePosition(Vector2 *position); - } struct NativeApiCallbacks { - ApiCallback NxHelloFromNative{&scripting::NxHelloFromNative}; - ApiCallback NxAddNumbers{&scripting::NxAddNumbers}; - ApiCallback NxGetNativeMessage{&scripting::NxGetNativeMessage}; - ApiCallback NxLog{&scripting::NxLog}; - - ApiCallback NxCreateCube{&scripting::NxCreateCube}; - ApiCallback NxGetTransformComponent{&scripting::NxGetTransformComponent}; - ApiCallback NxGetComponent{&scripting::NxGetComponent}; - ApiCallback NxAddComponent{&scripting::NxAddComponent}; - ApiCallback NxHasComponent{&scripting::NxHasComponent}; - ApiCallback NxRegisterComponent{&scripting::NxRegisterComponent}; - ApiCallback NxGetComponentTypeIds{&scripting::NxGetComponentTypeIds}; - ApiCallback NxIsKeyPressed{&scripting::NxIsKeyPressed}; - ApiCallback NxIsKeyReleased{&scripting::NxIsKeyReleased}; - ApiCallback NxIsMouseDown{&scripting::NxIsMouseDown}; - ApiCallback NxIsMouseReleased{&scripting::NxIsMouseReleased}; - ApiCallback NxGetMousePosition{&scripting::NxGetMousePosition}; + ApiCallback NxHelloFromNative; + ApiCallback NxAddNumbers; + ApiCallback NxGetNativeMessage; + ApiCallback NxLog; + + ApiCallback NxCreateCube; + ApiCallback NxGetTransformComponent; + ApiCallback NxGetComponent; + ApiCallback NxAddComponent; + ApiCallback NxHasComponent; + ApiCallback NxRegisterComponent; + ApiCallback NxGetComponentTypeIds; + ApiCallback NxIsKeyPressed; + ApiCallback NxIsKeyReleased; + ApiCallback NxIsMouseDown; + ApiCallback NxIsMouseReleased; + ApiCallback NxGetMousePosition; }; - inline NativeApiCallbacks nativeApiCallbacks; + extern NativeApiCallbacks nativeApiCallbacks; } // namespace nexo::scripting diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index abc75c470..ae329eb15 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -319,6 +319,10 @@ namespace nexo::scripting { HostHandler::Status HostHandler::initCallbacks() { + // Ensure callbacks are initialized + extern void initializeNativeApiCallbacks(); + initializeNativeApiCallbacks(); + // Initialize callbacks if (m_managedApi.NativeInterop.Initialize(&nativeApiCallbacks, sizeof(nativeApiCallbacks))) { m_params.errorCallback("Failed to initialize native API callbacks"); From 7bba020758f67489a6840a50b950f8b5ef73eb66 Mon Sep 17 00:00:00 2001 From: Jean Cardonne Date: Wed, 25 Jun 2025 17:32:56 +0200 Subject: [PATCH 449/450] feat(scripting): add input demo system example --- .../managed/Scripts/InputDemoSystem.cs | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 engine/src/scripting/managed/Scripts/InputDemoSystem.cs diff --git a/engine/src/scripting/managed/Scripts/InputDemoSystem.cs b/engine/src/scripting/managed/Scripts/InputDemoSystem.cs new file mode 100644 index 000000000..2c2d4bbb6 --- /dev/null +++ b/engine/src/scripting/managed/Scripts/InputDemoSystem.cs @@ -0,0 +1,221 @@ +//// InputDemoSystem.cs //////////////////////////////////////////////////////// +// +// 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: Cardonne +// Date: 25/06/2025 +// Description: Demo system showing input handling capabilities +// +/////////////////////////////////////////////////////////////////////////////// + +using System.Numerics; +using Nexo.Components; +using Nexo.Systems; + +namespace Nexo.Scripts; + +/// +/// Demonstration system that shows various input handling capabilities: +/// - Spawning cubes with keyboard input +/// - Deleting cubes with keyboard input +/// - Changing cube colors based on mouse position +/// - Different behaviors for key combinations +/// +public class InputDemoSystem : SystemBase +{ + private readonly List _cubes = []; + private bool _spaceWasPressed = false; + private bool _deleteWasPressed = false; + private float _timeSinceLastSpawn = 0.0f; + private const float SpawnCooldown = 0.5f; // Prevent spam spawning + + protected override void OnInitialize(WorldState worldState) + { + Logger.Log(LogLevel.Info, $"Initializing {Name} - Press SPACE to spawn cubes, DELETE to remove them!"); + Logger.Log(LogLevel.Info, "Hold SHIFT while pressing SPACE for rainbow cubes!"); + Logger.Log(LogLevel.Info, "Left click makes cubes jump, right click shows mouse position"); + } + + protected override void OnUpdate(WorldState worldState) + { + Single deltaTime = (Single)worldState.Time.DeltaTime; + _timeSinceLastSpawn += deltaTime; + + // Handle cube spawning with SPACE key + HandleCubeSpawning(); + + // Handle cube deletion with DELETE key + HandleCubeDeletion(); + + // Handle mouse-based color changes + HandleMouseColorChange(); + + // Handle arrow keys for moving last spawned cube + HandleCubeMovement(deltaTime); + + // Display help message when TAB is pressed + if (Input.IsKeyPressed(KeyCode.Tab)) + { + Logger.Log(LogLevel.Info, "=== Input Demo Controls ==="); + Logger.Log(LogLevel.Info, "SPACE: Spawn a cube"); + Logger.Log(LogLevel.Info, "SHIFT+SPACE: Spawn a rainbow cube"); + Logger.Log(LogLevel.Info, "DELETE: Remove last cube"); + Logger.Log(LogLevel.Info, "Arrow Keys: Move last cube"); + Logger.Log(LogLevel.Info, "Left Click: Make cubes jump"); + Logger.Log(LogLevel.Info, "Right Click: Log mouse position"); + } + } + + private void HandleCubeSpawning() + { + bool spacePressed = Input.IsKeyPressed(KeyCode.Space); + + // Detect key press (transition from released to pressed) + if (spacePressed && !_spaceWasPressed && _timeSinceLastSpawn >= SpawnCooldown) + { + bool shiftHeld = Input.IsKeyPressed(KeyCode.Shift); + SpawnCube(shiftHeld); + _timeSinceLastSpawn = 0.0f; + } + + _spaceWasPressed = spacePressed; + } + + private void HandleCubeDeletion() + { + bool deletePressed = Input.IsKeyPressed(KeyCode.Delete); + + // Detect key press (transition from released to pressed) + if (deletePressed && !_deleteWasPressed && _cubes.Count > 0) + { + UInt32 lastCube = _cubes[_cubes.Count - 1]; + _cubes.RemoveAt(_cubes.Count - 1); + Logger.Log(LogLevel.Info, $"Deleted cube {lastCube}. Remaining cubes: {_cubes.Count}"); + } + + _deleteWasPressed = deletePressed; + } + + private void HandleMouseColorChange() + { + Vector2 mousePos = Input.GetMousePosition(); + + // Left click makes cubes jump + if (Input.IsMouseDown(MouseButton.Left)) + { + foreach (var cubeId in _cubes) + { + ref Transform transform = ref NativeInterop.GetComponent(cubeId); + // Add a small upward impulse effect + transform.pos.Y = MathF.Max(transform.pos.Y, 5.0f + MathF.Sin((float)_timeSinceLastSpawn * 10.0f) * 2.0f); + } + } + + // Log mouse position when right clicking + if (Input.IsMouseDown(MouseButton.Right)) + { + Logger.Log(LogLevel.Info, $"Mouse position: {mousePos.X}, {mousePos.Y}"); + } + } + + private void HandleCubeMovement(float deltaTime) + { + if (_cubes.Count == 0) return; + + // Move the last spawned cube with arrow keys + UInt32 lastCube = _cubes[_cubes.Count - 1]; + ref Transform transform = ref NativeInterop.GetComponent(lastCube); + + float moveSpeed = 5.0f * deltaTime; + + if (Input.IsKeyPressed(KeyCode.Left)) + transform.pos.X -= moveSpeed; + if (Input.IsKeyPressed(KeyCode.Right)) + transform.pos.X += moveSpeed; + if (Input.IsKeyPressed(KeyCode.Up)) + transform.pos.Z -= moveSpeed; + if (Input.IsKeyPressed(KeyCode.Down)) + transform.pos.Z += moveSpeed; + } + + private void SpawnCube(bool rainbow) + { + // Random position around the center + Vector3 position = new Vector3( + Random.Shared.NextSingle() * 10.0f - 5.0f, + Random.Shared.NextSingle() * 5.0f + 2.0f, + Random.Shared.NextSingle() * 10.0f - 5.0f + ); + + Vector3 size = Vector3.One; + Vector3 rotation = Vector3.Zero; + + Vector4 color; + if (rainbow) + { + // Create rainbow effect + float hue = Random.Shared.NextSingle(); + color = HsvToRgb(hue, 1.0f, 1.0f); + } + else + { + // Random color + color = new Vector4( + Random.Shared.NextSingle(), + Random.Shared.NextSingle(), + Random.Shared.NextSingle(), + 1.0f + ); + } + + UInt32 cubeId = NativeInterop.CreateCube(position, size, rotation, color); + _cubes.Add(cubeId); + + Logger.Log(LogLevel.Info, $"Spawned {(rainbow ? "rainbow" : "regular")} cube {cubeId} at {position}. Total cubes: {_cubes.Count}"); + } + + private static Vector4 HsvToRgb(float h, float s, float v) + { + float c = v * s; + float x = c * (1 - MathF.Abs((h * 6) % 2 - 1)); + float m = v - c; + + float r, g, b; + if (h < 1.0f / 6.0f) + { + r = c; g = x; b = 0; + } + else if (h < 2.0f / 6.0f) + { + r = x; g = c; b = 0; + } + else if (h < 3.0f / 6.0f) + { + r = 0; g = c; b = x; + } + else if (h < 4.0f / 6.0f) + { + r = 0; g = x; b = c; + } + else if (h < 5.0f / 6.0f) + { + r = x; g = 0; b = c; + } + else + { + r = c; g = 0; b = x; + } + + return new Vector4(r + m, g + m, b + m, 1.0f); + } + + protected override void OnShutdown(WorldState worldState) + { + _cubes.Clear(); + Logger.Log(LogLevel.Info, $"Shutting down {Name} system"); + } +} From 6237574eca1bfe191607c042a656dce7cda56a6d Mon Sep 17 00:00:00 2001 From: Jean Cardonne Date: Thu, 26 Jun 2025 18:14:13 +0200 Subject: [PATCH 450/450] feat(scripting): optimize IsAnyKeyPressed with native implementation --- engine/src/scripting/managed/Input.cs | 8 +------ engine/src/scripting/managed/KeyCode.cs | 16 ++++++++++++-- engine/src/scripting/managed/NativeInterop.cs | 17 +++++++++++++++ engine/src/scripting/native/NativeApi.cpp | 21 +++++++++++++++++++ engine/src/scripting/native/NativeApi.hpp | 4 ++++ engine/src/scripting/native/Scripting.cpp | 1 - 6 files changed, 57 insertions(+), 10 deletions(-) diff --git a/engine/src/scripting/managed/Input.cs b/engine/src/scripting/managed/Input.cs index cb2ef7566..801803bf5 100644 --- a/engine/src/scripting/managed/Input.cs +++ b/engine/src/scripting/managed/Input.cs @@ -78,13 +78,7 @@ public static Vector2 GetMousePosition() /// true if any key is pressed public static bool IsAnyKeyPressed() { - // Check common keys - foreach (KeyCode key in Enum.GetValues(typeof(KeyCode))) - { - if (IsKeyPressed(key)) - return true; - } - return false; + return NativeInterop.IsAnyKeyPressed(); } /// diff --git a/engine/src/scripting/managed/KeyCode.cs b/engine/src/scripting/managed/KeyCode.cs index 48ef1bc36..e98bcce29 100644 --- a/engine/src/scripting/managed/KeyCode.cs +++ b/engine/src/scripting/managed/KeyCode.cs @@ -15,7 +15,19 @@ namespace Nexo { /// - /// Keyboard key codes matching the C++ definitions + /// Keyboard key codes matching the C++ definitions. + /// + /// These values follow GLFW's key code system exactly to enable direct pass-through + /// to the underlying windowing API without translation. GLFW uses a hybrid approach: + /// - Printable ASCII characters (32-126) use their ASCII values directly + /// - Special keys (arrows, function keys, etc.) use GLFW-specific codes starting from 256 + /// + /// This design choice allows the engine to pass key codes directly to GLFW's input + /// functions without any conversion overhead. When adding new keys, use the corresponding + /// GLFW key code value. + /// + /// For a complete list of GLFW key codes, see: + /// https://www.glfw.org/docs/latest/group__keys.html /// public enum KeyCode { @@ -26,7 +38,7 @@ public enum KeyCode Key2 = 50, Key3 = 51, - // Letter keys (using AZERTY layout codes) + // Letter keys (GLFW uses ASCII values for printable characters) Q = 65, D = 68, E = 69, diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs index e8379602b..24617b68f 100644 --- a/engine/src/scripting/managed/NativeInterop.cs +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -89,6 +89,9 @@ private unsafe struct NativeApiCallbacks [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate bool NxIsKeyReleasedDelegate(Int32 keycode); + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] + public delegate bool NxIsAnyKeyPressedDelegate(); + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Ansi)] public delegate bool NxIsMouseDownDelegate(Int32 button); @@ -112,6 +115,7 @@ private unsafe struct NativeApiCallbacks public NxGetComponentTypeIdsDelegate NxGetComponentTypeIds; public NxIsKeyPressedDelegate NxIsKeyPressed; public NxIsKeyReleasedDelegate NxIsKeyReleased; + public NxIsAnyKeyPressedDelegate NxIsAnyKeyPressed; public NxIsMouseDownDelegate NxIsMouseDown; public NxIsMouseReleasedDelegate NxIsMouseReleased; public NxGetMousePositionDelegate NxGetMousePosition; @@ -337,6 +341,19 @@ public static bool IsKeyReleased(Int32 keycode) } } + public static bool IsAnyKeyPressed() + { + try + { + return s_callbacks.NxIsAnyKeyPressed.Invoke(); + } + catch (Exception ex) + { + Console.WriteLine($"Error calling IsAnyKeyPressed: {ex.Message}"); + return false; + } + } + public static bool IsMouseDown(Int32 button) { try diff --git a/engine/src/scripting/native/NativeApi.cpp b/engine/src/scripting/native/NativeApi.cpp index accf3efb0..82eed7001 100644 --- a/engine/src/scripting/native/NativeApi.cpp +++ b/engine/src/scripting/native/NativeApi.cpp @@ -154,6 +154,26 @@ namespace nexo::scripting { return event::isKeyReleased(keycode); } + bool NxIsAnyKeyPressed() + { + // GLFW key range: 32-96 (printable characters), 256-348 (special keys) + // Check printable ASCII range + for (Int32 key = 32; key <= 96; ++key) { + if (event::isKeyPressed(key)) { + return true; + } + } + + // Check special keys range + for (Int32 key = 256; key <= 348; ++key) { + if (event::isKeyPressed(key)) { + return true; + } + } + + return false; + } + bool NxIsMouseDown(const Int32 button) { return event::isMouseDown(button); @@ -189,6 +209,7 @@ namespace nexo::scripting { nativeApiCallbacks.NxGetComponentTypeIds = &NxGetComponentTypeIds; nativeApiCallbacks.NxIsKeyPressed = &NxIsKeyPressed; nativeApiCallbacks.NxIsKeyReleased = &NxIsKeyReleased; + nativeApiCallbacks.NxIsAnyKeyPressed = &NxIsAnyKeyPressed; nativeApiCallbacks.NxIsMouseDown = &NxIsMouseDown; nativeApiCallbacks.NxIsMouseReleased = &NxIsMouseReleased; nativeApiCallbacks.NxGetMousePosition = &NxGetMousePosition; diff --git a/engine/src/scripting/native/NativeApi.hpp b/engine/src/scripting/native/NativeApi.hpp index 5f52cc5de..630979e33 100644 --- a/engine/src/scripting/native/NativeApi.hpp +++ b/engine/src/scripting/native/NativeApi.hpp @@ -103,6 +103,7 @@ namespace nexo::scripting { NEXO_RET(bool) NxIsKeyPressed(Int32 keycode); NEXO_RET(bool) NxIsKeyReleased(Int32 keycode); + NEXO_RET(bool) NxIsAnyKeyPressed(void); NEXO_RET(bool) NxIsMouseDown(Int32 button); NEXO_RET(bool) NxIsMouseReleased(Int32 button); NEXO_RET(void) NxGetMousePosition(Vector2 *position); @@ -123,6 +124,7 @@ namespace nexo::scripting { ApiCallback NxGetComponentTypeIds; ApiCallback NxIsKeyPressed; ApiCallback NxIsKeyReleased; + ApiCallback NxIsAnyKeyPressed; ApiCallback NxIsMouseDown; ApiCallback NxIsMouseReleased; ApiCallback NxGetMousePosition; @@ -130,4 +132,6 @@ namespace nexo::scripting { extern NativeApiCallbacks nativeApiCallbacks; + void initializeNativeApiCallbacks(); + } // namespace nexo::scripting diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index ae329eb15..d69b3acd1 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -320,7 +320,6 @@ namespace nexo::scripting { HostHandler::Status HostHandler::initCallbacks() { // Ensure callbacks are initialized - extern void initializeNativeApiCallbacks(); initializeNativeApiCallbacks(); // Initialize callbacks

    Hz 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Ð#*L?F-$d>$Ls4P3P5E6&%0ad2QY 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 literal 0 HcmV?d00001 From 283f06c6b9452365661ccb7afd780ee887895613 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 10:54:07 +0900 Subject: [PATCH 176/450] feat(document-windows): add utils function to project a ray in the world --- common/math/Projection.cpp | 39 ++++++++++++++++++++++++++++++++++++++ common/math/Projection.hpp | 24 +++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 common/math/Projection.cpp create mode 100644 common/math/Projection.hpp diff --git a/common/math/Projection.cpp b/common/math/Projection.cpp new file mode 100644 index 000000000..9691e61fd --- /dev/null +++ b/common/math/Projection.cpp @@ -0,0 +1,39 @@ +//// Projection.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: 20/04/2025 +// Description: Source file for the math projection utils +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Projection.hpp" + +namespace nexo::math { + + const glm::vec3 projectRayToWorld(float x, float y, + const glm::mat4 &viewProjectionMatrix, + const glm::vec3 &cameraPosition, + unsigned int width, unsigned int height + ) { + // Convert to NDC + float ndcX = (2.0f * x) / width - 1.0f; + float ndcY = 1.0f - (2.0f * y) / height; + + glm::mat4 inverseViewProj = glm::inverse(viewProjectionMatrix); + + // Points in NDC space at near and far planes + glm::vec4 nearPoint = inverseViewProj * glm::vec4(ndcX, ndcY, -1.0f, 1.0f); + + nearPoint /= nearPoint.w; + + glm::vec3 rayDir = glm::normalize(glm::vec3(nearPoint) - cameraPosition); + + return rayDir; + } +} diff --git a/common/math/Projection.hpp b/common/math/Projection.hpp new file mode 100644 index 000000000..0e8a923a6 --- /dev/null +++ b/common/math/Projection.hpp @@ -0,0 +1,24 @@ +//// Projection.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: Mehdy MORVAN +// Date: 20/04/2025 +// Description: Header file for the math projection utils +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include + +namespace nexo::math { + const glm::vec3 projectRayToWorld(float x, float y, + const glm::mat4 &viewProjectionMatrix, + const glm::vec3 &cameraPosition, + unsigned int width, unsigned int height + ); +} From c8b3008a9ac83248afdc69a7352f2e4690866e64 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 10:54:32 +0900 Subject: [PATCH 177/450] feat(document-windows): add grid shader --- engine/src/systems/RenderSystem.cpp | 44 ++++++ engine/src/systems/RenderSystem.hpp | 1 + resources/shaders/grid_shader.glsl | 202 ++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 resources/shaders/grid_shader.glsl diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 93dc02256..39b0fb5a7 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -21,6 +21,8 @@ #include "components/Camera.hpp" #include "components/Transform.hpp" #include "components/Render.hpp" +#include "core/event/Input.hpp" +#include "math/Projection.hpp" #include "renderer/RenderCommand.hpp" #include "ecs/Coordinator.hpp" #include "core/exceptions/Exceptions.hpp" @@ -94,6 +96,43 @@ namespace nexo::system { } } + void RenderSystem::renderGrid(const components::CameraContext &camera, components::RenderContext &renderContext) + { + //TODO: Implement a way to do this without relying on camera render target when none is bound + if (!camera.renderTarget) + return; + renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, "Grid shader"); + auto gridShader = renderContext.renderer3D.getShader(); + gridShader->bind(); + + // Grid appearance + gridShader->setUniformFloat("uGridSize", 100.0f); // Size of grid from center to edge + gridShader->setUniformFloat("uGridCellSize", 0.025f); // Base size of each cell + gridShader->setUniformFloat("uGridMinPixelsBetweenCells", 2.0f); // For LOD calculation + + gridShader->setUniformFloat4("uGridColorThin", {0.5f, 0.55f, 0.7f, 0.6f}); // Soft blue-purple + gridShader->setUniformFloat4("uGridColorThick", {0.7f, 0.75f, 0.9f, 0.8f}); // Lighter blue-purple + const glm::vec2 &mousePos = event::getMousePosition(); + const glm::vec2 &screenSize = camera.renderTarget->getSize(); + const glm::vec3 &rayDir = math::projectRayToWorld(mousePos.x, mousePos.y, camera.viewProjectionMatrix, camera.cameraPosition, screenSize.x, screenSize.y); + + glm::vec3 mouseWorldPos = camera.cameraPosition; + if (rayDir.y != 0.0f) { + float t = -camera.cameraPosition.y / rayDir.y; + if (t > 0.0f) { + mouseWorldPos = camera.cameraPosition + rayDir * t; + } + } + // For glowing effect + gridShader->setUniformFloat3("uMouseWorldPos", mouseWorldPos); + gridShader->setUniformFloat("uTime", static_cast(glfwGetTime())); + + renderer::RenderCommand::setDepthMask(false); + renderer::RenderCommand::drawUnIndexed(6); + gridShader->unbind(); + renderer::RenderCommand::setDepthMask(true); + } + void RenderSystem::update() { auto &renderContext = getSingleton(); @@ -185,6 +224,11 @@ namespace nexo::system { } } + if (sceneType == SceneType::EDITOR) + { + renderGrid(camera, renderContext); + } + if (camera.renderTarget != nullptr) { camera.renderTarget->unbind(); diff --git a/engine/src/systems/RenderSystem.hpp b/engine/src/systems/RenderSystem.hpp index a1fb81aa3..1a015d32f 100644 --- a/engine/src/systems/RenderSystem.hpp +++ b/engine/src/systems/RenderSystem.hpp @@ -50,5 +50,6 @@ namespace nexo::system { void update(); private: void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext); + void renderGrid(const components::CameraContext &camera, components::RenderContext &renderContext); }; } diff --git a/resources/shaders/grid_shader.glsl b/resources/shaders/grid_shader.glsl new file mode 100644 index 000000000..701fa04c8 --- /dev/null +++ b/resources/shaders/grid_shader.glsl @@ -0,0 +1,202 @@ +/* + + Copyright 2024 Etay Meiri + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#type vertex +#version 430 + +uniform mat4 uViewProjection = mat4(1.0); +uniform float uGridSize = 100.0; +uniform vec3 uCamPos; + +out vec3 FragPos; + +const vec3 gridPos[4] = vec3[4]( + vec3(-1.0, 0.0, -1.0), // bottom left + vec3(1.0, 0.0, -1.0), // bottom right + vec3(1.0, 0.0, 1.0), // top right + vec3(-1.0, 0.0, 1.0) // top left + ); + +const int gridIndices[6] = int[6](0, 2, 1, 2, 0, 3); + +void main() +{ + int Index = gridIndices[gl_VertexID]; + vec3 position = gridPos[Index] * uGridSize; + + position.x += uCamPos.x; + position.z += uCamPos.z; + + gl_Position = uViewProjection * vec4(position, 1.0); + FragPos = position; +} + +#type fragment +/* + + Copyright 2024 Etay Meiri + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#version 420 +layout(location = 0) out vec4 FragColor; +in vec3 FragPos; + +uniform vec3 uMouseWorldPos; +uniform float uTime; +uniform vec3 uCamPos; +uniform float uGridSize = 100.0; +uniform float uGridMinPixelsBetweenCells = 2.0; +uniform float uGridCellSize = 0.025; +uniform vec4 uGridColorThin = vec4(0.5, 0.5, 0.5, 1.0); +uniform vec4 uGridColorThick = vec4(0.0, 0.0, 0.0, 1.0); + +float log10(float x) +{ + float f = log(x) / log(10.0); + return f; +} + +float satf(float x) +{ + float f = clamp(x, 0.0, 1.0); + return f; +} + +vec2 satv(vec2 x) +{ + vec2 v = clamp(x, vec2(0.0), vec2(1.0)); + return v; +} + +float max2(vec2 v) +{ + float f = max(v.x, v.y); + return f; +} + +float calculateLodAlpha(float cellSize, vec2 dudv) { + vec2 mod_div_dudv = mod(FragPos.xz, cellSize) / dudv; + return max2(vec2(1.0) - abs(satv(mod_div_dudv) * 2.0 - vec2(1.0))); +} + +void main() +{ + vec2 dvx = vec2(dFdx(FragPos.x), dFdy(FragPos.x)); + vec2 dvy = vec2(dFdx(FragPos.z), dFdy(FragPos.z)); + + float lx = length(dvx); + float ly = length(dvy); + + vec2 dudv = vec2(lx, ly); + + float l = length(dudv); + + float LOD = max(0.0, log10(l * uGridMinPixelsBetweenCells / uGridCellSize) + 1.0); + + float GridCellSizeLod0 = uGridCellSize * pow(10.0, floor(LOD)); + float GridCellSizeLod1 = GridCellSizeLod0 * 10.0; + float GridCellSizeLod2 = GridCellSizeLod1 * 10.0; + + dudv *= 4.0; + float Lod0a = calculateLodAlpha(GridCellSizeLod0, dudv); + float Lod1a = calculateLodAlpha(GridCellSizeLod1, dudv); + float Lod2a = calculateLodAlpha(GridCellSizeLod2, dudv); + + float LOD_fade = fract(LOD); + vec4 Color; + + if (Lod2a > 0.0) { + // Major grid lines with blue highlight + Color = mix(uGridColorThick, vec4(0.6, 0.7, 0.9, 0.7), 0.3); + Color.a *= Lod2a; + } else if (Lod1a > 0.0) { + // Medium grid lines with subtle color transition + Color = mix(uGridColorThick, uGridColorThin, LOD_fade); + Color.a *= Lod1a * 0.8; // Slightly more transparent + } else { + // Fine grid lines fade more with distance + Color = uGridColorThin; + Color.a *= (Lod0a * (1.0 - LOD_fade)) * 0.7; + } + + float distToMouse = length(FragPos.xz - uMouseWorldPos.xz); + + // Simple size pulse for the glow radius + float pulsePhase = uTime * 1.2; + float pulseSize = 1.0 + 0.3 * sin(pulsePhase); + float glowRadius = 8.0 * pulseSize; + + // Calculate how much the lines should glow based on distance + float glowFactor = 1.0 - smoothstep(0.0, glowRadius, distToMouse); + glowFactor = glowFactor * glowFactor; // Squared for smoother falloff + + // Colors for the glowing lines + vec3 glowColor1 = vec3(0.9, 0.3, 0.8); // Pink + vec3 glowColor2 = vec3(0.5, 0.3, 0.9); // Purple + float colorMix = 0.5 + 0.5 * sin(pulsePhase * 0.5); + vec3 finalGlowColor = mix(glowColor1, glowColor2, colorMix); + + float distance = length(FragPos.xz - uCamPos.xz); + float normalizedDist = distance / uGridSize; + + // More gradual falloff curve for distance + float edgeFadeStart = 0.65; // Start fading at 65% of grid size + float edgeFadeEnd = 0.92; // Complete fade by 92% of grid size + float distanceFactor = 1.0 - smoothstep(edgeFadeStart, edgeFadeEnd, normalizedDist); + + // Apply distance gradient to grid color (using an improved curve) + float fadeExponent = 1.5; // Controls how quickly the fade happens (higher = sharper edge) + float smoothFade = pow(distanceFactor, fadeExponent); + + // Apply the color gradient + vec3 nearColor = vec3(0.4, 0.45, 0.6); // Slightly blue tint for closer grid + vec3 farColor = vec3(0.3, 0.3, 0.5); // Subtle purple tint for distant grid + Color.rgb = mix(farColor, nearColor, smoothFade) * Color.rgb; + + // Apply the opacity falloff (more gradual, less abrupt) + Color.a *= smoothFade; + + // Apply the glow only to the grid lines - enhance their color and brightness + if (Lod2a > 0.1 || Lod1a > 0.1 || Lod0a > 0.1) { + // Scale the glow effect based on the distance fade + float distanceAdjustedGlow = glowFactor * smoothFade; + + // Only apply to actual grid lines, not the spaces between + Color.rgb = mix(Color.rgb, finalGlowColor, distanceAdjustedGlow * 0.8); + + // Brighten the lines (less intense at grid edges) + float brightnessFactor = distanceAdjustedGlow * 0.4; + Color.rgb = mix(Color.rgb, vec3(1.0), brightnessFactor * 0.4); + + // Make lines slightly more opaque when glowing (respects edge fade) + Color.a = mix(Color.a, min(1.0, Color.a * 1.2), distanceAdjustedGlow * 0.7); + } + FragColor = Color; +} From 478efb662faae759d6e73901c0c040db1b8da7e9 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 10:54:48 +0900 Subject: [PATCH 178/450] chore(document-windows): add source file --- engine/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 66a3e85b9..0b510fc41 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -11,6 +11,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) set(COMMON_SOURCES common/Exception.cpp common/math/Vector.cpp + common/math/Projection.cpp engine/src/Nexo.cpp engine/src/EntityFactory2D.cpp engine/src/EntityFactory3D.cpp From d1bea4498bc4eed48993e7bdf2e9dd86407644c7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 10:55:07 +0900 Subject: [PATCH 179/450] refactor(document-windows): changed default entities --- editor/src/DocumentWindows/EditorScene.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 15d3aa99e..3a7bf2412 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -61,7 +61,7 @@ namespace nexo::editor { framebufferSpecs.width = static_cast(m_viewSize.x); framebufferSpecs.height = static_cast(m_viewSize.y); const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs); - m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, 0.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget); + m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget); auto &cameraComponent = app.m_coordinator->getComponent(m_editorCamera); cameraComponent.render = true; app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_editorCamera)); @@ -82,16 +82,16 @@ namespace nexo::editor { scene::Scene &scene = app.getSceneManager().getScene(m_sceneId); const ecs::Entity ambientLight = LightFactory::createAmbientLight({0.5f, 0.5f, 0.5f}); scene.addEntity(ambientLight); - const ecs::Entity pointLight = LightFactory::createPointLight({1.2f, 5.0f, 0.1f}); + const ecs::Entity pointLight = LightFactory::createPointLight({2.0f, 5.0f, 0.0f}); utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); scene.addEntity(pointLight); const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.2f, -1.0f, -0.3f}); scene.addEntity(directionalLight); - const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, -2.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}); + const ecs::Entity spotLight = LightFactory::createSpotLight({-2.0f, 5.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}); utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); scene.addEntity(spotLight); - const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, -5.0f, -5.0f}, {20.0f, 1.0f, 20.0f}, - {0.0f, 0.0f, 0.0f}, {1.0f, 0.5f, 0.31f, 1.0f}); + const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, 0.25f, 0.0f}, {20.0f, 0.5f, 20.0f}, + {0.0f, 0.0f, 0.0f}, {0.05f * 1.7, 0.09f * 1.35, 0.13f * 1.45, 1.0f}); app.getSceneManager().getScene(m_sceneId).addEntity(basicCube); } From 0efee5c52f8654cf15394879c80c6e97ef614665 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 10:55:45 +0900 Subject: [PATCH 180/450] feat(document-windows): add getter for size --- engine/src/renderer/Framebuffer.hpp | 2 ++ engine/src/renderer/opengl/OpenGlFramebuffer.cpp | 5 +++++ engine/src/renderer/opengl/OpenGlFramebuffer.hpp | 2 ++ 3 files changed, 9 insertions(+) diff --git a/engine/src/renderer/Framebuffer.hpp b/engine/src/renderer/Framebuffer.hpp index 22b0e576a..e5df703b5 100644 --- a/engine/src/renderer/Framebuffer.hpp +++ b/engine/src/renderer/Framebuffer.hpp @@ -194,6 +194,8 @@ namespace nexo::renderer { */ virtual void resize(unsigned int width, unsigned int height) = 0; + virtual const glm::vec2 getSize() const = 0; + virtual void getPixelWrapper(unsigned int attachementIndex, int x, int y, void *result, const std::type_info &ti) const = 0; diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp index 2b8381cdd..a1f9c6838 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp @@ -313,6 +313,11 @@ namespace nexo::renderer { toResize = true; } + const glm::vec2 OpenGlFramebuffer::getSize() const + { + return glm::vec2(m_specs.width, m_specs.height); + } + void OpenGlFramebuffer::getPixelWrapper(unsigned int attachementIndex, int x, int y, void *result, const std::type_info &ti) const { // Add more types here when necessary diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp index 5223d4dcf..f725688ca 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp @@ -138,6 +138,8 @@ namespace nexo::renderer { */ void resize(unsigned int width, unsigned int height) override; + const glm::vec2 getSize() const override; + /** * @brief Reads a pixel value from a specified attachment. From 9be44114ba0cd367102c17d57967df3ac75567ae Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 10:56:11 +0900 Subject: [PATCH 181/450] feat(document-windows): load grid shader --- engine/src/renderer/Renderer3D.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index 20580ad73..11050134a 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -66,6 +66,8 @@ namespace nexo::renderer { "../resources/shaders/outline_pulse_transparent_flat.glsl").string()); auto albedoUnshadedTransparent = m_storage->shaderLibrary.load("Albedo unshaded transparent", Path::resolvePathRelativeToExe( "../resources/shaders/albedo_unshaded_transparent.glsl").string()); + m_storage->shaderLibrary.load("Grid shader", Path::resolvePathRelativeToExe( + "../resources/shaders/grid_shader.glsl").string()); phong->bind(); phong->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), Renderer3DStorage::maxTextureSlots); phong->unbind(); From 56f64d45bf32f6d316533db5e2f6e96a5bd172b3 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 10:56:28 +0900 Subject: [PATCH 182/450] feat(document-windows): add depth mask setter --- engine/src/renderer/RenderCommand.hpp | 10 ++++++++++ engine/src/renderer/RendererAPI.hpp | 3 +++ .../src/renderer/opengl/OpenGlRendererAPI.hpp | 3 +++ .../src/renderer/opengl/OpenGlRendererApi.cpp | 18 ++++++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/engine/src/renderer/RenderCommand.hpp b/engine/src/renderer/RenderCommand.hpp index ccc6cb58b..ced9e006a 100644 --- a/engine/src/renderer/RenderCommand.hpp +++ b/engine/src/renderer/RenderCommand.hpp @@ -122,11 +122,21 @@ namespace nexo::renderer { _rendererApi->drawIndexed(vertexArray, indexCount); } + static void drawUnIndexed(unsigned int verticesCount) + { + _rendererApi->drawUnIndexed(verticesCount); + } + static void setDepthTest(bool enable) { _rendererApi->setDepthTest(enable); } + static void setDepthMask(bool enable) + { + _rendererApi->setDepthMask(enable); + } + static void setDepthFunc(unsigned int func) { _rendererApi->setDepthFunc(func); diff --git a/engine/src/renderer/RendererAPI.hpp b/engine/src/renderer/RendererAPI.hpp index 4c5692fe8..bf0a5d7ed 100644 --- a/engine/src/renderer/RendererAPI.hpp +++ b/engine/src/renderer/RendererAPI.hpp @@ -113,6 +113,7 @@ namespace nexo::renderer { 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. @@ -127,6 +128,8 @@ namespace nexo::renderer { */ virtual void drawIndexed(const std::shared_ptr &vertexArray, unsigned int count = 0) = 0; + virtual void drawUnIndexed(unsigned int 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; diff --git a/engine/src/renderer/opengl/OpenGlRendererAPI.hpp b/engine/src/renderer/opengl/OpenGlRendererAPI.hpp index 418e5e199..7ffdb66fa 100644 --- a/engine/src/renderer/opengl/OpenGlRendererAPI.hpp +++ b/engine/src/renderer/opengl/OpenGlRendererAPI.hpp @@ -108,6 +108,7 @@ namespace nexo::renderer { void setDepthTest(bool enable) override; void setDepthFunc(unsigned int func) override; + void setDepthMask(bool enable) override; /** * @brief Renders indexed geometry using OpenGL. @@ -123,6 +124,8 @@ namespace nexo::renderer { */ void drawIndexed(const std::shared_ptr &vertexArray, unsigned int indexCount = 0) override; + void drawUnIndexed(unsigned int verticesCount) override; + void setStencilTest(bool enable) override; void setStencilMask(unsigned int mask) override; void setStencilFunc(unsigned int func, int ref, unsigned int mask) override; diff --git a/engine/src/renderer/opengl/OpenGlRendererApi.cpp b/engine/src/renderer/opengl/OpenGlRendererApi.cpp index ed023300c..18776ce9c 100644 --- a/engine/src/renderer/opengl/OpenGlRendererApi.cpp +++ b/engine/src/renderer/opengl/OpenGlRendererApi.cpp @@ -28,6 +28,7 @@ namespace nexo::renderer { glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 0, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); @@ -97,6 +98,16 @@ namespace nexo::renderer { glDepthFunc(func); } + void OpenGlRendererApi::setDepthMask(bool enable) + { + if (!m_initialized) + THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + if (enable) + glDepthMask(GL_TRUE); + else + glDepthMask(GL_FALSE); + } + void OpenGlRendererApi::drawIndexed(const std::shared_ptr &vertexArray, const unsigned int indexCount) { if (!m_initialized) @@ -107,6 +118,13 @@ namespace nexo::renderer { glDrawElements(GL_TRIANGLES, static_cast(count), GL_UNSIGNED_INT, nullptr); } + void OpenGlRendererApi::drawUnIndexed(unsigned int verticesCount) + { + if (!m_initialized) + THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + glDrawArrays(GL_TRIANGLES, 0, verticesCount); + } + void OpenGlRendererApi::setStencilTest(bool enable) { if (!m_initialized) From 6c41fc9a6620157c77fb22b099e17ec7f503e3dd Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 12:23:47 +0900 Subject: [PATCH 183/450] feat(document-windows): now possible to edit grid params --- editor/src/DocumentWindows/EditorScene.cpp | 64 +++++++++++++++++++++- editor/src/DocumentWindows/EditorScene.hpp | 6 +- engine/src/components/RenderContext.hpp | 20 +++++-- engine/src/systems/RenderSystem.cpp | 9 +-- resources/shaders/grid_shader.glsl | 2 +- 5 files changed, 85 insertions(+), 16 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 3a7bf2412..4b7cc5fb9 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -27,6 +27,7 @@ #include "Texture.hpp" #include "WindowRegistry.hpp" #include "components/Camera.hpp" +#include "components/RenderContext.hpp" #include "components/Uuid.hpp" #include "components/Editor.hpp" #include "math/Matrix.hpp" @@ -140,13 +141,15 @@ namespace nexo::editor { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 0)); } - bool EditorScene::renderToolbarButton(const std::string &uniqueId, const std::string &icon, const std::string &tooltip, const std::vector & gradientStop) + bool EditorScene::renderToolbarButton(const std::string &uniqueId, const std::string &icon, const std::string &tooltip, const std::vector & gradientStop, bool *rightClicked) { constexpr float buttonWidth = 35.0f; constexpr float buttonHeight = 35.0f; 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); return clicked; } @@ -268,6 +271,51 @@ namespace nexo::editor { } } + void EditorScene::gridSettingsPopup() + { + if (m_popupManager.showPopupModal("Grid settings")) + { + auto &app = getApp(); + components::RenderContext::GridParams &gridSettings = + app.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); + ImNexo::RowDragFloat1("Grid size", "", &gridSettings.gridSize, 50.0f, 150.0f); + 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"); + ImNexo::RowDragFloat1("Cell size", "", &gridSettings.cellSize, 0.1f, 20.0f, 0.02f); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("The size of the internal cells"); + ImGui::EndTable(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + + float buttonWidth = 120.0f; + float windowWidth = ImGui::GetWindowSize().x; + ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); + + if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) + { + m_popupManager.closePopupInContext(); + } + ImGui::Unindent(10.0f); + ImGui::PopStyleVar(); + m_popupManager.closePopup(); + } + } + void EditorScene::renderEditorCameraToolbarButton() { auto &app = getApp(); @@ -308,6 +356,7 @@ namespace nexo::editor { void EditorScene::renderToolbar() { + auto &app = getApp(); constexpr float buttonWidth = 35.0f; constexpr float buttonHeight = 35.0f; constexpr ImVec2 buttonSize{buttonWidth, buttonHeight}; @@ -400,10 +449,15 @@ namespace nexo::editor { ImGui::SameLine(); // -------- Grid enabled button -------- - if (renderToolbarButton("grid_enabled", ICON_FA_TH_LARGE, "Enable / Disable grid", m_gridEnabled ? m_selectedGradient : m_buttonGradient)) + bool rightClicked = false; + components::RenderContext::GridParams &gridParams = app.m_coordinator->getSingletonComponent().gridParams; + if (renderToolbarButton("grid_enabled", ICON_FA_TH_LARGE, "Enable / Disable grid", gridParams.enabled ? m_selectedGradient : m_buttonGradient, &rightClicked)) { - m_gridEnabled = !m_gridEnabled; + gridParams.enabled = !gridParams.enabled; + } + if (rightClicked) + m_popupManager.openPopup("Grid settings", ImVec2(300, 180)); ImGui::SameLine(); @@ -411,6 +465,7 @@ namespace nexo::editor { if (renderToolbarButton("snap_to_grid", ICON_FA_TH, "Enable snapping to grid", m_snapToGrid ? m_selectedGradient : m_buttonGradient)) { m_snapToGrid = !m_snapToGrid; + } ImGui::SameLine(); @@ -458,6 +513,9 @@ namespace nexo::editor { // -------- Snap settings popup -------- snapSettingsPopup(); + // -------- Grid settings popup -------- + gridSettingsPopup(); + // IMPORTANT: Restore original cursor position so we don't affect layout ImGui::SetCursorPos(originalCursorPos); } diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp index 3c6e6811c..6d68541fc 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene.hpp @@ -106,7 +106,6 @@ namespace nexo::editor { glm::vec3 m_snapTranslate = {10.0f, 10.0f, 10.0f}; bool m_snapRotateOn = false; float m_angleSnap = 90.0f; - bool m_gridEnabled = false; bool m_snapToGrid = false; bool m_wireframeEnabled = false; @@ -237,6 +236,8 @@ namespace nexo::editor { */ void snapSettingsPopup(); + void gridSettingsPopup(); + /** * @brief Renders a standard toolbar button with optional tooltip and styling. * @@ -252,7 +253,8 @@ namespace nexo::editor { const std::string &uniqueId, const std::string &icon, const std::string &tooltip, - const std::vector & gradientStop + const std::vector & gradientStop, + bool *rightClicked = nullptr ); /** diff --git a/engine/src/components/RenderContext.hpp b/engine/src/components/RenderContext.hpp index f5d2bc371..2dc18e754 100644 --- a/engine/src/components/RenderContext.hpp +++ b/engine/src/components/RenderContext.hpp @@ -20,12 +20,20 @@ #include "Light.hpp" namespace nexo::components { - struct RenderContext { - int sceneRendered = -1; - SceneType sceneType; - renderer::Renderer3D renderer3D; - std::queue cameras; - LightContext sceneLights; + struct RenderContext { + int sceneRendered = -1; + SceneType sceneType; + struct GridParams { + bool enabled = true; + float gridSize = 100.0f; + float minPixelsBetweenCells = 2.0f; + float cellSize = 0.025f; + }; + GridParams gridParams; + renderer::Renderer3D renderer3D; + std::queue cameras; + LightContext sceneLights; + RenderContext() { diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 39b0fb5a7..64adef13d 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -106,9 +106,10 @@ namespace nexo::system { gridShader->bind(); // Grid appearance - gridShader->setUniformFloat("uGridSize", 100.0f); // Size of grid from center to edge - gridShader->setUniformFloat("uGridCellSize", 0.025f); // Base size of each cell - gridShader->setUniformFloat("uGridMinPixelsBetweenCells", 2.0f); // For LOD calculation + const components::RenderContext::GridParams &gridParams = renderContext.gridParams; + gridShader->setUniformFloat("uGridSize", gridParams.gridSize); // Size of grid from center to edge + gridShader->setUniformFloat("uGridCellSize", gridParams.cellSize); // Base size of each cell + gridShader->setUniformFloat("uGridMinPixelsBetweenCells", gridParams.minPixelsBetweenCells); // For LOD calculation gridShader->setUniformFloat4("uGridColorThin", {0.5f, 0.55f, 0.7f, 0.6f}); // Soft blue-purple gridShader->setUniformFloat4("uGridColorThick", {0.7f, 0.75f, 0.9f, 0.8f}); // Lighter blue-purple @@ -224,7 +225,7 @@ namespace nexo::system { } } - if (sceneType == SceneType::EDITOR) + if (sceneType == SceneType::EDITOR && renderContext.gridParams.enabled) { renderGrid(camera, renderContext); } diff --git a/resources/shaders/grid_shader.glsl b/resources/shaders/grid_shader.glsl index 701fa04c8..21e2a15ed 100644 --- a/resources/shaders/grid_shader.glsl +++ b/resources/shaders/grid_shader.glsl @@ -19,7 +19,7 @@ #version 430 uniform mat4 uViewProjection = mat4(1.0); -uniform float uGridSize = 100.0; +uniform float uGridSize; uniform vec3 uCamPos; out vec3 FragPos; From 5ca0d63bf1975d000fe31bee8e08d82809e847fe Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 13:42:54 +0900 Subject: [PATCH 184/450] feat(document-windows): add util function to retrieve the lat guizmo op --- editor/src/DocumentWindows/EditorScene.cpp | 34 ++++++++++++---------- editor/src/DocumentWindows/EditorScene.hpp | 2 ++ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 4b7cc5fb9..c5ef3a12d 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -462,10 +462,10 @@ namespace nexo::editor { ImGui::SameLine(); // -------- Snap to gridbutton -------- - if (renderToolbarButton("snap_to_grid", ICON_FA_TH, "Enable snapping to grid", m_snapToGrid ? m_selectedGradient : m_buttonGradient)) + // NOTE: This seems complicated to implement using ImGuizmo, we leave it for now but i dont know if it will be implemented + 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_snapToGrid; - } ImGui::SameLine(); @@ -520,6 +520,20 @@ namespace nexo::editor { ImGui::SetCursorPos(originalCursorPos); } + ImGuizmo::OPERATION EditorScene::getLastGuizmoOperation() + { + for (int bitPos = 0; bitPos <= 13; bitPos++) + { + // Create a mask for this bit position + ImGuizmo::OPERATION op = static_cast(1u << bitPos); + + // Check if this bit is set + if (ImGuizmo::IsOver(op)) + return op; + } + return ImGuizmo::OPERATION::UNIVERSAL; + } + void EditorScene::renderGizmo() { const auto &coord = nexo::Application::m_coordinator; @@ -546,22 +560,12 @@ namespace nexo::editor { static ImGuizmo::OPERATION lastOperation; if (!ImGuizmo::IsUsing()) - { - if (ImGuizmo::IsOver(ImGuizmo::OPERATION::TRANSLATE)) - { - lastOperation = ImGuizmo::OPERATION::TRANSLATE; - } - else if (ImGuizmo::IsOver(ImGuizmo::OPERATION::ROTATE)) - { - lastOperation = ImGuizmo::OPERATION::ROTATE; - } - } - + lastOperation = getLastGuizmoOperation(); float *snap = nullptr; - if (m_snapTranslateOn && lastOperation == ImGuizmo::OPERATION::TRANSLATE) { + if (m_snapTranslateOn && lastOperation & ImGuizmo::OPERATION::TRANSLATE) { snap = &m_snapTranslate.x; - } else if (m_snapRotateOn && lastOperation == ImGuizmo::OPERATION::ROTATE) { + } else if (m_snapRotateOn && lastOperation & ImGuizmo::OPERATION::ROTATE) { snap = &m_angleSnap; } diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp index 6d68541fc..cd94b5243 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene.hpp @@ -257,6 +257,8 @@ namespace nexo::editor { bool *rightClicked = nullptr ); + ImGuizmo::OPERATION getLastGuizmoOperation(); + /** * @brief Renders the transformation gizmo for the selected entity. * From 5fbd0e1bcd035b4bad75a9ee00ef3a23419890b2 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 20 Apr 2025 13:43:08 +0900 Subject: [PATCH 185/450] fix(document-windows): fix camera test --- tests/engine/components/Camera.test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/engine/components/Camera.test.cpp b/tests/engine/components/Camera.test.cpp index 246a3b547..d91bacd10 100644 --- a/tests/engine/components/Camera.test.cpp +++ b/tests/engine/components/Camera.test.cpp @@ -28,6 +28,7 @@ class DummyFramebuffer : public nexo::renderer::Framebuffer { void unbind() override {} void setClearColor(const glm::vec4 &) override {} unsigned int getFramebufferId() const override { return 0; } + const glm::vec2 getSize() const override { return glm::vec2(0.0f); } void resize(unsigned int, unsigned int ) override {} void getPixelWrapper(unsigned int, int, int, void *, const std::type_info &) const override {} void clearAttachmentWrapper(unsigned int, const void *, const std::type_info &) const override {} From 872e3e53b312672ea504676d2a59668acb835e51 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 22 Apr 2025 22:56:44 +0900 Subject: [PATCH 186/450] feat(document-windows): add flat color shader --- resources/shaders/flat_color.glsl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 resources/shaders/flat_color.glsl diff --git a/resources/shaders/flat_color.glsl b/resources/shaders/flat_color.glsl new file mode 100644 index 000000000..01d65ebc5 --- /dev/null +++ b/resources/shaders/flat_color.glsl @@ -0,0 +1,21 @@ +#type vertex +#version 430 core +layout(location = 0) in vec3 aPos; + +uniform mat4 uViewProjection; +uniform mat4 uMatModel; + +void main() +{ + vec4 worldPos = uMatModel * vec4(aPos, 1.0); + gl_Position = uViewProjection * worldPos; +} + +#type fragment +#version 430 core +layout(location = 0) out vec4 FragColor; + +void main() +{ + FragColor = vec4(1.0, 1.0, 1.0, 1.0); +} From 5fa509330de10d23fe09d5ab25b0dd249c021c90 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 22 Apr 2025 22:57:25 +0900 Subject: [PATCH 187/450] feat(document-windows): outline shader is now a post processing shader implemented concentric ring sampling to detect edges --- resources/shaders/outline_pulse_flat.glsl | 75 +++++++++++++++++++---- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/resources/shaders/outline_pulse_flat.glsl b/resources/shaders/outline_pulse_flat.glsl index 2f5eacfa2..53257729b 100644 --- a/resources/shaders/outline_pulse_flat.glsl +++ b/resources/shaders/outline_pulse_flat.glsl @@ -1,35 +1,85 @@ #type vertex #version 430 core -layout(location = 0) in vec3 aPos; +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec2 aTexCoord; -uniform mat4 uViewProjection; -uniform mat4 uMatModel; +out vec2 vTexCoord; void main() { - vec4 worldPos = uMatModel * vec4(aPos, 1.0); - - gl_Position = uViewProjection * worldPos; + gl_Position = vec4(aPosition, 1.0); + vTexCoord = aTexCoord; } #type fragment #version 430 core layout(location = 0) out vec4 FragColor; +in vec2 vTexCoord; + +uniform sampler2D uMaskTexture; +uniform vec2 uScreenSize; uniform float uTime; +uniform float uOutlineWidth = 5.0; void main() { + float mask = texture(uMaskTexture, vTexCoord).r; + // Exit if inside the mask + if (mask > 0.5) { + FragColor = vec4(0.0, 0.0, 0.0, 0.0); + return; + } + + float minDist = uOutlineWidth * uOutlineWidth; + const int MAX_SAMPLES = 5; + const float MAX_DIST = uOutlineWidth; + + // Concentric ring sampling + for (int i = 1; i <= MAX_SAMPLES && minDist > 1.0; i++) { + float angle_step = 3.14159 * 0.25; // 45 degrees + float radius = float(i) / float(MAX_SAMPLES) * MAX_DIST; + + for (float angle = 0.0; angle < 6.28318; angle += angle_step) { + float x = cos(angle) * radius; + float y = sin(angle) * radius; + + // Convert to texture space (textures coordinates range from 0 to 1, so dividing pixel coords by screen size does that) + vec2 offset = vec2(x / uScreenSize.x, y / uScreenSize.y); + float sampleMask = texture(uMaskTexture, vTexCoord + offset).r; + + if (sampleMask > 0.5) { + float dist = dot(vec2(x, y), vec2(x, y)); + minDist = min(minDist, dist); + break; + } + } + } + + // Calculate outline alpha based on distance + float alpha = 0.0; + const float solid = 1.0; // Solid edge width + const float fuzzy = uOutlineWidth - solid; // Fuzzy part, transparent part of the outline + + if (minDist <= uOutlineWidth * uOutlineWidth) { + // if sqrt(minDist) <= solid, we get a negative value clamped to 0.0 -> 1.0 - 0.0, full solid part + // else, the alpha value gets smoothed along the fuzzy part (further = less opaque) + alpha = 1.0 - clamp((sqrt(minDist) - solid) / fuzzy, 0.0, 1.0); + } else { + // If not close enough to mask, discard + discard; + } + vec4 purpleColor = vec4(0.5, 0.0, 1.0, 1.0); vec4 blueColor = vec4(0.0, 0.4, 0.9, 1.0); - - // Color shifting effect + // 0.5Hz oscillation between -1 and 1 then remapped to 0 and 1 float colorShift = (sin(uTime * 3.0) * 0.5 + 0.5); vec4 baseColor = mix(purpleColor, blueColor, colorShift); - // Contrast pulsation - float contrastPulse = (sin(uTime * 2.3) * 0.5 + 0.5) * 0.5 + 0.5; // Range from 0.5 to 1.0 + // 0.365Hz oscillation between -1 and 1 then remapped to 0.5 and 1 + float contrastPulse = (sin(uTime * 2.3) * 0.5 + 0.5) * 0.5 + 0.5; + // Increase the contrast by this pulse vec4 highContrastColor = vec4( pow(baseColor.r, contrastPulse), pow(baseColor.g, contrastPulse), @@ -37,8 +87,9 @@ void main() 1.0 ); - // Add subtle brightness pulsation for a glowing effect - float brightnessPulse = sin(uTime * 4.0) * 0.15 + 0.85; // Range from 0.7 to 1.0 + // Increase the brightness by a 0.63Hz pulse,remap to 0.7 and 1.0 + float brightnessPulse = sin(uTime * 4.0) * 0.15 + 0.85; FragColor = highContrastColor * brightnessPulse; + FragColor.a = alpha; } From 6653400a34c00ffb9a901e433f229d93376afc6e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 22 Apr 2025 22:58:06 +0900 Subject: [PATCH 188/450] refactor(document-windows): outline is now rendering via post processing edge detecte combined with mask framebuffer --- engine/src/systems/RenderSystem.cpp | 146 ++++++++++++++++++++-------- engine/src/systems/RenderSystem.hpp | 13 ++- 2 files changed, 116 insertions(+), 43 deletions(-) diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 64adef13d..fcca796d5 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -33,6 +33,41 @@ namespace nexo::system { + RenderSystem::RenderSystem() + { + renderer::FramebufferSpecs maskFramebufferSpecs; + maskFramebufferSpecs.attachments = { renderer::FrameBufferTextureFormats::RGBA8 }; + maskFramebufferSpecs.width = 1280; // Default size, will be resized as needed + maskFramebufferSpecs.height = 720; + m_maskFramebuffer = renderer::Framebuffer::create(maskFramebufferSpecs); + + // Create fullscreen quad for post-processing + m_fullscreenQuad = renderer::createVertexArray(); + + // Define fullscreen quad vertices (position and texture coordinates) + float quadVertices[] = { + // positions // texture coords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f + }; + + auto quadVB = renderer::createVertexBuffer(sizeof(quadVertices)); + quadVB->setData(quadVertices, sizeof(quadVertices)); + + renderer::BufferLayout quadLayout = { + {renderer::ShaderDataType::FLOAT3, "aPosition"}, + {renderer::ShaderDataType::FLOAT2, "aTexCoord"} + }; + + quadVB->setLayout(quadLayout); + m_fullscreenQuad->addVertexBuffer(quadVB); + } + /** * @brief Sets up the lighting uniforms in the given shader. * @@ -129,9 +164,70 @@ namespace nexo::system { gridShader->setUniformFloat("uTime", static_cast(glfwGetTime())); renderer::RenderCommand::setDepthMask(false); + glDisable(GL_CULL_FACE); renderer::RenderCommand::drawUnIndexed(6); gridShader->unbind(); renderer::RenderCommand::setDepthMask(true); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + } + + void RenderSystem::renderOutline( + components::RenderContext &renderContext, + const components::CameraContext &camera, + const components::RenderComponent &renderComponent, + const components::TransformComponent &transformComponent + ) { + if (m_maskFramebuffer->getSize().x != camera.renderTarget->getSize().x || + m_maskFramebuffer->getSize().y != camera.renderTarget->getSize().y) { + m_maskFramebuffer->resize( + static_cast(camera.renderTarget->getSize().x), + static_cast(camera.renderTarget->getSize().y) + ); + } + + // Step 2: Render selected object to mask texture + m_maskFramebuffer->bind(); + renderer::RenderCommand::setClearColor({0.0f, 0.0f, 0.0f, 0.0f}); + renderer::RenderCommand::clear(); + const auto &material = std::dynamic_pointer_cast(renderComponent.renderable)->material; + + + std::string maskShaderName = "Flat color"; + if (!material.isOpaque) + maskShaderName = "Albedo unshaded transparent"; + + renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, maskShaderName); + auto context = std::make_shared(); + context->renderer3D = renderContext.renderer3D; + renderComponent.draw(context, transformComponent); + renderContext.renderer3D.endScene(); + + + m_maskFramebuffer->unbind(); + if (camera.renderTarget != nullptr) + camera.renderTarget->bind(); + + // Step 3: Draw full-screen quad with outline post-process shader + renderer::RenderCommand::setDepthMask(false); + renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, "Outline pulse flat"); + auto outlineShader = renderContext.renderer3D.getShader(); + outlineShader->bind(); + unsigned int maskTexture = m_maskFramebuffer->getColorAttachmentId(0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, maskTexture); + outlineShader->setUniformInt("uMaskTexture", 0); + outlineShader->setUniformFloat("uTime", static_cast(glfwGetTime())); + outlineShader->setUniformFloat2("uScreenSize", {camera.renderTarget->getSize().x, camera.renderTarget->getSize().y}); + outlineShader->setUniformFloat("uOutlineWidth", 10.0f); + + m_fullscreenQuad->bind(); + renderer::RenderCommand::drawUnIndexed(6); + m_fullscreenQuad->unbind(); + renderContext.renderer3D.endScene(); + + outlineShader->unbind(); + renderer::RenderCommand::setDepthMask(true); } void RenderSystem::update() @@ -156,11 +252,9 @@ namespace nexo::system { const auto renderSpan = get(); const std::span entitySpan = m_group->entities(); - while (!renderContext.cameras.empty()) - { + while (!renderContext.cameras.empty()) { const components::CameraContext &camera = renderContext.cameras.front(); - if (camera.renderTarget != nullptr) - { + if (camera.renderTarget != nullptr) { camera.renderTarget->bind(); //TODO: Find a way to automatically clear color attachments renderer::RenderCommand::setClearColor(camera.clearColor); @@ -176,8 +270,10 @@ namespace nexo::system { } nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneName)); - for (size_t i = partition->startIndex; i < partition->startIndex + partition->count; ++i) - { + if (sceneType == SceneType::EDITOR && renderContext.gridParams.enabled) + renderGrid(camera, renderContext); + + 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; @@ -187,13 +283,6 @@ namespace nexo::system { const auto &material = std::dynamic_pointer_cast(render.renderable)->material; if (render.isRendered) { - bool isSelected = coord->entityHasComponent(entity); - if (isSelected) - { - renderer::RenderCommand::setStencilFunc(GL_ALWAYS, 1, 0xFF); - renderer::RenderCommand::setStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - renderer::RenderCommand::setStencilMask(0xFF); - } renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, material.shader); auto shader = renderContext.renderer3D.getShader(); setupLights(shader, renderContext.sceneLights); @@ -201,40 +290,13 @@ namespace nexo::system { context->renderer3D = renderContext.renderer3D; render.draw(context, transform, static_cast(entity)); renderContext.renderer3D.endScene(); - if (isSelected) - { - renderer::RenderCommand::setStencilFunc(GL_NOTEQUAL, 1, 0xFF); - renderer::RenderCommand::setStencilMask(0x00); - renderer::RenderCommand::setDepthFunc(GL_LEQUAL); - float scaleFactor = 1.015f; - if (material.isOpaque) - renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, "Outline pulse flat"); - else { - renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, "Outline pulse transparent flat"); - scaleFactor = 1.1f; //Sligthly larger for transparency - } - auto outlineShader = renderContext.renderer3D.getShader(); - outlineShader->setUniformFloat("uTime", static_cast(glfwGetTime())); - render.draw(context, {transform.pos, transform.size * scaleFactor, transform.quat}); - renderContext.renderer3D.endScene(); - renderer::RenderCommand::setDepthFunc(GL_LESS); - renderer::RenderCommand::setStencilFunc(GL_ALWAYS, 0, 0xFF); - renderer::RenderCommand::setStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - renderer::RenderCommand::setStencilMask(0xFF); - } + if (coord->entityHasComponent(entity)) + renderOutline(renderContext, camera, render, transform); } } - if (sceneType == SceneType::EDITOR && renderContext.gridParams.enabled) - { - renderGrid(camera, renderContext); - } - if (camera.renderTarget != nullptr) - { camera.renderTarget->unbind(); - - } renderContext.cameras.pop(); } // We have to do this for now to reset the shader stored as a static here, this will change later diff --git a/engine/src/systems/RenderSystem.hpp b/engine/src/systems/RenderSystem.hpp index 1a015d32f..42497c11e 100644 --- a/engine/src/systems/RenderSystem.hpp +++ b/engine/src/systems/RenderSystem.hpp @@ -15,6 +15,7 @@ #include "Access.hpp" #include "GroupSystem.hpp" +#include "components/Camera.hpp" #include "components/Render.hpp" #include "components/RenderContext.hpp" #include "components/SceneComponents.hpp" @@ -47,9 +48,19 @@ namespace nexo::system { ecs::Read>, ecs::WriteSingleton> { public: - void update(); + RenderSystem(); + void update(); + private: void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext); void renderGrid(const components::CameraContext &camera, components::RenderContext &renderContext); + void renderOutline( + components::RenderContext &renderContext, + const components::CameraContext &camera, + const components::RenderComponent &renderComponent, + const components::TransformComponent &transformComponent + ); + std::shared_ptr m_fullscreenQuad; + std::shared_ptr m_maskFramebuffer; }; } From 32165563ed93752ea5c71e217dc97a5216e485b6 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 22 Apr 2025 22:58:36 +0900 Subject: [PATCH 189/450] feat(document-windows): add uniform for float2 and bool --- engine/src/renderer/Shader.hpp | 2 ++ engine/src/renderer/opengl/OpenGlShader.cpp | 20 ++++++++++++++++++++ engine/src/renderer/opengl/OpenGlShader.hpp | 2 ++ 3 files changed, 24 insertions(+) diff --git a/engine/src/renderer/Shader.hpp b/engine/src/renderer/Shader.hpp index 62812da54..c494e662b 100644 --- a/engine/src/renderer/Shader.hpp +++ b/engine/src/renderer/Shader.hpp @@ -130,9 +130,11 @@ namespace nexo::renderer { virtual void unbind() const = 0; virtual bool setUniformFloat(const std::string &name, float value) const = 0; + virtual bool setUniformFloat2(const std::string &name, const glm::vec2 &values) const = 0; virtual bool setUniformFloat3(const std::string &name, const glm::vec3 &values) const = 0; virtual bool setUniformFloat4(const std::string &name, const glm::vec4 &values) const = 0; virtual bool setUniformMatrix(const std::string &name, const glm::mat4 &matrix) const = 0; + virtual bool setUniformBool(const std::string &name, bool value) const = 0; virtual bool setUniformInt(const std::string &name, int value) const = 0; virtual bool setUniformIntArray(const std::string &name, const int *values, unsigned int count) const = 0; diff --git a/engine/src/renderer/opengl/OpenGlShader.cpp b/engine/src/renderer/opengl/OpenGlShader.cpp index 2ae905162..de6795421 100644 --- a/engine/src/renderer/opengl/OpenGlShader.cpp +++ b/engine/src/renderer/opengl/OpenGlShader.cpp @@ -220,6 +220,16 @@ namespace nexo::renderer { return true; } + bool OpenGlShader::setUniformFloat2(const std::string &name, const glm::vec2 &values) const + { + const int loc = glGetUniformLocation(m_id, name.c_str()); + if (loc == -1) + return false; + + glUniform2f(loc, values.x, values.y); + return true; + } + bool OpenGlShader::setUniformFloat3(const std::string &name, const glm::vec3 &values) const { const int loc = glGetUniformLocation(m_id, name.c_str()); @@ -290,6 +300,16 @@ namespace nexo::renderer { return true; } + bool OpenGlShader::setUniformBool(const std::string &name, bool value) const + { + const int loc = glGetUniformLocation(m_id, name.c_str()); + if (loc == -1) + return false; + + glUniform1i(loc, value); + return true; + } + bool OpenGlShader::setUniformInt(const ShaderUniforms uniform, const int value) const { const int loc = m_uniformLocations.at(uniform); diff --git a/engine/src/renderer/opengl/OpenGlShader.hpp b/engine/src/renderer/opengl/OpenGlShader.hpp index fc60bb81d..7ff93e6a0 100644 --- a/engine/src/renderer/opengl/OpenGlShader.hpp +++ b/engine/src/renderer/opengl/OpenGlShader.hpp @@ -58,9 +58,11 @@ namespace nexo::renderer { void unbind() const override; bool setUniformFloat(const std::string &name, const 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; From 4782642fd56154d2c6f796da987656284b022204 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 22 Apr 2025 22:58:55 +0900 Subject: [PATCH 190/450] feat(document-windows): add flat color shader loadd --- engine/src/renderer/Renderer3D.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index 11050134a..ffed09b30 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -68,6 +68,8 @@ namespace nexo::renderer { "../resources/shaders/albedo_unshaded_transparent.glsl").string()); m_storage->shaderLibrary.load("Grid shader", Path::resolvePathRelativeToExe( "../resources/shaders/grid_shader.glsl").string()); + m_storage->shaderLibrary.load("Flat color", Path::resolvePathRelativeToExe( + "../resources/shaders/flat_color.glsl").string()); phong->bind(); phong->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), Renderer3DStorage::maxTextureSlots); phong->unbind(); From e135be46d1abd6ed55abdcc98ff56cd00f45f064 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 23 Apr 2025 23:44:58 +0900 Subject: [PATCH 191/450] feat(document-windows): add dummy shortcuts --- editor/src/DocumentWindows/EditorScene.cpp | 54 ++++++++++++++++++++-- editor/src/DocumentWindows/EditorScene.hpp | 11 +++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index c5ef3a12d..85c470396 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -48,6 +48,55 @@ namespace nexo::editor { { setupWindow(); setupScene(); + + m_globalState = {static_cast(EditorState::GLOBAL)}; + m_globalState.registerCommand( + { + "Ctrl Context", + "Ctrl", + []{ + //std::cout << "Ctrl pressed" << std::endl; + }, + true, + { + { + "Copy", + "C", + []{ + std::cout << "Copy command executed" << std::endl; + }, + false, + }, + { + "Paste", + "V", + []{ + std::cout << "Paste command executed" << std::endl; + }, + false, + }, + { + "Sub child", + "Shift", + nullptr, + true, + { + { + "Delete", + "X", + []{ + std::cout << "Sub sub command executed" << std::endl; + }, + false, + } + } + } + } + } + ); + + m_windowState = m_globalState; + } void EditorScene::setupScene() @@ -640,9 +689,8 @@ namespace nexo::editor { ImGui::Dummy(ImVec2(0, 5)); m_viewPosition = ImGui::GetCursorScreenPos(); - - m_focused = ImGui::IsWindowFocused(); - m_hovered = ImGui::IsWindowHovered(); + m_focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); + m_hovered = ImGui::IsWindowHovered(ImGuiFocusedFlags_RootAndChildWindows); app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused); if (m_focused && selector.getSelectedScene() != m_sceneId) { diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp index cd94b5243..ff3e08ff7 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene.hpp @@ -14,12 +14,15 @@ #pragma once #include "ADocumentWindow.hpp" +#include "inputs/WindowState.hpp" #include "IDocumentWindow.hpp" #include "core/scene/SceneManager.hpp" +#include "inputs/WindowState.hpp" #include "PopupManager.hpp" #include #include #include "ImNexo/Widgets.hpp" +#include "inputs/InputManager.hpp" namespace nexo::editor { @@ -279,5 +282,13 @@ namespace nexo::editor { * rendered scene, and updates viewport bounds for input handling. */ void renderView(); + + enum class EditorState { + GLOBAL, + GIZMO, + NB_STATE + }; + + WindowState m_globalState; }; } From 7f5263fbcab5879ef96061b314de1658aaf95c83 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 23 Apr 2025 23:45:14 +0900 Subject: [PATCH 192/450] feat(document-windows): add input handling --- editor/src/inputs/Command.cpp | 170 +++++++++++++++++++++++++ editor/src/inputs/Command.hpp | 52 ++++++++ editor/src/inputs/InputManager.cpp | 197 +++++++++++++++++++++++++++++ editor/src/inputs/InputManager.hpp | 59 +++++++++ editor/src/inputs/WindowState.cpp | 32 +++++ editor/src/inputs/WindowState.hpp | 34 +++++ 6 files changed, 544 insertions(+) create mode 100644 editor/src/inputs/Command.cpp create mode 100644 editor/src/inputs/Command.hpp create mode 100644 editor/src/inputs/InputManager.cpp create mode 100644 editor/src/inputs/InputManager.hpp create mode 100644 editor/src/inputs/WindowState.cpp create mode 100644 editor/src/inputs/WindowState.hpp diff --git a/editor/src/inputs/Command.cpp b/editor/src/inputs/Command.cpp new file mode 100644 index 000000000..706070ed4 --- /dev/null +++ b/editor/src/inputs/Command.cpp @@ -0,0 +1,170 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// 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: 23/05/2025 +// Description: Source file for the input command +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Command.hpp" +#include +#include +#include +#include +#include +#include + +namespace nexo::editor { + Command::Command( + const std::string &description, + const std::string &key, + const std::function &callback, + bool isModifier, + const std::vector &children) + : m_description(description), m_key(key), m_callback(callback), m_isModifier(isModifier), m_childrens(children) + { + // Create a mapping of key names to ImGuiKey values + static const std::unordered_map keyMap = { + // Common modifiers + {"ctrl", ImGuiKey_LeftCtrl}, + {"control", ImGuiKey_LeftCtrl}, + {"shift", ImGuiKey_LeftShift}, + {"alt", ImGuiKey_LeftAlt}, + {"super", ImGuiKey_LeftSuper}, + {"cmd", ImGuiKey_LeftSuper}, + {"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}, + + // 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}, + + // 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}, + + // Special keys + {"space", ImGuiKey_Space}, + {"enter", ImGuiKey_Enter}, + {"return", ImGuiKey_Enter}, + {"escape", ImGuiKey_Escape}, + {"esc", ImGuiKey_Escape}, + {"tab", ImGuiKey_Tab}, + {"backspace", ImGuiKey_Backspace}, + {"delete", ImGuiKey_Delete}, + {"insert", ImGuiKey_Insert}, + {"home", ImGuiKey_Home}, + {"end", ImGuiKey_End}, + {"pageup", ImGuiKey_PageUp}, + {"pagedown", ImGuiKey_PageDown}, + {"up", ImGuiKey_UpArrow}, + {"down", ImGuiKey_DownArrow}, + {"left", ImGuiKey_LeftArrow}, + {"right", ImGuiKey_RightArrow}, + {"capslock", ImGuiKey_CapsLock}, + {"numlock", ImGuiKey_NumLock}, + {"printscreen", ImGuiKey_PrintScreen}, + {"pause", ImGuiKey_Pause}, + + // Keypad + {"keypad0", ImGuiKey_Keypad0}, + {"keypad1", ImGuiKey_Keypad1}, + {"keypad2", ImGuiKey_Keypad2}, + {"keypad3", ImGuiKey_Keypad3}, + {"keypad4", ImGuiKey_Keypad4}, + {"keypad5", ImGuiKey_Keypad5}, + {"keypad6", ImGuiKey_Keypad6}, + {"keypad7", ImGuiKey_Keypad7}, + {"keypad8", ImGuiKey_Keypad8}, + {"keypad9", ImGuiKey_Keypad9}, + {"keypad.", ImGuiKey_KeypadDecimal}, + {"keypad+", ImGuiKey_KeypadAdd}, + {"keypad-", ImGuiKey_KeypadSubtract}, + {"keypad*", ImGuiKey_KeypadMultiply}, + {"keypad/", ImGuiKey_KeypadDivide} + }; + + // Split the key string by '+' (e.g., "Ctrl+Shift+S") + std::istringstream keyStream(key); + std::string segment; + + while (std::getline(keyStream, segment, '+')) { + // Trim whitespace + segment.erase(0, segment.find_first_not_of(" \t")); + segment.erase(segment.find_last_not_of(" \t") + 1); + + // Convert to lowercase for case-insensitive comparison + std::transform(segment.begin(), segment.end(), segment.begin(), + [](unsigned char c){ return std::tolower(c); }); + + // Look up in the map and set the bit in the signature + auto it = keyMap.find(segment); + if (it != keyMap.end()) { + m_signature.set(static_cast(it->second - ImGuiKey_NamedKey_BEGIN)); + } + } + } + + bool Command::exactMatch(const std::bitset &inputSignature) const + { + return m_signature == inputSignature; + } + + bool Command::partialMatch(const std::bitset &inputSignature) const + { + return (m_signature & inputSignature) == m_signature; + } + + void Command::executePressedCallback() const + { + if (m_callback) + m_callback(); + } + + void Command::executeReleasedCallback() const + { + + } + + const std::span Command::getChildren() const + { + return std::span(m_childrens); + } + + const std::bitset &Command::getSignature() const + { + return m_signature; + } + + const std::string &Command::getKey() const + { + return m_key; + } + + const bool Command::isModifier() const + { + return m_isModifier; + } + + const std::string &Command::getDescription() const + { + return m_description; + } + +} diff --git a/editor/src/inputs/Command.hpp b/editor/src/inputs/Command.hpp new file mode 100644 index 000000000..44df1a780 --- /dev/null +++ b/editor/src/inputs/Command.hpp @@ -0,0 +1,52 @@ +//// Command.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: Mehdy MORVAN +// Date: 23/05/2025 +// Description: Header file for the input commands +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +#include +#include +#include +#include + +namespace nexo::editor { + + class Command { + public: + Command( + const std::string &description, + const std::string &key, + const std::function &callback, + bool isModifier = false, + const std::vector &childrens = {} + ); + + bool exactMatch(const std::bitset &inputSignature) const; + bool partialMatch(const std::bitset &inputSignature) const; + void executePressedCallback() const; + void executeReleasedCallback() const; + const std::span getChildren() const; + const std::bitset &getSignature() const; + const std::string &getKey() const; + const std::string &getDescription() const; + const bool isModifier() const; + + private: + std::bitset m_signature; + std::string m_description; + std::string m_key; + std::function m_callback; + bool m_isModifier; + std::vector m_childrens; + }; +} diff --git a/editor/src/inputs/InputManager.cpp b/editor/src/inputs/InputManager.cpp new file mode 100644 index 000000000..0a364c1c3 --- /dev/null +++ b/editor/src/inputs/InputManager.cpp @@ -0,0 +1,197 @@ +//// InputManager.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: 23/05/2025 +// Description: Source file for the input manager +// +/////////////////////////////////////////////////////////////////////////////// + +#include "InputManager.hpp" +#include +#include +#include + +// Implementation sketch for InputManager + +namespace nexo::editor { + + void InputManager::processInputs(const WindowState& windowState) { + std::bitset pressedSignature; + std::bitset releasedSignature; + + // -5 to avoid reserved mod key + for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_COUNT + ImGuiKey_NamedKey_BEGIN - 5; key++) { + if (ImGui::IsKeyPressed(static_cast(key)) || ImGui::IsKeyDown(static_cast(key))) + { + pressedSignature.set(static_cast(key - ImGuiKey_NamedKey_BEGIN)); + } + if (ImGui::IsKeyReleased(static_cast(key))) + { + releasedSignature.set(static_cast(key - ImGuiKey_NamedKey_BEGIN)); + } + } + + // Process commands for the current window + processCommands(windowState.getCommands(), pressedSignature, releasedSignature); + } + + void InputManager::processCommands( + const std::span& commands, + const std::bitset& pressedSignature, + const std::bitset& releasedSignature + ) { + for (const auto& command : commands) { + if (command.exactMatch(pressedSignature)) { + // Execute the command on exact match + command.executePressedCallback(); + } + else if (command.partialMatch(pressedSignature) && !command.getChildren().empty()) { + // If partial match and has children, check children commands + auto remainingBits = pressedSignature ^ command.getSignature(); + processCommands(command.getChildren(), remainingBits, releasedSignature); + } + + if (command.exactMatch(releasedSignature)) { + // Execute the command on exact match + command.executeReleasedCallback(); + } + else if (command.partialMatch(releasedSignature) && !command.getChildren().empty()) { + // If partial match and has children, check children commands + auto remainingBits = releasedSignature ^ command.getSignature(); + processCommands(command.getChildren(), pressedSignature, remainingBits); + } + } + } + + std::vector InputManager::getPossibleCommands(const WindowState& windowState) const { + std::bitset pressedSignature; + + 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))) + { + pressedSignature.set(static_cast(key - ImGuiKey_NamedKey_BEGIN)); + } + } + + std::vector possibleCommands; + collectPossibleCommands(windowState.getCommands(), pressedSignature, possibleCommands); + return possibleCommands; + } + + void InputManager::collectPossibleCommands( + const std::span& commands, + const std::bitset& pressedSignature, + std::vector& possibleCommands) const { + + for (const auto& command : commands) { + // If no keys are pressed, show all possible command chains + if (pressedSignature.none()) { + if (command.getChildren().empty() || !command.isModifier()) { + possibleCommands.push_back({command.getKey(), command.getDescription()}); + } else { + // For modifiers with children, build combinations recursively + std::vector childCombinations; + buildCommandCombinations(command, "", childCombinations); + possibleCommands.insert(possibleCommands.end(), childCombinations.begin(), childCombinations.end()); + } + continue; + } + + // If this command matches the pressed signature exactly or partially + if (command.partialMatch(pressedSignature)) { + // If it's a modifier and matches exactly + if (command.isModifier() && (command.getSignature() & pressedSignature) == command.getSignature()) { + // Check if any child modifiers are pressed + bool hasActivatedChildModifier = false; + + for (const auto& child : command.getChildren()) { + if (child.isModifier() && (child.getSignature() & pressedSignature) == child.getSignature()) { + hasActivatedChildModifier = true; + break; + } + } + + // For each child command, add the appropriate representation + for (const auto& child : command.getChildren()) { + // If we have an activated child modifier, only process those + if (hasActivatedChildModifier) { + // Skip non-modifier children or modifiers that aren't pressed + if (!child.isModifier() || !((child.getSignature() & pressedSignature) == child.getSignature())) { + continue; + } + + // Child modifier is pressed, show only its children's keys + for (const auto& grandchild : child.getChildren()) { + possibleCommands.push_back({grandchild.getKey(), grandchild.getDescription()}); + } + } else { + // No child modifiers are pressed, show all children + if (child.isModifier() && !child.getChildren().empty()) { + // Build combinations for this child modifier + for (const auto& grandchild : child.getChildren()) { + possibleCommands.push_back({ + child.getKey() + "+" + grandchild.getKey(), + grandchild.getDescription() + }); + } + } else { + // Child is not a modifier + possibleCommands.push_back({child.getKey(), child.getDescription()}); + } + } + } + + // Skip recursive processing if we've handled modifiers + continue; + } + + // 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 + collectPossibleCommands(command.getChildren(), remainingBits, possibleCommands); + } + } + } + } + } + + // Helper method to recursively build all possible command combinations + void InputManager::buildCommandCombinations( + const Command& command, + const std::string& prefix, + std::vector& combinations) const { + + std::string currentPrefix = prefix.empty() ? command.getKey() : prefix + "+" + command.getKey(); + + // If this is a leaf command or not a modifier, add the combination + if (command.getChildren().empty() || !command.isModifier()) { + combinations.push_back({currentPrefix, command.getDescription()}); + return; + } + + // For modifiers with children, recursively build combinations + for (const auto& child : command.getChildren()) { + buildCommandCombinations(child, currentPrefix, combinations); + } + } +} diff --git a/editor/src/inputs/InputManager.hpp b/editor/src/inputs/InputManager.hpp new file mode 100644 index 000000000..e828635c0 --- /dev/null +++ b/editor/src/inputs/InputManager.hpp @@ -0,0 +1,59 @@ +//// Input.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: Mehdy MORVAN +// Date: +// Description: +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "WindowState.hpp" +#include "Command.hpp" +#include + +namespace nexo::editor { + struct CommandInfo { + std::string key; + std::string description; + + CommandInfo(const std::string& k, const std::string& d) : key(k), description(d) {} + }; + + class InputManager { + public: + InputManager() = default; + ~InputManager() = default; + + // Process inputs based on current window state + void processInputs(const WindowState& windowState); + + // Update these method signatures: + std::vector getPossibleCommands(const WindowState& windowState) const; + + private: + // Current and previous key states for detecting changes + std::bitset m_currentKeyState; + std::bitset m_previousKeyState; + + // Recursive command matching + void processCommands(const std::span& commands, + const std::bitset& pressedSignature, + const std::bitset& releasedSignature + ); + 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; + }; +} diff --git a/editor/src/inputs/WindowState.cpp b/editor/src/inputs/WindowState.cpp new file mode 100644 index 000000000..0349dc144 --- /dev/null +++ b/editor/src/inputs/WindowState.cpp @@ -0,0 +1,32 @@ +//// WindowState.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: 23/05/2025 +// Description: Source file for the window state class +// +/////////////////////////////////////////////////////////////////////////////// + +#include "WindowState.hpp" + +namespace nexo::editor { + const unsigned int WindowState::getId() const + { + return m_id; + } + + void WindowState::registerCommand(const Command &command) + { + m_commands.push_back(command); + } + + const std::span WindowState::getCommands() const + { + return std::span(m_commands.data(), m_commands.size()); + } +} diff --git a/editor/src/inputs/WindowState.hpp b/editor/src/inputs/WindowState.hpp new file mode 100644 index 000000000..82431ce33 --- /dev/null +++ b/editor/src/inputs/WindowState.hpp @@ -0,0 +1,34 @@ +//// WindowState.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: Mehdy MORVAN +// Date: 23/05/2025 +// Description: Header file for the window state, a class wrapping a set of input commands +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "Command.hpp" +#include + +namespace nexo::editor { + + class WindowState { + public: + WindowState() = default; + WindowState(unsigned int id) : m_id(id) {} + ~WindowState() = default; + + const unsigned int getId() const; + void registerCommand(const Command &command); + const std::span getCommands() const; + private: + unsigned int m_id; + std::vector m_commands; + }; +} From aa8e968bc327dd55d32ce6e869f50c581a468f74 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 23 Apr 2025 23:45:31 +0900 Subject: [PATCH 193/450] feat(document-windows): add method to retrieve window state --- editor/src/ADocumentWindow.hpp | 4 ++++ editor/src/IDocumentWindow.hpp | 3 +++ 2 files changed, 7 insertions(+) diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index 3817e2f0a..02248dd9a 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -17,6 +17,7 @@ #include "IDocumentWindow.hpp" #include "Nexo.hpp" #include "WindowRegistry.hpp" +#include "inputs/WindowState.hpp" #include @@ -60,6 +61,8 @@ namespace nexo::editor { [[nodiscard]] const std::string &getWindowName() const override { return m_windowName; } + [[nodiscard]] const WindowState &getWindowState() const override { return m_windowState; }; + /** * @brief Initializes the docking configuration for the document window on its first display. * @@ -106,6 +109,7 @@ namespace nexo::editor { bool m_firstOpened = true; std::string m_windowName; + WindowState m_windowState; WindowRegistry &m_windowRegistry; }; diff --git a/editor/src/IDocumentWindow.hpp b/editor/src/IDocumentWindow.hpp index 86cee11cd..1e8759a80 100644 --- a/editor/src/IDocumentWindow.hpp +++ b/editor/src/IDocumentWindow.hpp @@ -16,6 +16,8 @@ #include +#include "inputs/WindowState.hpp" + namespace nexo::editor { using WindowId = unsigned int; @@ -34,5 +36,6 @@ namespace nexo::editor { [[nodiscard]] virtual bool isHovered() const = 0; [[nodiscard]] virtual bool &getOpened() = 0; [[nodiscard]] virtual const std::string &getWindowName() const = 0; + [[nodiscard]] virtual const WindowState &getWindowState() const = 0; }; } From 0992df9280bd9539a263b57dfd02894167e394be Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 23 Apr 2025 23:45:48 +0900 Subject: [PATCH 194/450] feat(document-windows): add method to retrieve focused window --- editor/src/WindowRegistry.cpp | 13 +++++++++++++ editor/src/WindowRegistry.hpp | 2 ++ 2 files changed, 15 insertions(+) diff --git a/editor/src/WindowRegistry.cpp b/editor/src/WindowRegistry.cpp index 348bc1b8f..7735ed69d 100644 --- a/editor/src/WindowRegistry.cpp +++ b/editor/src/WindowRegistry.cpp @@ -48,6 +48,19 @@ namespace nexo::editor { return m_dockingRegistry.getDockId(name); } + const 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); diff --git a/editor/src/WindowRegistry.hpp b/editor/src/WindowRegistry.hpp index 376657e8e..84a7b39aa 100644 --- a/editor/src/WindowRegistry.hpp +++ b/editor/src/WindowRegistry.hpp @@ -237,6 +237,8 @@ namespace nexo::editor { */ std::optional getDockId(const std::string& name) const; + const std::shared_ptr getFocusedWindow() const; + /** * @brief Removes a window's docking identifier. * From ff9f456de52851a2bb446d1aaf63ef40dac2929a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 23 Apr 2025 23:46:09 +0900 Subject: [PATCH 195/450] feat(document-windows): shortcut helper bar display --- editor/src/Editor.cpp | 108 ++++++++++++++++++++++++++++++++++++++++++ editor/src/Editor.hpp | 2 + 2 files changed, 110 insertions(+) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 1825a2b85..a0bc5e149 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -333,7 +333,115 @@ namespace nexo::editor { m_windowRegistry.render(); + std::vector possibleCommands; + { + auto focusedWindow = m_windowRegistry.getFocusedWindow(); + if (focusedWindow) + { + WindowState currentState = m_windowRegistry.getFocusedWindow()->getWindowState(); + m_inputManager.processInputs(currentState); + possibleCommands = m_inputManager.getPossibleCommands(currentState); + } + } + + const float bottomBarHeight = 38.0f; + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.05f, 0.06f, 0.12f, 0.85f)); // Matches your dark blue theme + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.0f)); auto viewport = ImGui::GetMainViewport(); + + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - bottomBarHeight)); + ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, bottomBarHeight)); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGuiWindowFlags bottomBarFlags = + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBackground; + + if (ImGui::Begin("CommandsBar", nullptr, bottomBarFlags)) + { + const float textScaleFactor = 0.90f; // 15% larger text + ImGui::SetWindowFontScale(textScaleFactor); + // Vertically center the content + float windowHeight = ImGui::GetWindowHeight(); + float textHeight = ImGui::GetTextLineHeight(); + float paddingY = (windowHeight - textHeight) * 0.5f; + + // Apply the vertical padding + ImGui::SetCursorPosY(paddingY); + + // Start with a small horizontal padding + ImGui::SetCursorPosX(10.0f); + + if (!possibleCommands.empty()) + { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + // Use horizontal layout for commands, left-aligned + for (const auto& cmd : possibleCommands) + { + // Calculate text sizes for proper positioning and border sizing + ImVec2 keySize = ImGui::CalcTextSize(cmd.key.c_str()); + ImVec2 colonSize = ImGui::CalcTextSize(":"); + ImVec2 descSize = ImGui::CalcTextSize(cmd.description.c_str()); + + // Position of the start of this command + ImVec2 commandStart = ImGui::GetCursorScreenPos(); + + // Total size of command group with padding + float commandWidth = keySize.x + colonSize.x + 5.0f + descSize.x; + float commandHeight = std::max(keySize.y, std::max(colonSize.y, descSize.y)); + + // Add padding around the entire command + float borderPadding = 6.0f; + float borderCornerRadius = 3.0f; + + // Draw the gradient border rectangle + ImVec2 rectMin = ImVec2(commandStart.x - borderPadding, commandStart.y - borderPadding); + ImVec2 rectMax = ImVec2(commandStart.x + commandWidth + 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 + ); + + // 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 + ); + + // Draw the command components + const std::string &key = cmd.key + ":"; + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", key.c_str()); + ImGui::SameLine(0.0f, 5.0f); + ImGui::Text("%s", cmd.description.c_str()); + + // Add space between commands + ImGui::SameLine(0.0f, 20.0f); + + // Update cursor position to account for the border we added + ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, commandStart.y)); + } + } + } + ImGui::End(); + ImGui::PopStyleColor(2); // Pop both text and bg colors + + + + ImGui::SetNextWindowPos(viewport->Pos); ImGui::SetNextWindowSize(viewport->Size); ImGui::SetNextWindowViewport(viewport->ID); diff --git a/editor/src/Editor.hpp b/editor/src/Editor.hpp index 69e7901f4..977f3ebd5 100644 --- a/editor/src/Editor.hpp +++ b/editor/src/Editor.hpp @@ -24,6 +24,7 @@ #include #include "WindowRegistry.hpp" +#include "inputs/InputManager.hpp" namespace nexo::editor { @@ -167,5 +168,6 @@ namespace nexo::editor { bool m_quit = false; bool m_showDemoWindow = false; WindowRegistry m_windowRegistry; + InputManager m_inputManager; }; } From eef0dc4b3084ab7ea9e43ab4be6d4fccc9aa6855 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 23 Apr 2025 23:46:21 +0900 Subject: [PATCH 196/450] chore(document-windows): add source files --- editor/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index f425cca50..d54038f0e 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -26,6 +26,9 @@ set(SRCS editor/src/utils/String.cpp editor/src/utils/FileSystem.cpp editor/src/utils/EditorProps.cpp + editor/src/inputs/Command.cpp + editor/src/inputs/InputManager.cpp + editor/src/inputs/WindowState.cpp editor/src/Editor.cpp editor/src/WindowRegistry.cpp editor/src/DockingRegistry.cpp From 489711ca8255dad477505a45a1f4095aca57bca4 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Apr 2025 15:22:22 +0900 Subject: [PATCH 197/450] fix(document-windows): the bar does not disapear when pressing buttons --- editor/src/Editor.cpp | 30 ++++++++++++++++++++++++++++++ editor/src/inputs/InputManager.cpp | 9 +++++++++ editor/src/inputs/InputManager.hpp | 1 + 3 files changed, 40 insertions(+) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index a0bc5e149..bfd9d1c53 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -333,7 +333,12 @@ namespace nexo::editor { m_windowRegistry.render(); + // Get the commands to display in the bottom bar 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 + const float commandPersistTime = 2.0f; // Show last commands for 2 seconds + { auto focusedWindow = m_windowRegistry.getFocusedWindow(); if (focusedWindow) @@ -341,6 +346,31 @@ namespace nexo::editor { WindowState currentState = m_windowRegistry.getFocusedWindow()->getWindowState(); m_inputManager.processInputs(currentState); possibleCommands = m_inputManager.getPossibleCommands(currentState); + + // Update the last valid commands if we have any + if (!possibleCommands.empty()) + { + 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()) + { + // 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); + lastValidCommands = possibleCommands; + } + else + { + // Use the last valid set of commands + possibleCommands = lastValidCommands; + } } } diff --git a/editor/src/inputs/InputManager.cpp b/editor/src/inputs/InputManager.cpp index 0a364c1c3..1ac8c5c72 100644 --- a/editor/src/inputs/InputManager.cpp +++ b/editor/src/inputs/InputManager.cpp @@ -69,6 +69,15 @@ namespace nexo::editor { } } + // Add this method implementation + std::vector InputManager::getAllPossibleCommands(const WindowState& windowState) const { + std::vector allCommands; + // Use an empty signature to get all commands + std::bitset emptySignature; + collectPossibleCommands(windowState.getCommands(), emptySignature, allCommands); + return allCommands; + } + std::vector InputManager::getPossibleCommands(const WindowState& windowState) const { std::bitset pressedSignature; diff --git a/editor/src/inputs/InputManager.hpp b/editor/src/inputs/InputManager.hpp index e828635c0..3abeae8d6 100644 --- a/editor/src/inputs/InputManager.hpp +++ b/editor/src/inputs/InputManager.hpp @@ -35,6 +35,7 @@ namespace nexo::editor { // Update these method signatures: std::vector getPossibleCommands(const WindowState& windowState) const; + std::vector getAllPossibleCommands(const WindowState& windowState) const; private: // Current and previous key states for detecting changes From 852616c0018c5d3263db9cd933fc6fcf977b60a0 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Apr 2025 15:22:40 +0900 Subject: [PATCH 198/450] feat(document-windows): add gizmo shortcut handling --- editor/src/DocumentWindows/EditorScene.cpp | 324 ++++++++++++++++++--- editor/src/DocumentWindows/EditorScene.hpp | 7 + 2 files changed, 296 insertions(+), 35 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 85c470396..d9814a478 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -35,6 +35,7 @@ #include "utils/String.hpp" #include "utils/EditorProps.hpp" #include "ImNexo/Widgets.hpp" +#include "ImNexo/Panels.hpp" #include #include @@ -49,54 +50,249 @@ namespace nexo::editor { setupWindow(); setupScene(); + // ================= GLOBAL STATE ============================= m_globalState = {static_cast(EditorState::GLOBAL)}; m_globalState.registerCommand( { - "Ctrl Context", - "Ctrl", - []{ - //std::cout << "Ctrl pressed" << std::endl; - }, + "Shift context", + "Shift", + nullptr, true, { { - "Copy", - "C", - []{ - std::cout << "Copy command executed" << std::endl; + "Add entity", + "A", + [this]{ + this->m_popupManager.openPopup("Add new entity popup"); }, false, - }, - { - "Paste", - "V", - []{ - std::cout << "Paste command executed" << std::endl; - }, - false, - }, - { - "Sub child", - "Shift", - nullptr, - true, - { - { - "Delete", - "X", - []{ - std::cout << "Sub sub command executed" << std::endl; - }, - false, - } - } } } } ); - + m_globalState.registerCommand( + { + "Select all", + "A", + [this]{ + LOG(NEXO_WARN, "Select all not implemented yet"); + }, + false + } + ); m_windowState = m_globalState; + // ================= GIZMO STATE ============================= + m_gizmoState = {static_cast(EditorState::GIZMO)}; + m_gizmoState.registerCommand( + { + "Translate", + "G", + [this]{ + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + false, + } + ); + m_gizmoState.registerCommand( + { + "Rotate", + "R", + [this]{ + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + false, + } + ); + m_gizmoState.registerCommand( + { + "Scale", + "S", + [this]{ + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + false, + } + ); + + // ================= TRANSLATE STATE ============================= + m_gizmoTranslateState = {static_cast(EditorState::GIZMO_TRANSLATE)}; + m_gizmoTranslateState.registerCommand( + { + "Universal", + "U", + [this]{ + this->m_windowState = m_gizmoState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; + }, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Translate", + "G", + [this]{ + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Lock X", + "X", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_X; + }, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Lock Y", + "Y", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Y; + }, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Lock Z", + "Z", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Z; + }, + false, + } + ); + + // ================= ROTATE STATE ============================= + m_gizmoRotateState = {static_cast(EditorState::GIZMO_ROTATE)}; + m_gizmoRotateState.registerCommand( + { + "Universal", + "U", + [this]{ + this->m_windowState = m_gizmoState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; + }, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Rotate", + "R", + [this]{ + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Screen rotation", + "S", + [this]{ + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_SCREEN; + }, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Lock X", + "X", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_X; + }, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Lock Y", + "Y", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Y; + }, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Lock Z", + "Z", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Z; + }, + false, + } + ); + + // ================= SCALE STATE ============================= + m_gizmoScaleState = {static_cast(EditorState::GIZMO_SCALE)}; + m_gizmoScaleState.registerCommand( + { + "Universal", + "U", + [this]{ + this->m_windowState = m_gizmoState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; + }, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Scale", + "S", + [this]{ + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Lock X", + "X", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_X; + }, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Lock Y", + "Y", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Y; + }, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Lock Z", + "Z", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Z; + }, + false, + } + ); } void EditorScene::setupScene() @@ -608,8 +804,10 @@ namespace nexo::editor { glm::scale(glm::mat4(1.0f), {transf->get().size.x, transf->get().size.y, transf->get().size.z}); static ImGuizmo::OPERATION lastOperation; - if (!ImGuizmo::IsUsing()) + if (!ImGuizmo::IsUsing()) { lastOperation = getLastGuizmoOperation(); + //m_windowState = m_globalState; + } float *snap = nullptr; if (m_snapTranslateOn && lastOperation & ImGuizmo::OPERATION::TRANSLATE) { @@ -714,6 +912,55 @@ namespace nexo::editor { renderToolbar(); } + + if (m_popupManager.showPopup("Add new entity popup")) + { + auto &app = Application::getInstance(); + auto &sceneManager = app.getSceneManager(); + const auto sceneId = m_sceneId; + + // --- Primitives submenu --- + if (ImGui::BeginMenu("Primitives")) { + if (ImGui::MenuItem("Cube")) { + const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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); + } + ImGui::EndMenu(); + } + + // --- Model item (with file‑dialog) --- + if (ImGui::MenuItem("Model")) { + //TODO: import model + } + + // --- Lights submenu --- + 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); + } + if (ImGui::MenuItem("Point")) { + const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); + utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); + sceneManager.getScene(sceneId).addEntity(pointLight); + } + if (ImGui::MenuItem("Spot")) { + const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); + utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); + sceneManager.getScene(sceneId).addEntity(spotLight); + } + ImGui::EndMenu(); + } + + // --- Camera item --- + if (ImGui::MenuItem("Camera")) { + m_popupManager.openPopupWithCallback("Popup camera inspector", [this]() { + ImNexo::CameraInspector(this->m_sceneId, this->m_viewSize); + }, ImVec2(1440,900)); + } + m_popupManager.closePopup(); + } } ImGui::End(); ImGui::PopStyleVar(); @@ -749,11 +996,18 @@ namespace nexo::editor { if (data == -1) { selector.unselectEntity(); + m_windowState = m_globalState; return; } const auto uuid = Application::m_coordinator->tryGetComponent(data); if (uuid) { + if (m_currentGizmoOperation == ImGuizmo::OPERATION::TRANSLATE) + m_windowState = m_gizmoTranslateState; + else if (m_currentGizmoOperation == ImGuizmo::OPERATION::ROTATE) + m_windowState = m_gizmoRotateState; + else + m_windowState = m_gizmoState; auto &app = getApp(); selector.setSelectedEntity(uuid->get().uuid, data); if (app.m_coordinator->entityHasComponent(data)) diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp index ff3e08ff7..8e92a781d 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene.hpp @@ -286,9 +286,16 @@ namespace nexo::editor { enum class EditorState { GLOBAL, GIZMO, + GIZMO_TRANSLATE, + GIZMO_ROTATE, + GIZMO_SCALE, NB_STATE }; WindowState m_globalState; + WindowState m_gizmoState; + WindowState m_gizmoTranslateState; + WindowState m_gizmoRotateState; + WindowState m_gizmoScaleState; }; } From f568952fd1b3f76d058fcf30959c70083acb7792 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Apr 2025 15:39:27 +0900 Subject: [PATCH 199/450] feat(document-windows): add on release callback --- editor/src/inputs/Command.cpp | 12 +++++++----- editor/src/inputs/Command.hpp | 6 ++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/editor/src/inputs/Command.cpp b/editor/src/inputs/Command.cpp index 706070ed4..81503d62b 100644 --- a/editor/src/inputs/Command.cpp +++ b/editor/src/inputs/Command.cpp @@ -24,10 +24,11 @@ namespace nexo::editor { Command::Command( const std::string &description, const std::string &key, - const std::function &callback, + const std::function &pressedCallback, + const std::function &releaseCallback, bool isModifier, const std::vector &children) - : m_description(description), m_key(key), m_callback(callback), m_isModifier(isModifier), m_childrens(children) + : m_description(description), m_key(key), m_pressedCallback(pressedCallback), m_releaseCallback(releaseCallback), m_isModifier(isModifier), m_childrens(children) { // Create a mapping of key names to ImGuiKey values static const std::unordered_map keyMap = { @@ -133,13 +134,14 @@ namespace nexo::editor { void Command::executePressedCallback() const { - if (m_callback) - m_callback(); + if (m_pressedCallback) + m_pressedCallback(); } void Command::executeReleasedCallback() const { - + if (m_releaseCallback) + m_releaseCallback(); } const std::span Command::getChildren() const diff --git a/editor/src/inputs/Command.hpp b/editor/src/inputs/Command.hpp index 44df1a780..2df6e5e38 100644 --- a/editor/src/inputs/Command.hpp +++ b/editor/src/inputs/Command.hpp @@ -26,7 +26,8 @@ namespace nexo::editor { Command( const std::string &description, const std::string &key, - const std::function &callback, + const std::function &pressedCallback, + const std::function &releaseCallback, bool isModifier = false, const std::vector &childrens = {} ); @@ -45,7 +46,8 @@ namespace nexo::editor { std::bitset m_signature; std::string m_description; std::string m_key; - std::function m_callback; + std::function m_pressedCallback; + std::function m_releaseCallback; bool m_isModifier; std::vector m_childrens; }; From 16150e96f00418d178b301dee5574ca9709f8b4b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 24 Apr 2025 15:40:22 +0900 Subject: [PATCH 200/450] feat(document-windows): now need to maintain press to show axis --- editor/src/DocumentWindows/EditorScene.cpp | 106 ++++++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index d9814a478..e21e91420 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -57,6 +57,7 @@ namespace nexo::editor { "Shift context", "Shift", nullptr, + nullptr, true, { { @@ -65,6 +66,7 @@ namespace nexo::editor { [this]{ this->m_popupManager.openPopup("Add new entity popup"); }, + nullptr, false, } } @@ -77,6 +79,7 @@ namespace nexo::editor { [this]{ LOG(NEXO_WARN, "Select all not implemented yet"); }, + nullptr, false } ); @@ -92,6 +95,7 @@ namespace nexo::editor { this->m_windowState = m_gizmoTranslateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, + nullptr, false, } ); @@ -103,6 +107,7 @@ namespace nexo::editor { this->m_windowState = m_gizmoRotateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }, + nullptr, false, } ); @@ -114,6 +119,7 @@ namespace nexo::editor { this->m_windowState = m_gizmoScaleState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, + nullptr, false, } ); @@ -128,6 +134,7 @@ namespace nexo::editor { this->m_windowState = m_gizmoState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; }, + nullptr, false, } ); @@ -139,6 +146,31 @@ namespace nexo::editor { this->m_windowState = m_gizmoTranslateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, + nullptr, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Rotate", + "R", + [this]{ + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + nullptr, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Scale", + "S", + [this]{ + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + nullptr, false, } ); @@ -149,6 +181,9 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_X; }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, false, } ); @@ -159,6 +194,9 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Y; }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, false, } ); @@ -169,6 +207,9 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Z; }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, false, } ); @@ -183,6 +224,7 @@ namespace nexo::editor { this->m_windowState = m_gizmoState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; }, + nullptr, false, } ); @@ -194,17 +236,31 @@ namespace nexo::editor { this->m_windowState = m_gizmoRotateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }, + nullptr, false, } ); m_gizmoRotateState.registerCommand( { - "Screen rotation", + "Translate", + "G", + [this]{ + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + nullptr, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Scale", "S", [this]{ - this->m_windowState = m_gizmoRotateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_SCREEN; + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, + nullptr, false, } ); @@ -215,6 +271,9 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_X; }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, false, } ); @@ -225,6 +284,9 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Y; }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, false, } ); @@ -235,6 +297,9 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Z; }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, false, } ); @@ -249,6 +314,7 @@ namespace nexo::editor { this->m_windowState = m_gizmoState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; }, + nullptr, false, } ); @@ -260,6 +326,31 @@ namespace nexo::editor { this->m_windowState = m_gizmoScaleState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, + nullptr, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Translate", + "G", + [this]{ + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + nullptr, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Rotate", + "R", + [this]{ + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + nullptr, false, } ); @@ -270,6 +361,9 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_X; }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, false, } ); @@ -280,6 +374,9 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Y; }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, false, } ); @@ -290,6 +387,9 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Z; }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, false, } ); From 32d99e207afe38cdf1c70c18161304e02fb1a643 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Apr 2025 13:03:13 +0900 Subject: [PATCH 201/450] feat(document-windows): add translation speed increase on camera controller --- engine/src/components/Camera.hpp | 1 + engine/src/core/event/KeyCodes.hpp | 2 ++ engine/src/systems/CameraSystem.cpp | 19 ++++++++++++------- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 833b19a54..e09098a04 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -120,6 +120,7 @@ namespace nexo::components { float mouseSensitivity = 0.1f;///< Sensitivity factor for mouse movement. float yaw = 0.0f; ///< Yaw angle in degrees. float pitch = 0.0f; ///< Pitch angle in degrees. + float translationSpeed = 5.0f; ///< Camera speed }; /** diff --git a/engine/src/core/event/KeyCodes.hpp b/engine/src/core/event/KeyCodes.hpp index d4f55206a..51a79e803 100644 --- a/engine/src/core/event/KeyCodes.hpp +++ b/engine/src/core/event/KeyCodes.hpp @@ -38,6 +38,8 @@ namespace nexo::event { #define NEXO_KEY_DOWN 264 #define NEXO_KEY_UP 265 + #define NEXO_KEY_SHIFT 340 + #define NEXO_MOUSE_LEFT 0 #define NEXO_MOUSE_RIGHT 1 } diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index 250a01a0b..bab131eef 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -83,7 +83,6 @@ namespace nexo::system { for (const ecs::Entity entity : entities) { - constexpr float translationSpeed = 5.0f; auto &sceneTag = getComponent(entity); if (!sceneTag.isActive || sceneTag.id != sceneRendered) continue; @@ -91,25 +90,31 @@ namespace nexo::system { 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 * translationSpeed * deltaTime; // Forward + transform.pos += front * cameraController.translationSpeed * deltaTime; // Forward if (event::isKeyPressed(NEXO_KEY_S)) - transform.pos -= front * translationSpeed * deltaTime; // Backward + transform.pos -= front * cameraController.translationSpeed * deltaTime; // Backward if (event::isKeyPressed(NEXO_KEY_Q)) - transform.pos -= right * translationSpeed * deltaTime; // Left + transform.pos -= right * cameraController.translationSpeed * deltaTime; // Left if (event::isKeyPressed(NEXO_KEY_D)) - transform.pos += right * translationSpeed * deltaTime; // Right + transform.pos += right * cameraController.translationSpeed * deltaTime; // Right if (event::isKeyPressed(NEXO_KEY_SPACE)) - transform.pos += up * translationSpeed * deltaTime; // Up + transform.pos += up * cameraController.translationSpeed * deltaTime; // Up if (event::isKeyPressed(NEXO_KEY_TAB)) - transform.pos -= up * translationSpeed * deltaTime; // Down + transform.pos -= up * cameraController.translationSpeed * deltaTime; // Down } } From 6324eea5b18c587a9b46322193e0195d0da76e22 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Apr 2025 13:03:40 +0900 Subject: [PATCH 202/450] fix(document-windows): now ignore mouse key inputs when retrieving inputs --- editor/src/inputs/InputManager.cpp | 46 +++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/editor/src/inputs/InputManager.cpp b/editor/src/inputs/InputManager.cpp index 1ac8c5c72..40cdcd874 100644 --- a/editor/src/inputs/InputManager.cpp +++ b/editor/src/inputs/InputManager.cpp @@ -25,8 +25,20 @@ namespace nexo::editor { std::bitset pressedSignature; std::bitset releasedSignature; + static const std::set excludedKeys = { + ImGuiKey_MouseLeft, + ImGuiKey_MouseRight, + ImGuiKey_MouseMiddle, + ImGuiKey_MouseX1, + ImGuiKey_MouseX2, + ImGuiKey_MouseWheelX, + ImGuiKey_MouseWheelY + }; + // -5 to avoid reserved mod key for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_COUNT + ImGuiKey_NamedKey_BEGIN - 5; key++) { + if (excludedKeys.contains(static_cast(key))) + continue; if (ImGui::IsKeyPressed(static_cast(key)) || ImGui::IsKeyDown(static_cast(key))) { pressedSignature.set(static_cast(key - ImGuiKey_NamedKey_BEGIN)); @@ -47,24 +59,36 @@ namespace nexo::editor { const std::bitset& releasedSignature ) { for (const auto& command : commands) { + // Handle pressed callbacks if (command.exactMatch(pressedSignature)) { - // Execute the command on exact match command.executePressedCallback(); } - else if (command.partialMatch(pressedSignature) && !command.getChildren().empty()) { - // If partial match and has children, check children commands - auto remainingBits = pressedSignature ^ command.getSignature(); - processCommands(command.getChildren(), remainingBits, releasedSignature); - } + // Handle released callbacks if (command.exactMatch(releasedSignature)) { - // Execute the command on exact match command.executeReleasedCallback(); } - else if (command.partialMatch(releasedSignature) && !command.getChildren().empty()) { - // If partial match and has children, check children commands - auto remainingBits = releasedSignature ^ command.getSignature(); - processCommands(command.getChildren(), pressedSignature, remainingBits); + + // Process child commands for partial matches + if (!command.getChildren().empty()) { + // Special case: When a modifier is held down and child keys are pressed/released + if (command.isModifier() && (command.getSignature() & pressedSignature) == command.getSignature()) { + // The modifier is currently pressed + auto remainingPressedBits = pressedSignature ^ command.getSignature(); + + // Process child commands with the remaining pressed bits and ALL released bits + // This ensures child release events work while modifier is held + processCommands(command.getChildren(), remainingPressedBits, releasedSignature); + } + // Standard partial match cases + else if (command.partialMatch(pressedSignature)) { + auto remainingPressedBits = pressedSignature ^ command.getSignature(); + processCommands(command.getChildren(), remainingPressedBits, releasedSignature); + } + else if (command.partialMatch(releasedSignature)) { + auto remainingReleasedBits = releasedSignature ^ command.getSignature(); + processCommands(command.getChildren(), pressedSignature, remainingReleasedBits); + } } } } From 54cfd47e3ff8331dac5c4432ae4ca086836f4758 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Apr 2025 13:04:13 +0900 Subject: [PATCH 203/450] feat(document-windows): add delete + snapping shortcut --- editor/src/DocumentWindows/EditorScene.cpp | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index e21e91420..601e8e1d2 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -87,6 +87,22 @@ namespace nexo::editor { // ================= GIZMO STATE ============================= m_gizmoState = {static_cast(EditorState::GIZMO)}; + m_gizmoState.registerCommand( + { + "Delete", + "X", + [this]{ + auto &selector = Selector::get(); + ecs::Entity selectedEntity = selector.getSelectedEntity(); + selector.unselectEntity(); + auto &app = nexo::getApp(); + app.deleteEntity(selectedEntity); + this->m_windowState = m_globalState; + }, + nullptr, + false, + } + ); m_gizmoState.registerCommand( { "Translate", @@ -123,6 +139,30 @@ namespace nexo::editor { false, } ); + m_gizmoState.registerCommand( + { + "Shift context", + "Shift", + nullptr, + nullptr, + true, + { + { + "Toggle snapping", + "S", + [this]{ + m_snapTranslateOn = true; + m_snapRotateOn = true; + }, + [this]{ + m_snapTranslateOn = false; + m_snapRotateOn = false; + }, + false, + } + } + } + ); // ================= TRANSLATE STATE ============================= m_gizmoTranslateState = {static_cast(EditorState::GIZMO_TRANSLATE)}; @@ -174,6 +214,28 @@ namespace nexo::editor { false, } ); + m_gizmoTranslateState.registerCommand( + { + "Shift context", + "Shift", + nullptr, + nullptr, + true, + { + { + "Toggle snapping", + "S", + [this]{ + m_snapTranslateOn = true; + }, + [this]{ + m_snapTranslateOn = false; + }, + false, + } + } + } + ); m_gizmoTranslateState.registerCommand( { "Lock X", @@ -264,6 +326,28 @@ namespace nexo::editor { false, } ); + m_gizmoRotateState.registerCommand( + { + "Shift context", + "Shift", + nullptr, + nullptr, + true, + { + { + "Toggle snapping", + "S", + [this]{ + m_snapRotateOn = true; + }, + [this]{ + m_snapRotateOn = false; + }, + false, + } + } + } + ); m_gizmoRotateState.registerCommand( { "Lock X", From 0832b34b97e35a79f229c56be51f2f51e4ba1585 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Apr 2025 14:37:56 +0900 Subject: [PATCH 204/450] feat(document-windows): add key repeat handling --- editor/src/inputs/Command.cpp | 9 +- editor/src/inputs/Command.hpp | 3 + editor/src/inputs/InputManager.cpp | 213 +++++++++++++++++++++++++---- editor/src/inputs/InputManager.hpp | 7 +- 4 files changed, 203 insertions(+), 29 deletions(-) diff --git a/editor/src/inputs/Command.cpp b/editor/src/inputs/Command.cpp index 81503d62b..5074048bb 100644 --- a/editor/src/inputs/Command.cpp +++ b/editor/src/inputs/Command.cpp @@ -26,9 +26,10 @@ namespace nexo::editor { const std::string &key, const std::function &pressedCallback, const std::function &releaseCallback, + const std::function &repeatCallback, bool isModifier, const std::vector &children) - : m_description(description), m_key(key), m_pressedCallback(pressedCallback), m_releaseCallback(releaseCallback), m_isModifier(isModifier), m_childrens(children) + : m_description(description), m_key(key), m_pressedCallback(pressedCallback), m_releaseCallback(releaseCallback), m_repeatCallback(repeatCallback), m_isModifier(isModifier), m_childrens(children) { // Create a mapping of key names to ImGuiKey values static const std::unordered_map keyMap = { @@ -144,6 +145,12 @@ namespace nexo::editor { m_releaseCallback(); } + void Command::executeRepeatCallback() const + { + if (m_repeatCallback) + m_repeatCallback(); + } + const std::span Command::getChildren() const { return std::span(m_childrens); diff --git a/editor/src/inputs/Command.hpp b/editor/src/inputs/Command.hpp index 2df6e5e38..331a9a366 100644 --- a/editor/src/inputs/Command.hpp +++ b/editor/src/inputs/Command.hpp @@ -28,6 +28,7 @@ namespace nexo::editor { const std::string &key, const std::function &pressedCallback, const std::function &releaseCallback, + const std::function &repeatCallback, bool isModifier = false, const std::vector &childrens = {} ); @@ -36,6 +37,7 @@ namespace nexo::editor { bool partialMatch(const std::bitset &inputSignature) const; void executePressedCallback() const; void executeReleasedCallback() const; + void executeRepeatCallback() const; const std::span getChildren() const; const std::bitset &getSignature() const; const std::string &getKey() const; @@ -48,6 +50,7 @@ namespace nexo::editor { std::string m_key; std::function m_pressedCallback; std::function m_releaseCallback; + std::function m_repeatCallback; bool m_isModifier; std::vector m_childrens; }; diff --git a/editor/src/inputs/InputManager.cpp b/editor/src/inputs/InputManager.cpp index 40cdcd874..eeb68326b 100644 --- a/editor/src/inputs/InputManager.cpp +++ b/editor/src/inputs/InputManager.cpp @@ -24,6 +24,8 @@ namespace nexo::editor { void InputManager::processInputs(const WindowState& windowState) { std::bitset pressedSignature; std::bitset releasedSignature; + std::bitset repeatSignature; + std::bitset currentlyHeldKeys; static const std::set excludedKeys = { ImGuiKey_MouseLeft, @@ -35,22 +37,119 @@ namespace nexo::editor { ImGuiKey_MouseWheelY }; - // -5 to avoid reserved mod key + // Track keys that were held down last frame + static std::bitset lastFrameHeldKeys; + + // Track multiple-press detection + static std::vector keyLastPressTime(ImGuiKey_NamedKey_COUNT, -1.0f); + static std::vector keyPressCount(ImGuiKey_NamedKey_COUNT, 0); + const float multiPressThreshold = 0.3f; // Time threshold for multiple press detection (seconds) + + float currentTime = ImGui::GetTime(); + for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_COUNT + ImGuiKey_NamedKey_BEGIN - 5; key++) { if (excludedKeys.contains(static_cast(key))) continue; - if (ImGui::IsKeyPressed(static_cast(key)) || ImGui::IsKeyDown(static_cast(key))) - { - pressedSignature.set(static_cast(key - ImGuiKey_NamedKey_BEGIN)); + + ImGuiKey imKey = static_cast(key); + size_t idx = static_cast(key - ImGuiKey_NamedKey_BEGIN); + + bool keyDown = ImGui::IsKeyDown(imKey); + bool keyPressed = ImGui::IsKeyPressed(imKey, false); + + // Update currently held keys + if (keyDown) { + currentlyHeldKeys.set(idx); + + // Handle key press detection + if (!lastFrameHeldKeys[idx]) { // Key was just pressed this frame + pressedSignature.set(idx); + + // Handle multiple press detection + if (currentTime - keyLastPressTime[idx] < multiPressThreshold) { + keyPressCount[idx]++; + if (keyPressCount[idx] > 1) { + repeatSignature.set(idx); + } + } else { + keyPressCount[idx] = 1; + } + keyLastPressTime[idx] = currentTime; + } + } else { + // Key is not down + if (lastFrameHeldKeys[idx]) { + // Key was just released + releasedSignature.set(idx); + } else if (keyLastPressTime[idx] > 0 && currentTime - keyLastPressTime[idx] > multiPressThreshold) { + // Too much time has passed since last press, reset the counter + keyPressCount[idx] = 0; + } } - if (ImGui::IsKeyReleased(static_cast(key))) - { - releasedSignature.set(static_cast(key - ImGuiKey_NamedKey_BEGIN)); + } + + // Get all commands to process + const auto& commands = windowState.getCommands(); + + // STEP 1: Find and process modifier/key combinations first + bool modifierCombinationProcessed = false; + + for (const auto& command : commands) { + if (command.isModifier()) { + // Check if modifier is pressed + std::bitset modifierSignature = command.getSignature(); + if ((modifierSignature & currentlyHeldKeys) == modifierSignature) { + // This modifier is held down, now check its children + for (const auto& childCmd : command.getChildren()) { + // Find a key that was just pressed while the modifier is held + std::bitset childSignature = childCmd.getSignature(); + if ((childSignature & pressedSignature).any()) { + // We found a modifier+key combination! Execute it + childCmd.executePressedCallback(); + modifierCombinationProcessed = true; + std::cout << "Executed modifier combination! Mod: " << command.getKey() + << " + Key: " << childCmd.getKey() << std::endl; + break; // Process only one modifier combination at a time + } + + // Check for key releases while modifier is held + if ((childSignature & releasedSignature).any()) { + childCmd.executeReleasedCallback(); + } + } + + if (modifierCombinationProcessed) { + break; // Stop checking other modifiers once we've processed one + } + } + } + } + + // STEP 2: Only process regular commands if no modifier combination was processed + if (!modifierCombinationProcessed) { + for (const auto& command : commands) { + // Skip modifiers, we already handled them + if (command.isModifier()) continue; + + // Handle pressed callbacks + if (command.exactMatch(pressedSignature)) { + command.executePressedCallback(); + } + + // Handle released callbacks + if (command.exactMatch(releasedSignature)) { + command.executeReleasedCallback(); + } } } - // Process commands for the current window - processCommands(windowState.getCommands(), pressedSignature, releasedSignature); + // Process repeat commands + if (repeatSignature.any()) { + processRepeatCommands(windowState.getCommands(), repeatSignature, currentlyHeldKeys); + } + + // Store current key state for next frame + lastFrameHeldKeys = currentlyHeldKeys; } void InputManager::processCommands( @@ -58,36 +157,96 @@ namespace nexo::editor { const std::bitset& pressedSignature, const std::bitset& releasedSignature ) { + // First pass: Check for modifier keys and their children + for (const auto& command : commands) { + // Only process modifiers in the first pass + if (!command.isModifier()) continue; + + // Check if this modifier is currently pressed + if ((command.getSignature() & pressedSignature) == command.getSignature()) { + // Calculate remaining pressed keys after removing the modifier + auto remainingPressedBits = pressedSignature; + remainingPressedBits ^= command.getSignature(); + + // Process child commands with this modifier + bool childCommandExecuted = false; + + for (const auto& child : command.getChildren()) { + // Check if child exactly matches remaining bits (modifier+key combination) + if (child.exactMatch(remainingPressedBits)) { + // We found an exact match for a modifier+key combination + child.executePressedCallback(); + childCommandExecuted = true; + + // Debugging + std::cout << "Executed modifier child: " << child.getKey() + << " with modifier: " << command.getKey() << std::endl; + } + + // Check for child key releases while modifier is held + if (child.exactMatch(releasedSignature)) { + child.executeReleasedCallback(); + } + } + + // If we executed a child command, return early to prevent regular commands from executing + if (childCommandExecuted) { + return; + } + } + } + + // Second pass: Process regular commands if no modifier combinations were executed for (const auto& command : commands) { - // Handle pressed callbacks + // Skip modifiers in the second pass + if (command.isModifier()) continue; + + // Handle pressed callbacks for non-modifiers if (command.exactMatch(pressedSignature)) { command.executePressedCallback(); } - // Handle released callbacks + // Handle released callbacks for non-modifiers if (command.exactMatch(releasedSignature)) { command.executeReleasedCallback(); } + } + } + + 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)) { + command.executeRepeatCallback(); + } - // Process child commands for partial matches + // Handle cases where a modifier is held and another key is repeating if (!command.getChildren().empty()) { - // Special case: When a modifier is held down and child keys are pressed/released - if (command.isModifier() && (command.getSignature() & pressedSignature) == command.getSignature()) { - // The modifier is currently pressed - auto remainingPressedBits = pressedSignature ^ command.getSignature(); + // Special case for modifiers: if the modifier key is held and a child key is repeating + if (command.isModifier() && (command.getSignature() & currentlyHeldKeys) == command.getSignature()) { + // Check if any child key is in the repeat signature + for (const auto& child : command.getChildren()) { + // If this child is directly repeating + if ((child.getSignature() & repeatSignature) == child.getSignature() && + child.getSignature() != command.getSignature()) { + child.executeRepeatCallback(); + } + } - // Process child commands with the remaining pressed bits and ALL released bits - // This ensures child release events work while modifier is held - processCommands(command.getChildren(), remainingPressedBits, releasedSignature); + // Also check deeper in the hierarchy + auto remainingBits = repeatSignature; + processRepeatCommands(command.getChildren(), remainingBits, currentlyHeldKeys); } - // Standard partial match cases - else if (command.partialMatch(pressedSignature)) { - auto remainingPressedBits = pressedSignature ^ command.getSignature(); - processCommands(command.getChildren(), remainingPressedBits, releasedSignature); - } - else if (command.partialMatch(releasedSignature)) { - auto remainingReleasedBits = releasedSignature ^ command.getSignature(); - processCommands(command.getChildren(), pressedSignature, remainingReleasedBits); + // Standard partial match handling + else if (command.partialMatch(repeatSignature)) { + auto remainingBits = repeatSignature ^ command.getSignature(); + if (remainingBits.any()) { + processRepeatCommands(command.getChildren(), remainingBits, currentlyHeldKeys); + } } } } diff --git a/editor/src/inputs/InputManager.hpp b/editor/src/inputs/InputManager.hpp index 3abeae8d6..a5d76b765 100644 --- a/editor/src/inputs/InputManager.hpp +++ b/editor/src/inputs/InputManager.hpp @@ -40,13 +40,18 @@ namespace nexo::editor { private: // Current and previous key states for detecting changes std::bitset m_currentKeyState; - std::bitset m_previousKeyState; + std::bitset m_keyWasDownLastFrame; // Recursive command matching void processCommands(const std::span& commands, const std::bitset& pressedSignature, const std::bitset& releasedSignature ); + void processRepeatCommands( + const std::span& commands, + const std::bitset& repeatSignature, + const std::bitset& currentlyHeldKeys + ); void collectPossibleCommands( const std::span& commands, const std::bitset& pressedSignature, From 7d17c4de6b4a56ba0802daba53a5432fabb1d4e1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Apr 2025 14:38:26 +0900 Subject: [PATCH 205/450] feat(document-windows): add exclude axis + switch local/global shortcuts --- editor/src/DocumentWindows/EditorScene.cpp | 175 ++++++++++++++++++++- 1 file changed, 172 insertions(+), 3 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 601e8e1d2..da22804ea 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -58,6 +58,7 @@ namespace nexo::editor { "Shift", nullptr, nullptr, + nullptr, true, { { @@ -67,6 +68,7 @@ namespace nexo::editor { this->m_popupManager.openPopup("Add new entity popup"); }, nullptr, + nullptr, false, } } @@ -80,6 +82,7 @@ namespace nexo::editor { LOG(NEXO_WARN, "Select all not implemented yet"); }, nullptr, + nullptr, false } ); @@ -100,6 +103,7 @@ namespace nexo::editor { this->m_windowState = m_globalState; }, nullptr, + nullptr, false, } ); @@ -112,6 +116,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, nullptr, + nullptr, false, } ); @@ -124,6 +129,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }, nullptr, + nullptr, false, } ); @@ -136,6 +142,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, nullptr, + nullptr, false, } ); @@ -145,6 +152,7 @@ namespace nexo::editor { "Shift", nullptr, nullptr, + nullptr, true, { { @@ -158,6 +166,7 @@ namespace nexo::editor { m_snapTranslateOn = false; m_snapRotateOn = false; }, + nullptr, false, } } @@ -175,6 +184,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; }, nullptr, + nullptr, false, } ); @@ -187,6 +197,12 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, nullptr, + [this]{ + if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) + this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; + else + this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; + }, false, } ); @@ -199,6 +215,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }, nullptr, + nullptr, false, } ); @@ -211,6 +228,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, nullptr, + nullptr, false, } ); @@ -220,8 +238,45 @@ namespace nexo::editor { "Shift", nullptr, nullptr, + nullptr, true, { + { + "Exclude X", + "X", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_X); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_X); + }, + nullptr, + false, + }, + { + "Exclude Y", + "Y", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Y); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Y); + }, + nullptr, + false, + }, + { + "Exclude Z", + "Z", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Z); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Z); + }, + nullptr, + false, + }, { "Toggle snapping", "S", @@ -231,6 +286,7 @@ namespace nexo::editor { [this]{ m_snapTranslateOn = false; }, + nullptr, false, } } @@ -246,6 +302,7 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, + nullptr, false, } ); @@ -259,6 +316,7 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, + nullptr, false, } ); @@ -272,6 +330,7 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, + nullptr, false, } ); @@ -287,6 +346,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; }, nullptr, + nullptr, false, } ); @@ -297,8 +357,14 @@ namespace nexo::editor { [this]{ this->m_windowState = m_gizmoRotateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, + }, // << Key pressed + nullptr, // << Key released + [this]{ + if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) + this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; + else + this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; + }, // << Key repeat false, } ); @@ -311,6 +377,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, nullptr, + nullptr, false, } ); @@ -323,6 +390,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, nullptr, + nullptr, false, } ); @@ -332,8 +400,45 @@ namespace nexo::editor { "Shift", nullptr, nullptr, + nullptr, true, { + { + "Exclude X", + "X", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_X); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_X); + }, + nullptr, + false, + }, + { + "Exclude Y", + "Y", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Y); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Y); + }, + nullptr, + false, + }, + { + "Exclude Z", + "Z", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Z); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Z); + }, + nullptr, + false, + }, { "Toggle snapping", "S", @@ -343,8 +448,9 @@ namespace nexo::editor { [this]{ m_snapRotateOn = false; }, + nullptr, false, - } + }, } } ); @@ -358,6 +464,7 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }, + nullptr, false, } ); @@ -371,6 +478,7 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }, + nullptr, false, } ); @@ -384,6 +492,7 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }, + nullptr, false, } ); @@ -399,6 +508,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; }, nullptr, + nullptr, false, } ); @@ -411,6 +521,12 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, nullptr, + [this]{ + if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) + this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; + else + this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; + }, false, } ); @@ -423,6 +539,7 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; }, nullptr, + nullptr, false, } ); @@ -435,9 +552,58 @@ namespace nexo::editor { this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; }, nullptr, + nullptr, false, } ); + m_gizmoScaleState.registerCommand( + { + "Shift context", + "Shift", + nullptr, + nullptr, + nullptr, + true, + { + { + "Exclude X", + "X", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_X); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_X); + }, + nullptr, + false, + }, + { + "Exclude Y", + "Y", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Y); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Y); + }, + nullptr, + false, + }, + { + "Exclude Z", + "Z", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Z); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Z); + }, + nullptr, + false, + } + } + } + ); m_gizmoScaleState.registerCommand( { "Lock X", @@ -448,6 +614,7 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, + nullptr, false, } ); @@ -461,6 +628,7 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, + nullptr, false, } ); @@ -474,6 +642,7 @@ namespace nexo::editor { [this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; }, + nullptr, false, } ); From 708b9c7a8eedcee28d76814740062b51f6fe019a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Apr 2025 20:12:47 +0900 Subject: [PATCH 206/450] fix(document-windows): mag filter is now linear --- engine/src/core/scene/Scene.hpp | 1 + engine/src/renderer/opengl/OpenGlTexture2D.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/src/core/scene/Scene.hpp b/engine/src/core/scene/Scene.hpp index 4dffb8fbd..2c2415b1b 100644 --- a/engine/src/core/scene/Scene.hpp +++ b/engine/src/core/scene/Scene.hpp @@ -91,6 +91,7 @@ namespace nexo::scene { void setName(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() {return m_entities;}; private: unsigned int m_id = nextSceneId++; std::string m_sceneName; diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index a712c64df..e0b59c892 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -115,7 +115,7 @@ namespace nexo::renderer { glBindTexture(GL_TEXTURE_2D, m_id); glTexImage2D(GL_TEXTURE_2D, 0, static_cast(internalFormat), width, height, 0, dataFormat, GL_UNSIGNED_BYTE, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glBindTexture(GL_TEXTURE_2D, 0); From b8f8b960c6d2c9eaf4cfb739de371966c505f4a2 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Apr 2025 20:12:58 +0900 Subject: [PATCH 207/450] feat(document-windows): add multi selection --- editor/src/DocumentWindows/EditorScene.cpp | 206 +++++++++---- .../src/DocumentWindows/InspectorWindow.cpp | 115 +++---- .../src/DocumentWindows/MaterialInspector.cpp | 2 +- .../src/DocumentWindows/SceneTreeWindow.cpp | 242 ++++++++++----- editor/src/context/Selector.cpp | 222 +++++++++----- editor/src/context/Selector.hpp | 281 ++++++++++++------ 6 files changed, 736 insertions(+), 332 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index da22804ea..b0a11888f 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -79,7 +79,20 @@ namespace nexo::editor { "Select all", "A", [this]{ - LOG(NEXO_WARN, "Select all not implemented yet"); + auto &selector = Selector::get(); + auto &app = nexo::getApp(); + auto &scene = app.getSceneManager().getScene(m_sceneId); + + selector.clearSelection(); + + for (const auto entity : scene.getEntities()) { + if (entity == m_editorCamera) continue; // Skip editor camera + + const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); + if (uuidComponent) + selector.addToSelection(uuidComponent->get().uuid, entity); + } + m_windowState = m_gizmoState; }, nullptr, nullptr, @@ -95,12 +108,14 @@ namespace nexo::editor { "Delete", "X", [this]{ - auto &selector = Selector::get(); - ecs::Entity selectedEntity = selector.getSelectedEntity(); - selector.unselectEntity(); - auto &app = nexo::getApp(); - app.deleteEntity(selectedEntity); - this->m_windowState = m_globalState; + auto &selector = Selector::get(); + const auto &selectedEntities = selector.getSelectedEntities(); + auto &app = nexo::getApp(); + + for (const auto entity : selectedEntities) + app.deleteEntity(entity); + selector.clearSelection(); + this->m_windowState = m_globalState; }, nullptr, nullptr, @@ -922,7 +937,7 @@ namespace nexo::editor { if (m_activeCamera == m_editorCamera) { if (renderToolbarButton("editor_camera", ICON_FA_CAMERA, "Edit Editor Camera Setting", m_buttonGradient)) { const auto &uuidComponent = app.m_coordinator->getComponent(m_editorCamera); - selector.setSelectedEntity(uuidComponent.uuid, m_editorCamera); + selector.addToSelection(uuidComponent.uuid, m_editorCamera); } } else { if (renderToolbarButton("switch_back", ICON_FA_EXCHANGE, "Switch back to editor camera", m_buttonGradient)) { @@ -1136,21 +1151,44 @@ namespace nexo::editor { { const auto &coord = nexo::Application::m_coordinator; auto const &selector = Selector::get(); - if (selector.getSelectionType() == SelectionType::SCENE || - selector.getSelectedScene() != m_sceneId) + if (selector.getPrimarySelectionType() == SelectionType::SCENE || + selector.getSelectedScene() != m_sceneId || + !selector.hasSelection()) + return; + + // Get all selected entities and find the first one with a transform component + const auto &selectedEntities = selector.getSelectedEntities(); + ecs::Entity targetEntity = selector.getPrimaryEntity(); + auto transf = coord->tryGetComponent(targetEntity); + + if (!transf) { + for (const auto entity : selectedEntities) { + if (entity != targetEntity) { + auto entityTransf = coord->tryGetComponent(entity); + if (entityTransf) { + targetEntity = entity; + transf = entityTransf; + break; + } + } + } + } + + // Still no entity with transform component found + if (!transf) return; - const ecs::Entity entity = selector.getSelectedEntity(); + const auto &transformCameraComponent = coord->getComponent(m_activeCamera); auto &cameraComponent = coord->getComponent(m_activeCamera); + ImGuizmo::SetOrthographic(cameraComponent.type == components::CameraType::ORTHOGRAPHIC); ImGuizmo::SetDrawlist(); - ImGuizmo::SetID(static_cast(entity)); + ImGuizmo::SetID(static_cast(targetEntity)); ImGuizmo::SetRect(m_viewPosition.x, m_viewPosition.y, m_viewSize.x, m_viewSize.y); + glm::mat4 viewMatrix = cameraComponent.getViewMatrix(transformCameraComponent); glm::mat4 projectionMatrix = cameraComponent.getProjectionMatrix(); - const auto transf = coord->tryGetComponent(entity); - if (!transf) - return; + const glm::mat4 rotationMat = glm::toMat4(transf->get().quat); glm::mat4 transformMatrix = glm::translate(glm::mat4(1.0f), transf->get().pos) * rotationMat * @@ -1159,7 +1197,6 @@ namespace nexo::editor { static ImGuizmo::OPERATION lastOperation; if (!ImGuizmo::IsUsing()) { lastOperation = getLastGuizmoOperation(); - //m_windowState = m_globalState; } float *snap = nullptr; @@ -1176,20 +1213,54 @@ namespace nexo::editor { glm::value_ptr(transformMatrix), nullptr, snap); - glm::vec3 translation(0); - glm::vec3 scale(0); - glm::quat quaternion; - - math::decomposeTransformQuat(transformMatrix, translation, quaternion, scale); - if (ImGuizmo::IsUsing()) { cameraComponent.active = false; - transf->get().pos = translation; - transf->get().quat = quaternion; - transf->get().size = scale; - } else + + glm::vec3 originalPos = transf->get().pos; + glm::quat originalRot = transf->get().quat; + glm::vec3 originalScale = transf->get().size; + + glm::vec3 newPos; + glm::vec3 newScale; + glm::quat newRot; + + math::decomposeTransformQuat(transformMatrix, newPos, newRot, newScale); + + // Calculate deltas + glm::vec3 positionDelta = newPos - originalPos; + glm::vec3 scaleFactor = newScale / originalScale; // Element-wise division + glm::quat rotationDelta = newRot * glm::inverse(originalRot); + + // Apply transformation to target entity + transf->get().pos = newPos; + transf->get().quat = newRot; + transf->get().size = newScale; + + // Apply same transformation to all other selected entities with transform components + for (const auto entity : selectedEntities) { + if (entity == targetEntity) continue; // Skip target entity, already transformed + + auto entityTransf = coord->tryGetComponent(entity); + if (entityTransf) { + if (m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE) + entityTransf->get().pos += positionDelta; + + + if (m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE) + entityTransf->get().quat = rotationDelta * entityTransf->get().quat; + + + if (m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE) { + entityTransf->get().size.x *= scaleFactor.x; + entityTransf->get().size.y *= scaleFactor.y; + entityTransf->get().size.z *= scaleFactor.z; + } + } + } + } else { cameraComponent.active = true; + } } void EditorScene::renderView() @@ -1246,7 +1317,7 @@ namespace nexo::editor { if (m_focused && selector.getSelectedScene() != m_sceneId) { selector.setSelectedScene(m_sceneId); - selector.unselectEntity(); + selector.clearSelection(); } if (m_activeCamera == -1) @@ -1321,54 +1392,87 @@ namespace nexo::editor { void EditorScene::update() { - auto &selector = Selector::get(); - //m_windowName = selector.getUiHandle(m_sceneUuid, m_windowName); + auto &selector = Selector::get(); if (!m_opened || m_activeCamera == -1) return; if (m_focused && m_hovered) handleKeyEvents(); SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; runEngine(m_sceneId, RenderingType::FRAMEBUFFER, sceneType); + auto const &cameraComponent = Application::m_coordinator->getComponent(static_cast(m_activeCamera)); + + // Handle mouse clicks for selection if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsUsing() && m_focused) { auto [mx, my] = ImGui::GetMousePos(); mx -= m_viewportBounds[0].x; my -= m_viewportBounds[0].y; - // Flip the y-coordinate to match opengl texture format (maybe make it modular in some way) + // Flip the y-coordinate to match opengl texture format my = m_viewSize.y - my; - // Mouse is not inside the viewport + // Check if mouse is inside viewport if (!(mx >= 0 && my >= 0 && mx < m_viewSize.x && my < m_viewSize.y)) return; cameraComponent.m_renderTarget->bind(); - int data = cameraComponent.m_renderTarget->getPixel(1, static_cast(mx), static_cast(my)); + int entityId = cameraComponent.m_renderTarget->getPixel(1, static_cast(mx), static_cast(my)); cameraComponent.m_renderTarget->unbind(); - if (data == -1) - { - selector.unselectEntity(); - m_windowState = m_globalState; - return; + + // Check for multi-selection key modifiers + bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); + bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + + if (entityId == -1) { + // Clicked on empty space - clear selection unless shift/ctrl is held + if (!isShiftPressed && !isCtrlPressed) { + selector.clearSelection(); + m_windowState = m_globalState; + } + return; } - const auto uuid = Application::m_coordinator->tryGetComponent(data); - if (uuid) - { - if (m_currentGizmoOperation == ImGuizmo::OPERATION::TRANSLATE) - m_windowState = m_gizmoTranslateState; - else if (m_currentGizmoOperation == ImGuizmo::OPERATION::ROTATE) - m_windowState = m_gizmoRotateState; - else - m_windowState = m_gizmoState; + + const auto uuid = Application::m_coordinator->tryGetComponent(entityId); + if (uuid) { auto &app = getApp(); - selector.setSelectedEntity(uuid->get().uuid, data); - if (app.m_coordinator->entityHasComponent(data)) - selector.setSelectionType(SelectionType::CAMERA); + + // Determine selection type + SelectionType selType = SelectionType::ENTITY; + if (app.m_coordinator->entityHasComponent(entityId)) { + selType = SelectionType::CAMERA; + } else if (app.m_coordinator->entityHasComponent(entityId)) { + selType = SelectionType::DIR_LIGHT; + } else if (app.m_coordinator->entityHasComponent(entityId)) { + selType = SelectionType::POINT_LIGHT; + } else if (app.m_coordinator->entityHasComponent(entityId)) { + selType = SelectionType::SPOT_LIGHT; + } else if (app.m_coordinator->entityHasComponent(entityId)) { + selType = SelectionType::AMBIENT_LIGHT; + } + + // Handle different selection modes + if (isCtrlPressed) + selector.toggleSelection(uuid->get().uuid, entityId, selType); + else if (isShiftPressed) + selector.addToSelection(uuid->get().uuid, entityId, selType); else - selector.setSelectionType(SelectionType::ENTITY); + selector.selectEntity(uuid->get().uuid, entityId, selType); + + // Update window state based on primary selection's transform mode + if (selector.hasSelection()) { + if (m_currentGizmoOperation == ImGuizmo::OPERATION::TRANSLATE) + m_windowState = m_gizmoTranslateState; + else if (m_currentGizmoOperation == ImGuizmo::OPERATION::ROTATE) + m_windowState = m_gizmoRotateState; + else if (m_currentGizmoOperation == ImGuizmo::OPERATION::SCALE) + m_windowState = m_gizmoScaleState; + else + m_windowState = m_gizmoState; + } + + selector.setSelectedScene(m_sceneId); } - selector.setSelectedScene(m_sceneId); } } diff --git a/editor/src/DocumentWindows/InspectorWindow.cpp b/editor/src/DocumentWindows/InspectorWindow.cpp index a19139f21..675eb16d8 100644 --- a/editor/src/DocumentWindows/InspectorWindow.cpp +++ b/editor/src/DocumentWindows/InspectorWindow.cpp @@ -1,4 +1,3 @@ -//// InspectorWindow.cpp ////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -41,23 +40,23 @@ extern ImGuiID g_materialInspectorDockID; namespace nexo::editor { - InspectorWindow::~InspectorWindow() = default; + InspectorWindow::~InspectorWindow() = default; void InspectorWindow::setup() { - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); } void InspectorWindow::shutdown() { - // Nothing to clear for now + // Nothing to clear for now } void InspectorWindow::show() @@ -65,18 +64,24 @@ namespace nexo::editor ImGui::Begin(ICON_FA_SLIDERS " Inspector" NEXO_WND_USTRID_INSPECTOR, &m_opened, ImGuiWindowFlags_NoCollapse); firstDockSetup(NEXO_WND_USTRID_INSPECTOR); auto const &selector = Selector::get(); - const int selectedEntity = selector.getSelectedEntity(); - if (selectedEntity != -1) - { - if (selector.getSelectionType() == SelectionType::SCENE) - { - showSceneProperties(selectedEntity); - } - else - { - showEntityProperties(selectedEntity); + if (selector.getPrimarySelectionType() == SelectionType::SCENE) { + // Scene selection stays the same - only show the selected scene + showSceneProperties(selector.getSelectedScene()); + } + else if (selector.hasSelection()) { + const ecs::Entity primaryEntity = selector.getPrimaryEntity(); + + 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()); + ImGui::PopStyleColor(); + ImGui::Separator(); } + + showEntityProperties(primaryEntity); } ImGui::End(); @@ -84,39 +89,39 @@ namespace nexo::editor void InspectorWindow::showSceneProperties(const scene::SceneId sceneId) const { - 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(), ""); - - // Remove the icon prefix - if (size_t spacePos = uiHandle.find(' '); spacePos != std::string::npos) - uiHandle = uiHandle.substr(spacePos + 1); - - if (ImNexo::Header("##SceneNode", uiHandle)) - { - ImGui::Spacing(); - ImGui::Columns(2, "sceneProps"); - ImGui::SetColumnWidth(0, 80); - - ImGui::Text("Hide"); - ImGui::NextColumn(); - bool hidden = !scene.isRendered(); - ImGui::Checkbox("##HideCheckBox", &hidden); - scene.setRenderStatus(!hidden); - ImGui::NextColumn(); - - ImGui::Text("Pause"); - ImGui::NextColumn(); - bool paused = !scene.isActive(); - ImGui::Checkbox("##PauseCheckBox", &paused); - scene.setActiveStatus(!paused); - ImGui::NextColumn(); - - ImGui::Columns(1); - ImGui::TreePop(); - } + 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(), ""); + + // Remove the icon prefix + if (size_t spacePos = uiHandle.find(' '); spacePos != std::string::npos) + uiHandle = uiHandle.substr(spacePos + 1); + + if (ImNexo::Header("##SceneNode", uiHandle)) + { + ImGui::Spacing(); + ImGui::Columns(2, "sceneProps"); + ImGui::SetColumnWidth(0, 80); + + ImGui::Text("Hide"); + ImGui::NextColumn(); + bool hidden = !scene.isRendered(); + ImGui::Checkbox("##HideCheckBox", &hidden); + scene.setRenderStatus(!hidden); + ImGui::NextColumn(); + + ImGui::Text("Pause"); + ImGui::NextColumn(); + bool paused = !scene.isActive(); + ImGui::Checkbox("##PauseCheckBox", &paused); + scene.setActiveStatus(!paused); + ImGui::NextColumn(); + + ImGui::Columns(1); + ImGui::TreePop(); + } } void InspectorWindow::showEntityProperties(const ecs::Entity entity) @@ -133,6 +138,6 @@ namespace nexo::editor void InspectorWindow::update() { - // Nothing to update here + // Nothing to update here } } diff --git a/editor/src/DocumentWindows/MaterialInspector.cpp b/editor/src/DocumentWindows/MaterialInspector.cpp index ca8861371..8a95335b2 100644 --- a/editor/src/DocumentWindows/MaterialInspector.cpp +++ b/editor/src/DocumentWindows/MaterialInspector.cpp @@ -86,7 +86,7 @@ namespace nexo::editor { void MaterialInspector::show() { auto const &selector = Selector::get(); - const int selectedEntity = selector.getSelectedEntity(); + const int selectedEntity = selector.getPrimaryEntity(); auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); if (!inspectorWindow) return; diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index 5e0e2ab71..43a8ebfdf 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -1,4 +1,3 @@ -//// SceneTreeWindow.cpp ////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -105,11 +104,19 @@ namespace nexo::editor { const bool nodeOpen = ImGui::TreeNodeEx(uniqueLabel.c_str(), baseFlags); if (!nodeOpen) return nodeOpen; + if (ImGui::IsItemClicked()) { auto &selector = Selector::get(); - selector.setSelectedEntity(obj.uuid, obj.data.entity); - selector.setSelectionType(obj.type); + bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); + bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + + if (isCtrlPressed) + selector.toggleSelection(obj.uuid, obj.data.entity, obj.type); + else if (isShiftPressed) + selector.addToSelection(obj.uuid, obj.data.entity, obj.type); + else + selector.selectEntity(obj.uuid, obj.data.entity, obj.type); selector.setSelectedScene(obj.data.sceneProperties.sceneId); } return nodeOpen; @@ -120,7 +127,7 @@ namespace nexo::editor { if (ImGui::MenuItem("Delete Scene")) { auto &app = Application::getInstance(); auto &selector = Selector::get(); - selector.unselectEntity(); + selector.clearSelection(); const std::string &sceneName = selector.getUiHandle(obj.uuid, obj.uiName); m_windowRegistry.unregisterWindow(sceneName); app.getSceneManager().deleteScene(obj.data.sceneProperties.sceneId); @@ -187,10 +194,28 @@ namespace nexo::editor { { auto &app = Application::getInstance(); auto &selector = Selector::get(); - if (ImGui::MenuItem("Delete Light")) + + // Check if we're operating on a single item or multiple items + const auto& selectedEntities = selector.getSelectedEntities(); + bool multipleSelected = selectedEntities.size() > 1; + + std::string menuText = multipleSelected ? + "Delete Selected Lights (" + std::to_string(selectedEntities.size()) + ")" : + "Delete Light"; + + if (ImGui::MenuItem(menuText.c_str())) { - selector.unselectEntity(); - app.deleteEntity(obj.data.entity); + if (multipleSelected) { + // Delete all selected lights + for (const auto& entityId : selectedEntities) { + app.deleteEntity(entityId); + } + selector.clearSelection(); + } else { + // Delete just this light + selector.clearSelection(); + app.deleteEntity(obj.data.entity); + } } } @@ -218,16 +243,34 @@ namespace nexo::editor { void SceneTreeWindow::cameraSelected(const SceneObject &obj) const { - auto &app = Application::getInstance(); - auto &selector = Selector::get(); - if (ImGui::MenuItem("Delete Camera")) + 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(); + bool multipleSelected = selectedEntities.size() > 1; + + std::string deleteMenuText = multipleSelected ? + "Delete Selected Cameras (" + std::to_string(selectedEntities.size()) + ")" : + "Delete Camera"; + + if (ImGui::MenuItem(deleteMenuText.c_str())) { - const auto &scenes = m_windowRegistry.getWindows(); - selector.unselectEntity(); - app.deleteEntity(obj.data.entity); + if (multipleSelected) { + // Delete all selected cameras + for (const auto& entityId : selectedEntities) { + app.deleteEntity(entityId); + } + selector.clearSelection(); + } else { + // Delete just this camera + selector.clearSelection(); + app.deleteEntity(obj.data.entity); + } } - if (ImGui::MenuItem("Switch to")) + // Switch to camera only makes sense for a single camera + if (!multipleSelected && ImGui::MenuItem("Switch to")) { auto &cameraComponent = app.m_coordinator->getComponent(obj.data.entity); cameraComponent.render = true; @@ -244,12 +287,30 @@ namespace nexo::editor { void SceneTreeWindow::entitySelected(const SceneObject &obj) const { - if (ImGui::MenuItem("Delete Entity")) + auto &selector = Selector::get(); + auto &app = nexo::getApp(); + + // Check if we're operating on a single item or multiple items + const auto& selectedEntities = selector.getSelectedEntities(); + bool multipleSelected = selectedEntities.size() > 1; + + std::string menuText = multipleSelected ? + "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : + "Delete Entity"; + + if (ImGui::MenuItem(menuText.c_str())) { - auto &selector = Selector::get(); - selector.unselectEntity(); - auto &app = nexo::getApp(); - app.deleteEntity(obj.data.entity); + if (multipleSelected) { + // Delete all selected entities + for (const auto& entityId : selectedEntities) { + app.deleteEntity(entityId); + } + selector.clearSelection(); + } else { + // Delete just this entity + selector.clearSelection(); + app.deleteEntity(obj.data.entity); + } } } @@ -262,8 +323,11 @@ namespace nexo::editor { if (leaf) baseFlags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; - // Checks if the object is selected - if (auto const &selector = Selector::get(); selector.isEntitySelected() && object.uuid == selector.getSelectedUuid()) + // Check if this object is selected + auto const &selector = Selector::get(); + bool isSelected = selector.isEntitySelected(object.data.entity); + + if (isSelected) baseFlags |= ImGuiTreeNodeFlags_Selected; bool nodeOpen = false; @@ -280,12 +344,14 @@ namespace nexo::editor { // Handles the right click on each different type of object if (object.type != SelectionType::NONE && ImGui::BeginPopupContextItem(uniqueLabel.c_str())) { - // Renaming works on every object excepts entities and cameras - if (ImGui::MenuItem("Rename")) + // Only show rename option for the primary selected entity or for non-selected entities + if ((!isSelected || (isSelected && selector.getPrimaryEntity() == object.data.entity)) && + ImGui::MenuItem("Rename")) { m_renameTarget = {object.type, object.uuid}; m_renameBuffer = object.uiName; } + if (object.type == SelectionType::SCENE) sceneSelected(object); else if (object.type == SelectionType::DIR_LIGHT || object.type == SelectionType::POINT_LIGHT || object.type == SelectionType::SPOT_LIGHT) @@ -422,23 +488,57 @@ namespace nexo::editor { if (ImGui::Begin(ICON_FA_SITEMAP " Scene Tree" NEXO_WND_USTRID_SCENE_TREE, &m_opened, ImGuiWindowFlags_NoCollapse)) { - firstDockSetup(NEXO_WND_USTRID_SCENE_TREE); + firstDockSetup(NEXO_WND_USTRID_SCENE_TREE); + + auto &selector = Selector::get(); + bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + + // Ctrl+A to select all entities in current scene + if (isCtrlPressed && ImGui::IsKeyPressed(ImGuiKey_A) && ImGui::IsWindowFocused()) { + // Get current scene ID + int currentSceneId = selector.getSelectedScene(); + if (currentSceneId != -1) { + auto &app = nexo::getApp(); + auto &scene = app.getSceneManager().getScene(currentSceneId); + + selector.clearSelection(); + + // Add all entities in the scene to selection + for (const auto entity : scene.getEntities()) { + const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); + if (uuidComponent) { + selector.addToSelection(uuidComponent->get().uuid, entity); + } + } + } + } + // Opens the right click popup when no items are hovered if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered( - ImGuiHoveredFlags_AllowWhenBlockedByPopup) && !ImGui::IsAnyItemHovered()) + ImGuiHoveredFlags_AllowWhenBlockedByPopup) && !ImGui::IsAnyItemHovered()) { m_popupManager.openPopup("Scene Tree Context Menu"); + } + + // Display multi-selection count at top of window if applicable + const auto& selectedEntities = selector.getSelectedEntities(); + if (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); } sceneContextMenu(); sceneCreationMenu(); - - } ImGui::End(); } + // Node creation methods SceneObject SceneTreeWindow::newSceneNode(const std::string &sceneName, const scene::SceneId sceneId, const WindowId uiId) const { SceneObject sceneNode; @@ -455,53 +555,53 @@ namespace nexo::editor { void SceneTreeWindow::newLightNode(SceneObject &lightNode, const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity, const std::string &uiName) const { - const SceneProperties sceneProperties{sceneId, uiId}; - lightNode.data.sceneProperties = sceneProperties; + const SceneProperties sceneProperties{sceneId, uiId}; + lightNode.data.sceneProperties = sceneProperties; lightNode.data.entity = lightEntity; auto &selector = Selector::get(); const auto entityUuid = Application::m_coordinator->tryGetComponent(lightEntity); if (entityUuid) { - lightNode.uuid = entityUuid->get().uuid; - lightNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); + lightNode.uuid = entityUuid->get().uuid; + lightNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); } else - lightNode.uiName = uiName; + lightNode.uiName = uiName; } SceneObject SceneTreeWindow::newAmbientLightNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity) const { - SceneObject lightNode; - lightNode.type = SelectionType::AMBIENT_LIGHT; + SceneObject lightNode; + 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; + newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); + return lightNode; } SceneObject SceneTreeWindow::newDirectionalLightNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity) { - SceneObject lightNode; - lightNode.type = SelectionType::DIR_LIGHT; + SceneObject lightNode; + lightNode.type = SelectionType::DIR_LIGHT; const std::string uiName = std::format("{}Directional light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbDirLights); - newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); - return lightNode; + newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); + return lightNode; } 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); - newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); - return lightNode; + SceneObject lightNode; + lightNode.type = SelectionType::SPOT_LIGHT; + 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 lightNode; - lightNode.type = SelectionType::POINT_LIGHT; - const std::string uiName = std::format("{}Point light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbPointLights); - newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); - return lightNode; + SceneObject lightNode; + lightNode.type = SelectionType::POINT_LIGHT; + const std::string uiName = std::format("{}Point light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbPointLights); + newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); + return lightNode; } SceneObject SceneTreeWindow::newCameraNode(const scene::SceneId sceneId, const WindowId uiId, @@ -517,17 +617,17 @@ namespace nexo::editor { const auto entityUuid = nexo::Application::m_coordinator->tryGetComponent(cameraEntity); if (entityUuid) { - cameraNode.uuid = entityUuid->get().uuid; - cameraNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); + cameraNode.uuid = entityUuid->get().uuid; + cameraNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); } else - cameraNode.uiName = uiName; + cameraNode.uiName = uiName; return cameraNode; } SceneObject SceneTreeWindow::newEntityNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) const { - auto &selector = Selector::get(); + auto &selector = Selector::get(); SceneObject entityNode; const std::string uiName = std::format("{}{}", ObjectTypeToIcon.at(SelectionType::ENTITY), entity); entityNode.type = SelectionType::ENTITY; @@ -536,11 +636,11 @@ namespace nexo::editor { const auto entityUuid = nexo::Application::m_coordinator->tryGetComponent(entity); if (entityUuid) { - entityNode.uuid = entityUuid->get().uuid; - entityNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); + entityNode.uuid = entityUuid->get().uuid; + entityNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); } else - entityNode.uiName = uiName; + entityNode.uiName = uiName; return entityNode; } @@ -559,33 +659,33 @@ namespace nexo::editor { std::map sceneNodes; for (const auto &scene : scenes) { - sceneNodes[scene->getSceneId()] = newSceneNode(scene->getWindowName(), scene->getSceneId(), windowId); + sceneNodes[scene->getSceneId()] = newSceneNode(scene->getWindowName(), scene->getSceneId(), windowId); } 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 this->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 this->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 this->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 this->newSpotLightNode(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 this->newCameraNode(sceneId, uiId, entity); }); @@ -596,14 +696,14 @@ namespace nexo::editor { ecs::Exclude, ecs::Exclude, ecs::Exclude>( - 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 this->newEntityNode(sceneId, uiId, entity); }); for (const auto &[_, sceneNode] : sceneNodes) { - root_.children.push_back(sceneNode); + root_.children.push_back(sceneNode); } } } diff --git a/editor/src/context/Selector.cpp b/editor/src/context/Selector.cpp index 6957a7a6c..3e2c3624b 100644 --- a/editor/src/context/Selector.cpp +++ b/editor/src/context/Selector.cpp @@ -17,78 +17,164 @@ #include "components/Editor.hpp" namespace nexo::editor { - int Selector::getSelectedEntity() const - { - return m_selectedEntity; - } - - const std::string &Selector::getSelectedUuid() const - { - return m_selectedUuid; - } - - void Selector::setSelectedEntity(std::string_view uuid, const int entity) - { - if (m_selectionType != SelectionType::NONE && m_selectionType != SelectionType::SCENE) - { - Application::m_coordinator->removeComponent(m_selectedEntity); - } - m_selectedUuid = uuid; - m_selectedEntity = entity; - components::SelectedTag selectTag{}; - Application::m_coordinator->addComponent(m_selectedEntity, selectTag); - } - void Selector::setSelectedScene(int scene) - { - m_selectedScene = scene; - } + int Selector::getPrimaryEntity() const + { + if (m_selectedEntities.empty()) { + return -1; + } + return m_selectedEntities.front().entityId; // First entity is the primary + } + + const std::vector& Selector::getSelectedEntities() const + { + static std::vector entityIds; + entityIds.clear(); + + for (const auto& data : m_selectedEntities) { + entityIds.push_back(data.entityId); + } + + return entityIds; + } - int Selector::getSelectedScene() const - { - return m_selectedScene; - } + const std::string& Selector::getPrimaryUuid() const + { + static std::string emptyString; + if (m_selectedEntities.empty()) { + return emptyString; + } + return m_selectedEntities.front().uuid; + } - void Selector::unselectEntity() + std::vector Selector::getSelectedUuids() const { - if (m_selectionType != SelectionType::NONE && m_selectionType != SelectionType::SCENE) - { - Application::m_coordinator->removeComponent(m_selectedEntity); + std::vector uuids; + uuids.reserve(m_selectedEntities.size()); + + for (const auto& data : m_selectedEntities) { + uuids.push_back(data.uuid); } - m_selectionType = SelectionType::NONE; - m_selectedEntity = -1; - m_selectedUuid = ""; - } - - SelectionType Selector::getSelectionType() const - { - return m_selectionType; - } - - void Selector::setSelectionType(SelectionType type) - { - m_selectionType = type; - } - - bool Selector::isEntitySelected() const - { - return (m_selectedEntity != -1); - } - - const std::string &Selector::getUiHandle(const std::string &uuid, const std::string &defaultHandle) - { - const auto it = m_uiHandles.find(uuid); - if (it == m_uiHandles.end()) - { - m_uiHandles[uuid] = defaultHandle; - return defaultHandle; - } - return it->second; - } - - void Selector::setUiHandle(const std::string &uuid, std::string_view handle) - { - m_uiHandles[uuid] = handle; - } + return uuids; + } + + void Selector::selectEntity(std::string_view uuid, int entity, SelectionType type) + { + clearSelection(); + addToSelection(uuid, entity, type); + } + + bool Selector::addToSelection(std::string_view uuid, int entity, SelectionType type) + { + if (m_selectedEntityIds.find(entity) != m_selectedEntityIds.end()) + return false; + + SelectionData data = { + .entityId = entity, + .uuid = std::string(uuid), + .type = type + }; + m_selectedEntities.push_back(std::move(data)); + m_selectedEntityIds.insert(entity); + + addSelectedTag(entity); + return true; + } + + bool Selector::toggleSelection(std::string_view uuid, int entity, SelectionType type) + { + if (isEntitySelected(entity)) { + removeFromSelection(entity); + return false; + } else { + addToSelection(uuid, entity, type); + return true; + } + } + + bool Selector::removeFromSelection(int entity) { + if (m_selectedEntityIds.find(entity) == m_selectedEntityIds.end()) + return false; + + m_selectedEntityIds.erase(entity); + for (auto it = m_selectedEntities.begin(); it != m_selectedEntities.end(); ++it) { + if (it->entityId == entity) { + m_selectedEntities.erase(it); + break; + } + } + + removeSelectedTag(entity); + return true; + } + + void Selector::setSelectedScene(int scene) + { + m_selectedScene = scene; + } + + int Selector::getSelectedScene() const + { + return m_selectedScene; + } + + void Selector::clearSelection() + { + for (const auto& data : m_selectedEntities) + removeSelectedTag(data.entityId); + + m_selectedEntities.clear(); + m_selectedEntityIds.clear(); + } + + bool Selector::isEntitySelected(int entity) const + { + return m_selectedEntityIds.find(entity) != m_selectedEntityIds.end(); + } + + bool Selector::hasSelection() const + { + return !m_selectedEntities.empty(); + } + + SelectionType Selector::getPrimarySelectionType() const + { + if (m_selectedEntities.empty()) { + return SelectionType::NONE; + } + return m_selectedEntities.front().type; + } + + void Selector::setSelectionType(SelectionType type) + { + m_defaultSelectionType = type; + } + + const std::string& Selector::getUiHandle(const std::string& uuid, const std::string& defaultHandle) + { + const auto it = m_uiHandles.find(uuid); + if (it == m_uiHandles.end()) { + m_uiHandles[uuid] = defaultHandle; + return defaultHandle; + } + return it->second; + } + + void Selector::setUiHandle(const std::string& uuid, std::string_view handle) + { + m_uiHandles[uuid] = handle; + } + + void Selector::addSelectedTag(int entity) + { + components::SelectedTag selectTag{}; + Application::m_coordinator->addComponent(entity, selectTag); + } + + void Selector::removeSelectedTag(int entity) + { + if (Application::m_coordinator->entityHasComponent(entity)) + Application::m_coordinator->removeComponent(entity); + } } diff --git a/editor/src/context/Selector.hpp b/editor/src/context/Selector.hpp index 4929defa9..fce22f707 100644 --- a/editor/src/context/Selector.hpp +++ b/editor/src/context/Selector.hpp @@ -16,99 +16,208 @@ #include "ecs/Coordinator.hpp" #include +#include +#include +#include namespace nexo::editor { - enum class SelectionType { - NONE, - SCENE, - CAMERA, - DIR_LIGHT, - AMBIENT_LIGHT, - SPOT_LIGHT, - POINT_LIGHT, - ENTITY - }; - - /** + enum class SelectionType { + NONE, + SCENE, + CAMERA, + DIR_LIGHT, + AMBIENT_LIGHT, + SPOT_LIGHT, + POINT_LIGHT, + ENTITY + }; + + /** * @class Selector * @brief Singleton class managing entity selection state in the editor * - * The Selector class tracks the currently selected entity, its type, and + * The Selector class tracks the currently selected entities, their types, and * provides methods to manipulate the selection state. It also maintains * entity UUID to UI handle mappings for consistent labeling in the interface. */ - class Selector { - public: - int getSelectedEntity() const; - const std::string &getSelectedUuid() const; - void setSelectedEntity(std::string_view uuid, int entity); - - /** - * @brief Sets the currently selected scene - * - * @param[in] scene The scene entity ID to select - */ - void setSelectedScene(int scene); - - /** - * @brief Gets the currently selected scene - * - * @return int The entity ID of the selected scene, or -1 if no scene is selected - */ - int getSelectedScene() const; - - void unselectEntity(); - - SelectionType getSelectionType() const; - void setSelectionType(SelectionType type); - - bool isEntitySelected() const; - - /** - * @brief Gets the UI handle associated with a UUID - * - * If the UUID doesn't have an associated handle yet, the default - * handle is stored and returned. - * - * @param[in] uuid The UUID to look up - * @param[in] defaultHandle The default handle to use if none exists - * @return const std::string& Reference to the UI handle for the UUID - */ - const std::string &getUiHandle(const std::string &uuid, const std::string &defaultHandle); - - /** - * @brief Sets the UI handle associated with a UUID - * - * @param[in] uuid The UUID to set the handle for - * @param[in] handle The handle to set - */ - void setUiHandle(const std::string &uuid, std::string_view handle); - - static Selector &get() - { - static Selector instance; - return instance; - } - - private: - std::string m_selectedUuid; - int m_selectedEntity = -1; - int m_selectedScene = -1; - SelectionType m_selectionType = SelectionType::NONE; - - struct TransparentHasher { - using is_transparent = void; // Marks this hasher as transparent for heterogeneous lookup - - size_t operator()(std::string_view key) const noexcept { - return std::hash{}(key); - } - - size_t operator()(const std::string &key) const noexcept { - return std::hash{}(key); - } - }; - - std::unordered_map> m_uiHandles; - }; + class Selector { + public: + /** + * @brief Gets the primary selected entity + * + * The primary entity is the entity that gizmos and other operations + * will primarily act on when multiple entities are selected. + * This returns the last entity selected if there are multiple selections. + * + * @return int The entity ID of the primary selection, or -1 if no entity is selected + */ + 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; + + /** + * @brief Gets the UUID of the primary entity + * + * @return const std::string& The UUID of the primary entity + */ + 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; + + /** + * @brief Selects a single entity, replacing the current selection + * + * @param[in] uuid The UUID of the entity to select + * @param[in] entity The entity ID to select + * @param[in] type The type of entity being selected + */ + void selectEntity(std::string_view uuid, int entity, SelectionType type = SelectionType::ENTITY); + + /** + * @brief Adds an entity to the current selection + * + * @param[in] uuid The UUID of the entity to add + * @param[in] entity The entity ID to add + * @param[in] type The type of entity being added + * @return true If the entity was successfully added to the selection + * @return false If the entity was already selected + */ + bool addToSelection(std::string_view uuid, int entity, SelectionType type = SelectionType::ENTITY); + + /** + * @brief Toggle selection state of an entity + * + * @param[in] uuid The UUID of the entity to toggle + * @param[in] entity The entity ID to toggle + * @param[in] type The type of entity being toggled + * @return true If the entity is now selected + * @return false If the entity is now deselected + */ + bool toggleSelection(std::string_view uuid, int entity, SelectionType type = SelectionType::ENTITY); + + /** + * @brief Removes an entity from the selection + * + * @param[in] entity The entity ID to remove + * @return true If the entity was successfully removed from the selection + * @return false If the entity wasn't selected + */ + bool removeFromSelection(int entity); + + /** + * @brief Sets the currently selected scene + * + * @param[in] scene The scene entity ID to select + */ + void setSelectedScene(int scene); + + /** + * @brief Gets the currently selected scene + * + * @return int The entity ID of the selected scene, or -1 if no scene is selected + */ + int getSelectedScene() const; + + /** + * @brief Clears the current entity selection + */ + void clearSelection(); + + /** + * @brief Checks if a specific entity is currently selected + * + * @param entity The entity ID to check + * @return true If the entity is selected + * @return false If the entity is not selected + */ + bool isEntitySelected(int entity) const; + + /** + * @brief Checks if any entity is currently selected + * + * @return true If at least one entity is selected + * @return false If no entities are selected + */ + bool hasSelection() const; + + /** + * @brief Gets the primary selection type + * + * @return SelectionType The type of the primary selected entity + */ + SelectionType getPrimarySelectionType() const; + + /** + * @brief Sets the selection type (only applied to subsequent selections) + * + * @param type The selection type to set + */ + void setSelectionType(SelectionType type); + + /** + * @brief Gets the UI handle associated with a UUID + * + * If the UUID doesn't have an associated handle yet, the default + * handle is stored and returned. + * + * @param[in] uuid The UUID to look up + * @param[in] defaultHandle The default handle to use if none exists + * @return const std::string& Reference to the UI handle for the UUID + */ + const std::string& getUiHandle(const std::string& uuid, const std::string& defaultHandle); + + /** + * @brief Sets the UI handle associated with a UUID + * + * @param[in] uuid The UUID to set the handle for + * @param[in] handle The handle to set + */ + void setUiHandle(const std::string& uuid, std::string_view handle); + + static Selector& get() { + static Selector instance; + return instance; + } + + 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 + + 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 { + return std::hash{}(key); + } + + size_t operator()(const std::string& key) const noexcept { + return std::hash{}(key); + } + }; + + std::unordered_map> m_uiHandles; + + void addSelectedTag(int entity); + void removeSelectedTag(int entity); + }; } From 11777e0f8470e26ad50b53cc656151f14c3c876a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 25 Apr 2025 20:24:41 +0900 Subject: [PATCH 208/450] feat(document-windows): add hide shortcuts --- editor/src/DocumentWindows/EditorScene.cpp | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index b0a11888f..14fde2bc8 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -27,6 +27,7 @@ #include "Texture.hpp" #include "WindowRegistry.hpp" #include "components/Camera.hpp" +#include "components/Render.hpp" #include "components/RenderContext.hpp" #include "components/Uuid.hpp" #include "components/Editor.hpp" @@ -74,6 +75,35 @@ namespace nexo::editor { } } ); + m_globalState.registerCommand( + { + "Control context", + "Ctrl", + nullptr, + nullptr, + nullptr, + true, + { + { + "Unhide all", + "H", + [this]{ + auto &app = getApp(); + const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); + for (const auto entity : entities) { + if (app.m_coordinator->entityHasComponent(entity)) { + auto &renderComponent = app.m_coordinator->getComponent(entity); + renderComponent.isRendered = true; + } + } + }, + nullptr, + nullptr, + false, + } + } + } + ); m_globalState.registerCommand( { "Select all", @@ -122,6 +152,28 @@ namespace nexo::editor { false, } ); + m_gizmoState.registerCommand( + { + "Hide", + "H", + [this]{ + auto &selector = Selector::get(); + const auto &selectedEntities = selector.getSelectedEntities(); + auto &app = nexo::getApp(); + + for (const auto entity : selectedEntities) { + if (app.m_coordinator->entityHasComponent(entity)) { + auto &renderComponent = app.m_coordinator->getComponent(entity); + renderComponent.isRendered = !renderComponent.isRendered; + } + } + selector.clearSelection(); + }, + nullptr, + nullptr, + false, + } + ); m_gizmoState.registerCommand( { "Translate", @@ -183,6 +235,24 @@ namespace nexo::editor { }, nullptr, false, + }, + { + "Hide all but selection", + "H", + [this]{ + auto &app = getApp(); + const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); + auto &selector = Selector::get(); + for (const auto entity : entities) { + if (app.m_coordinator->entityHasComponent(entity) && !selector.isEntitySelected(entity)) { + auto &renderComponent = app.m_coordinator->getComponent(entity); + renderComponent.isRendered = false; + } + } + }, + nullptr, + nullptr, + false, } } } From 78644f2e9b8525ecddc5b31e75203211510863f5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 17:59:56 +0900 Subject: [PATCH 209/450] feat(document-windows): add restore/save/Memento to components --- engine/src/components/Camera.hpp | 97 +++++++++++++++++++++++ engine/src/components/Light.hpp | 75 ++++++++++++++++++ engine/src/components/Render.hpp | 26 +++++- engine/src/components/SceneComponents.hpp | 16 ++++ engine/src/components/Transform.hpp | 16 ++++ engine/src/components/Uuid.hpp | 22 ++++- 6 files changed, 248 insertions(+), 4 deletions(-) diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index e09098a04..a0e62a79f 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -103,6 +103,48 @@ namespace nexo::components { m_renderTarget->resize(newWidth, newHeight); } } + + struct Memento { + unsigned int width; + unsigned int height; + bool viewportLocked; + float fov; + float nearPlane; + float farPlane; + CameraType type; + glm::vec4 clearColor; + bool active; + bool render; + bool main; + bool resizing; + std::shared_ptr renderTarget; + + CameraComponent restore() const + { + return {width, height, viewportLocked, fov, nearPlane, farPlane, type, clearColor, active, render, main, resizing, renderTarget}; + } + }; + + [[nodiscard]] Memento save() const + { + return { + width, + height, + viewportLocked, + fov, + nearPlane, + farPlane, + type, + clearColor, + active, + render, + main, + resizing, + m_renderTarget + }; + } + + }; struct EditorCameraTag {}; @@ -121,6 +163,35 @@ namespace nexo::components { float yaw = 0.0f; ///< Yaw angle in degrees. float pitch = 0.0f; ///< Pitch angle in degrees. float translationSpeed = 5.0f; ///< Camera speed + + struct Memento { + float mouseSensitivity; + float yaw; + float pitch; + float translationSpeed; + + PerspectiveCameraController restore() const + { + PerspectiveCameraController controller; + controller.mouseSensitivity = mouseSensitivity; + controller.yaw = yaw; + controller.pitch = pitch; + controller.translationSpeed = translationSpeed; + return controller; + } + }; + + [[nodiscard]] Memento save() const + { + return { + mouseSensitivity, + yaw, + pitch, + translationSpeed + }; + } + + }; /** @@ -133,6 +204,32 @@ namespace nexo::components { 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; + float distance; + ecs::Entity targetEntity; + + PerspectiveCameraTarget restore() const + { + PerspectiveCameraTarget target; + target.mouseSensitivity = mouseSensitivity; + target.distance = distance; + target.targetEntity = targetEntity; + return target; + } + }; + + [[nodiscard]] Memento save() const + { + return { + mouseSensitivity, + distance, + targetEntity + }; + } + + }; /** diff --git a/engine/src/components/Light.hpp b/engine/src/components/Light.hpp index c5561bd07..1d314cfdf 100644 --- a/engine/src/components/Light.hpp +++ b/engine/src/components/Light.hpp @@ -26,6 +26,22 @@ namespace nexo::components { struct AmbientLightComponent { glm::vec3 color{}; + + struct Memento { + glm::vec3 color; + + AmbientLightComponent restore(const Memento& memento) const + { + return {color}; + } + }; + + Memento save() const + { + return {color}; + } + + }; struct DirectionalLightComponent { @@ -36,6 +52,22 @@ namespace nexo::components { glm::vec3 direction{}; glm::vec3 color{}; + + struct Memento { + glm::vec3 direction; + glm::vec3 color; + + DirectionalLightComponent restore() const + { + DirectionalLightComponent dirLight(direction, color); + return dirLight; + } + }; + + Memento save() const + { + return {direction, color}; + } }; struct PointLightComponent { @@ -44,6 +76,26 @@ namespace nexo::components { float quadratic{}; float maxDistance = 50.0f; float constant = 1.0f; + + struct Memento { + glm::vec3 color{}; + float linear{}; + float quadratic{}; + float maxDistance; + float constant; + + PointLightComponent restore() const + { + return {color, linear, quadratic, maxDistance, constant}; + } + }; + + Memento save() const + { + return {color, linear, quadratic, maxDistance, constant}; + } + + }; struct SpotLightComponent { @@ -55,6 +107,29 @@ namespace nexo::components { float quadratic{}; float maxDistance = 325.0f; float constant = 1.0f; + + struct Memento { + glm::vec3 direction{}; + glm::vec3 color{}; + float cutOff{}; + float outerCutoff{}; + float linear{}; + float quadratic{}; + float maxDistance; + float constant; + + SpotLightComponent restore() const + { + return {direction, color, cutOff, outerCutoff, linear, quadratic, maxDistance, constant}; + } + }; + + Memento save() const + { + return {direction, color, cutOff, outerCutoff, linear, quadratic, maxDistance, constant}; + } + + }; struct LightContext { diff --git a/engine/src/components/Render.hpp b/engine/src/components/Render.hpp index 36386fecd..ebeb2feb3 100644 --- a/engine/src/components/Render.hpp +++ b/engine/src/components/Render.hpp @@ -103,7 +103,31 @@ namespace nexo::components renderable->draw(context, transform, entityID); } - [[nodiscard]] RenderComponent clone() const { + struct Memento { + bool isRendered = true; + RenderType type = RenderType::RENDER_2D; + + std::shared_ptr renderable; + + RenderComponent restore() const + { + RenderComponent restored(renderable, type); + restored.isRendered = isRendered; + return restored; + } + }; + + Memento save() const + { + return { + isRendered, + type, + renderable ? renderable->clone() : nullptr + }; + } + + [[nodiscard]] RenderComponent clone() const + { RenderComponent copy; copy.isRendered = isRendered; copy.type = type; diff --git a/engine/src/components/SceneComponents.hpp b/engine/src/components/SceneComponents.hpp index 0dcdadd87..31c08150b 100644 --- a/engine/src/components/SceneComponents.hpp +++ b/engine/src/components/SceneComponents.hpp @@ -26,6 +26,22 @@ namespace nexo::components { unsigned int id; bool isActive = true; bool isRendered = true; + + struct Memento { + unsigned int id; + bool isActive; + bool isRendered; + + SceneTag restore() const + { + return {id, isActive, isRendered}; + } + }; + + Memento save() const + { + return {id, isActive, isRendered}; + } }; } diff --git a/engine/src/components/Transform.hpp b/engine/src/components/Transform.hpp index 38b5f4da6..3ae10bd38 100644 --- a/engine/src/components/Transform.hpp +++ b/engine/src/components/Transform.hpp @@ -20,6 +20,22 @@ namespace nexo::components { struct TransformComponent final { + struct Memento { + glm::vec3 position; + glm::quat rotation; + glm::vec3 scale; + + TransformComponent restore() const + { + return {position, scale, rotation}; + } + }; + + Memento save() const + { + return {pos, quat, size}; + } + glm::vec3 pos; glm::vec3 size = glm::vec3(1.0f); glm::quat quat = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); diff --git a/engine/src/components/Uuid.hpp b/engine/src/components/Uuid.hpp index 916c7df5d..c70e5bff8 100644 --- a/engine/src/components/Uuid.hpp +++ b/engine/src/components/Uuid.hpp @@ -37,7 +37,23 @@ namespace nexo::components { return res; } - struct UuidComponent { - std::string uuid = genUuid(); - }; + struct UuidComponent { + struct Memento { + std::string uuid; + + UuidComponent restore() const + { + return {uuid}; + } + }; + + Memento save() const + { + return {uuid}; + } + + + + std::string uuid = genUuid(); + }; } From e4339b8d1032279213ff7ae11f0c2c5b9233c0da Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 18:01:27 +0900 Subject: [PATCH 210/450] refactor(document-windows): now use a map of type index to component type + feat: now handle the memento save/restore pattern --- engine/src/ecs/Coordinator.cpp | 24 ++++++-- engine/src/ecs/Coordinator.hpp | 109 +++++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 10 deletions(-) diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index 605bca397..f6db36fce 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -47,9 +47,16 @@ namespace nexo::ecs { { std::vector types; - for (const auto& [type, func] : m_hasComponentFunctions) { - if (func(entity)) { - types.emplace_back(type); + // Get the entity's signature which already tells us which components it has + Signature signature = m_entityManager->getSignature(entity); + + // We need a mapping from component type IDs to type_index + // This could be stored as a member variable and populated during registerComponent + + // If we have that mapping: + for (ComponentType type = 0; type < MAX_COMPONENT_TYPE; ++type) { + if (signature.test(type) && m_typeIDtoTypeIndex.contains(type)) { + types.emplace_back(m_typeIDtoTypeIndex.at(type)); } } @@ -60,9 +67,14 @@ namespace nexo::ecs { { std::vector> components; - for (const auto& [type, func] : m_hasComponentFunctions) { - if (func(entity)) { - components.emplace_back(type, m_getComponentFunctions[type](entity)); + // Get the entity's signature which already tells us which components it has + Signature signature = m_entityManager->getSignature(entity); + + // Iterate only through components that the entity actually has + for (ComponentType type = 0; type < MAX_COMPONENT_TYPE; ++type) { + if (signature.test(type) && m_typeIDtoTypeIndex.contains(type)) { + const auto& typeIndex = m_typeIDtoTypeIndex.at(type); + components.emplace_back(typeIndex, m_getComponentFunctions[typeIndex](entity)); } } diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 95b8896af..2e508d005 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -54,6 +54,53 @@ namespace nexo::ecs { template using extract_type_t = typename extract_type::type; + // Check if T has a nested Memento type + template + struct has_memento_type : std::false_type {}; + + template + struct has_memento_type> : std::true_type {}; + + // Check if T has a save() method that returns Memento + template + struct has_save_method : std::false_type {}; + + template + struct has_save_method().save())>> + : std::is_same().save()), typename T::Memento> {}; + + // Check if T::Memento has a restore() method that returns T + template + struct has_restore_method : std::false_type {}; + + template + struct has_restore_method().restore())>> + : std::is_same().restore()), T> {}; + + // Combined check for full memento pattern support + template + struct supports_memento_pattern : + std::conjunction< + has_memento_type, + has_save_method, + has_restore_method + > {}; + + template + inline constexpr bool has_memento_type_v = has_memento_type::value; + + template + inline constexpr bool has_save_method_v = has_save_method::value; + + template + inline constexpr bool has_restore_method_v = has_restore_method::value; + + template + inline constexpr bool supports_memento_pattern_v = supports_memento_pattern::value; + + /** * @class Coordinator * @@ -93,13 +140,32 @@ namespace nexo::ecs { void registerComponent() { m_componentManager->registerComponent(); - m_hasComponentFunctions[typeid(T)] = [this](Entity entity) -> bool { - return this->entityHasComponent(entity); - }; m_getComponentFunctions[typeid(T)] = [this](Entity entity) -> std::any { return std::any(this->getComponent(entity)); }; + m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); + + m_addComponentFunctions[typeid(T)] = [this](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); + return std::any(memento.restore()); + }; + } else { + m_supportsMementoPattern.emplace(typeid(T), false); + } } /** @@ -396,6 +462,37 @@ namespace nexo::ecs { const ComponentType componentType = m_componentManager->getComponentType(); return signature.test(componentType); } + + bool supportsMementoPattern(const std::any& component) const + { + auto typeId = std::type_index(component.type()); + auto it = m_supportsMementoPattern.find(typeId); + return (it != m_supportsMementoPattern.end()) && it->second; + } + + std::any saveComponent(const std::any& component) const { + auto typeId = std::type_index(component.type()); + auto it = m_saveComponentFunctions.find(typeId); + if (it != m_saveComponentFunctions.end()) { + return it->second(component); + } + return std::any(); + } + + std::any restoreComponent(const std::any& memento, const std::type_index& componentType) const { + auto it = m_restoreComponentFunctions.find(componentType); + if (it != m_restoreComponentFunctions.end()) { + return it->second(memento); + } + return std::any(); + } + + void addComponentAny(Entity entity, const std::type_index& typeIndex, const std::any& component) { + auto it = m_addComponentFunctions.find(typeIndex); + if (it != m_addComponentFunctions.end()) { + it->second(entity, component); + } + } private: template @@ -415,7 +512,11 @@ namespace nexo::ecs { std::shared_ptr m_systemManager; std::shared_ptr m_singletonComponentManager; - std::unordered_map> m_hasComponentFunctions; + 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; }; } From 12dc602510865e6defb46831a38cf7448b04fa1a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 18:01:56 +0900 Subject: [PATCH 211/450] feat(document-windows): add action management for undo/redo --- editor/src/context/ActionGroup.cpp | 40 ++++++ editor/src/context/ActionGroup.hpp | 38 +++++ editor/src/context/ActionHistory.cpp | 69 ++++++++++ editor/src/context/ActionHistory.hpp | 46 +++++++ editor/src/context/ActionManager.cpp | 63 +++++++++ editor/src/context/ActionManager.hpp | 64 +++++++++ editor/src/context/actions/Action.hpp | 34 +++++ editor/src/context/actions/EntityActions.hpp | 138 +++++++++++++++++++ editor/src/context/actions/StateAction.hpp | 70 ++++++++++ 9 files changed, 562 insertions(+) create mode 100644 editor/src/context/ActionGroup.cpp create mode 100644 editor/src/context/ActionGroup.hpp create mode 100644 editor/src/context/ActionHistory.cpp create mode 100644 editor/src/context/ActionHistory.hpp create mode 100644 editor/src/context/ActionManager.cpp create mode 100644 editor/src/context/ActionManager.hpp create mode 100644 editor/src/context/actions/Action.hpp create mode 100644 editor/src/context/actions/EntityActions.hpp create mode 100644 editor/src/context/actions/StateAction.hpp diff --git a/editor/src/context/ActionGroup.cpp b/editor/src/context/ActionGroup.cpp new file mode 100644 index 000000000..52aab5870 --- /dev/null +++ b/editor/src/context/ActionGroup.cpp @@ -0,0 +1,40 @@ +//// ActionGroup.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: 26/04/2025 +// Description: Source file for the action group class +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ActionGroup.hpp" + +namespace nexo::editor { + + void ActionGroup::addAction(std::unique_ptr action) + { + actions.push_back(std::move(action)); + } + + bool ActionGroup::hasActions() const + { + return !actions.empty(); + } + + void ActionGroup::redo() + { + for (auto& action : actions) + action->redo(); + } + + void ActionGroup::undo() + { + for (auto it = actions.rbegin(); it != actions.rend(); ++it) + (*it)->undo(); + } +} diff --git a/editor/src/context/ActionGroup.hpp b/editor/src/context/ActionGroup.hpp new file mode 100644 index 000000000..09de9c9bd --- /dev/null +++ b/editor/src/context/ActionGroup.hpp @@ -0,0 +1,38 @@ +//// ActionGroup.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: Mehdy MORVAN +// Date: 26/04/2025 +// Description: Header file for the action group class +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "actions/Action.hpp" +#include +#include + +namespace nexo::editor { + + /** + * Groups multiple actions into a single undoable action + */ + class ActionGroup : public Action { + public: + ActionGroup() = default; + + void addAction(std::unique_ptr action); + bool hasActions() const; + void redo() override; + void undo() override; + + private: + std::vector> actions; + }; + +} diff --git a/editor/src/context/ActionHistory.cpp b/editor/src/context/ActionHistory.cpp new file mode 100644 index 000000000..bcfecd02b --- /dev/null +++ b/editor/src/context/ActionHistory.cpp @@ -0,0 +1,69 @@ +//// ActionHistory.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: 26/04/2025 +// Description: Source file for the action history class +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ActionHistory.hpp" + +namespace nexo::editor { + void ActionHistory::addAction(std::unique_ptr action) + { + undoStack.push_back(std::move(action)); + redoStack.clear(); + + while (undoStack.size() > maxUndoLevels) + undoStack.erase(undoStack.begin()); + } + + bool ActionHistory::canUndo() const + { + return !undoStack.empty(); + } + + bool ActionHistory::canRedo() const + { + return !redoStack.empty(); + } + + void ActionHistory::undo() + { + if (!canUndo()) + return; + auto action = std::move(undoStack.back()); + undoStack.pop_back(); + action->undo(); + redoStack.push_back(std::move(action)); + } + + void ActionHistory::redo() + { + if (!canRedo()) + return; + auto action = std::move(redoStack.back()); + redoStack.pop_back(); + action->redo(); + undoStack.push_back(std::move(action)); + } + + void ActionHistory::setMaxUndoLevels(size_t levels) + { + maxUndoLevels = levels; + while (undoStack.size() > maxUndoLevels) + undoStack.erase(undoStack.begin()); + } + + void ActionHistory::clear() + { + undoStack.clear(); + redoStack.clear(); + } +} diff --git a/editor/src/context/ActionHistory.hpp b/editor/src/context/ActionHistory.hpp new file mode 100644 index 000000000..5b8c5a149 --- /dev/null +++ b/editor/src/context/ActionHistory.hpp @@ -0,0 +1,46 @@ +//// ActionHistory.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: Mehdy MORVAN +// Date: 26/04/2025 +// Description: Header file for the action history class +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "actions/Action.hpp" +#include +#include +#include + +namespace nexo::editor { + + /** + * 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); + + bool canUndo() const; + bool canRedo() const; + void undo(); + void redo(); + + void setMaxUndoLevels(size_t levels); + + void clear(); + + private: + std::vector> undoStack; + std::vector> redoStack; + size_t maxUndoLevels = 50; + }; + +} diff --git a/editor/src/context/ActionManager.cpp b/editor/src/context/ActionManager.cpp new file mode 100644 index 000000000..92842e088 --- /dev/null +++ b/editor/src/context/ActionManager.cpp @@ -0,0 +1,63 @@ +//// ActionManager.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: 26/04/2025 +// Description: Source file for the action manager class +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ActionManager.hpp" +#include "context/actions/EntityActions.hpp" + +namespace nexo::editor { + void ActionManager::recordAction(std::unique_ptr action) + { + history.addAction(std::move(action)); + } + + void ActionManager::recordEntityCreation(ecs::Entity entityId) + { + recordAction(std::make_unique(entityId)); + } + + std::unique_ptr ActionManager::prepareEntityDeletion(ecs::Entity entityId) + { + return std::make_unique(entityId); + } + + std::unique_ptr ActionManager::createActionGroup() + { + return std::make_unique(); + } + + void ActionManager::undo() + { + history.undo(); + } + + void ActionManager::redo() + { + history.redo(); + } + + bool ActionManager::canUndo() const + { + return history.canUndo(); + } + + bool ActionManager::canRedo() const + { + return history.canRedo(); + } + + void ActionManager::clearHistory() + { + history.clear(); + } +} diff --git a/editor/src/context/ActionManager.hpp b/editor/src/context/ActionManager.hpp new file mode 100644 index 000000000..17e1051c8 --- /dev/null +++ b/editor/src/context/ActionManager.hpp @@ -0,0 +1,64 @@ +//// ActionManager.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: Mehdy MORVAN +// Date: 26/04/2025 +// Description: Header file for the action manager class +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "ActionHistory.hpp" +#include "ActionGroup.hpp" +#include "context/actions/EntityActions.hpp" +#include + +namespace nexo::editor { + + class ActionManager { + public: + // Record a command after an operation is done + void recordAction(std::unique_ptr action); + // Record entity creation + void recordEntityCreation(ecs::Entity entityId); + // Record entity deletion (call before actually deleting) + std::unique_ptr prepareEntityDeletion(ecs::Entity entityId); + + // For component changes using memento pattern + template + void recordComponentChange(ecs::Entity entityId, + const typename MementoComponent::Memento& beforeState, + const typename MementoComponent::Memento& afterState) + { + auto& app = nexo::getApp(); + auto& component = app.m_coordinator->getComponent(entityId); + + auto action = std::make_unique>(entityId, beforeState, afterState); + + recordAction(std::move(action)); + } + + // Action group for multiple operations + std::unique_ptr createActionGroup(); + + // Basic undo/redo operations + void undo(); + void redo(); + bool canUndo() const; + bool canRedo() const; + void clearHistory(); + + static ActionManager& get() { + static ActionManager instance; + return instance; + } + private: + ActionHistory history; + }; + +} diff --git a/editor/src/context/actions/Action.hpp b/editor/src/context/actions/Action.hpp new file mode 100644 index 000000000..3a37ffb5c --- /dev/null +++ b/editor/src/context/actions/Action.hpp @@ -0,0 +1,34 @@ +//// Action.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: Mehdy MORVAN +// Date: 26/04/2025 +// Description: Header file for the action interface +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include + +namespace nexo::editor { + + /** + * Base Action interface for all undoable operations + */ + class Action { + public: + virtual ~Action() = default; + + // Redo the action after it was undone + virtual void redo() = 0; + + // Undo the action's effects + virtual void undo() = 0; + }; + +} diff --git a/editor/src/context/actions/EntityActions.hpp b/editor/src/context/actions/EntityActions.hpp new file mode 100644 index 000000000..e6f535946 --- /dev/null +++ b/editor/src/context/actions/EntityActions.hpp @@ -0,0 +1,138 @@ +// EntityActions.hpp +#pragma once + +#include "Action.hpp" +#include "core/scene/Scene.hpp" +#include "Nexo.hpp" + +namespace nexo::editor { + +template +auto getComponentLambda(ecs::Entity entity) { + return [entity]() -> ComponentType& { + return Application::getInstance().m_coordinator->getComponent(entity); + }; +} + +template +class ComponentChangeAction : public Action { + public: + ComponentChangeAction( + 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 + { + auto &app = getApp(); + ComponentType &target = app.m_coordinator->getComponent(m_entity); + target = m_afterState.restore(); + } + + void undo() override + { + auto &app = getApp(); + ComponentType &target = app.m_coordinator->getComponent(m_entity); + target = m_beforeState.restore(); + } + + private: + ecs::Entity m_entity; + typename ComponentType::Memento m_beforeState; + typename ComponentType::Memento m_afterState; +}; + +inline std::vector> captureEntityState(ecs::Entity entity) +{ + auto& coordinator = Application::getInstance().m_coordinator; + const auto &componentData = coordinator->getAllComponents(entity); + std::vector> result; + + // For components that support the Memento pattern, create mementos + for (const auto& [typeIndex, componentAny] : componentData) { + if (coordinator->supportsMementoPattern(componentAny)) { + std::any memento = coordinator->saveComponent(componentAny); + if (memento.has_value()) { + result.emplace_back(typeIndex, std::move(memento)); + } + } + } + return result; +} + +inline void restoreComponents(ecs::Entity entity, const std::vector>& mementos) +{ + auto &app = getApp(); + auto &coordinator = app.m_coordinator; + for (const auto& [typeIndex, mementoAny] : mementos) { + std::any component = coordinator->restoreComponent(mementoAny, typeIndex); + coordinator->addComponentAny(entity, typeIndex, component); + } +} + +/** + * Stores information needed to undo/redo entity creation + * Relies on engine systems for actual creation/deletion logic + */ +class EntityCreationAction : public Action { +private: + ecs::Entity m_entityId; + std::vector> m_mementos; + +public: + EntityCreationAction(ecs::Entity entityId) + : m_entityId(entityId) {} + + void redo() override { + auto& coordinator = Application::getInstance().m_coordinator; + m_entityId = coordinator->createEntity(); + + restoreComponents(m_entityId, m_mementos); + } + + void undo() override + { + m_mementos = captureEntityState(m_entityId); + auto& coordinator = Application::getInstance().m_coordinator; + coordinator->destroyEntity(m_entityId); + } +}; + +/** + * Stores information needed to undo/redo entity deletion + * Relies on engine systems for actual deletion logic + */ + class EntityDeletionAction : public Action { + private: + ecs::Entity m_entityId; + // Store component mementos for those that support the pattern + std::vector> m_mementos; + + public: + EntityDeletionAction(ecs::Entity entityId) : m_entityId(entityId) + { + m_mementos = captureEntityState(m_entityId); + } + + void redo() override + { + // Simply destroy the entity + auto& coordinator = Application::getInstance().m_coordinator; + coordinator->destroyEntity(m_entityId); + } + + void undo() override + { + auto& coordinator = Application::getInstance().m_coordinator; + + // First recreate the entity with the same ID + // This assumes you have a createEntityWithId method, otherwise + // you'll need to adapt to your coordinator's API + m_entityId = coordinator->createEntity(); + + restoreComponents(m_entityId, m_mementos); + } + }; + +} diff --git a/editor/src/context/actions/StateAction.hpp b/editor/src/context/actions/StateAction.hpp new file mode 100644 index 000000000..1e69fbb68 --- /dev/null +++ b/editor/src/context/actions/StateAction.hpp @@ -0,0 +1,70 @@ +//// StateAction.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: Mehdy MORVAN +// Date: 26/04/2025 +// Description: Header file for the state action class +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "Action.hpp" + +#include +#include + +namespace nexo::editor { + + /** + * Generic action for objects that can save and restore their state + * Object must implement save() and restore() methods + */ + template + class StateAction : public Action { + public: + StateAction( + T& target, + const typename T::Memento& before, + const typename T::Memento& after + ) : m_target(target), m_beforeState(before), m_afterState(after){} + + static std::unique_ptr> createFromOperation( + T& target, + std::function operation + ) { + + // Capture before state + auto beforeState = target.save(); + + // Execute operation + operation(); + + // Capture after state + auto afterState = target.save(); + + // Create action + return std::make_unique>(target, beforeState, afterState); + } + + void redo() override + { + m_target = m_afterState.restore(); + } + + void undo() override + { + m_target = m_beforeState.restore(); + } + + private: + T& m_target; + typename T::Memento m_beforeState; + typename T::Memento m_afterState; + }; + +} From cede59da594b1912b2ca857ca805239e176624c3 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 18:02:09 +0900 Subject: [PATCH 212/450] feat(document-windows): add undo/redo handling --- editor/src/DocumentWindows/EditorScene.cpp | 95 ++++++++++++++++++++-- editor/src/Editor.cpp | 13 +++ 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp index 14fde2bc8..19468ffb7 100644 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ b/editor/src/DocumentWindows/EditorScene.cpp @@ -19,6 +19,7 @@ #include "ADocumentWindow.hpp" #include "Application.hpp" +#include "Definitions.hpp" #include "EntityFactory3D.hpp" #include "IconsFontAwesome.h" #include "LightFactory.hpp" @@ -29,10 +30,14 @@ #include "components/Camera.hpp" #include "components/Render.hpp" #include "components/RenderContext.hpp" +#include "components/Transform.hpp" #include "components/Uuid.hpp" #include "components/Editor.hpp" #include "math/Matrix.hpp" #include "context/Selector.hpp" +#include "context/actions/StateAction.hpp" +#include "context/actions/EntityActions.hpp" +#include "context/ActionManager.hpp" #include "utils/String.hpp" #include "utils/EditorProps.hpp" #include "ImNexo/Widgets.hpp" @@ -141,11 +146,14 @@ namespace nexo::editor { auto &selector = Selector::get(); const auto &selectedEntities = selector.getSelectedEntities(); auto &app = nexo::getApp(); + auto& actionManager = ActionManager::get(); + auto deleteAction = actionManager.prepareEntityDeletion(selectedEntities[0]); for (const auto entity : selectedEntities) app.deleteEntity(entity); selector.clearSelection(); this->m_windowState = m_globalState; + actionManager.recordAction(std::move(deleteAction)); }, nullptr, nullptr, @@ -1244,7 +1252,6 @@ namespace nexo::editor { } } - // Still no entity with transform component found if (!transf) return; @@ -1276,6 +1283,26 @@ namespace nexo::editor { snap = &m_angleSnap; } + // Track ImGuizmo state across frames + static bool wasUsingGizmo = false; + bool isUsingGizmo = ImGuizmo::IsUsing(); + + // Store initial states when starting to use gizmo + static std::unordered_map initialStates; + + // Check if we're just starting to use the gizmo this frame + if (!wasUsingGizmo && ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGuizmo::IsOver()) { + initialStates.clear(); + initialStates[targetEntity] = transf->get().save(); + for (const auto entity : selectedEntities) { + if (entity != targetEntity) { + auto entityTransf = coord->tryGetComponent(entity); + if (entityTransf) + initialStates[entity] = entityTransf->get().save(); + } + } + } + ImGuizmo::Enable(true); ImGuizmo::Manipulate(glm::value_ptr(viewMatrix), glm::value_ptr(projectionMatrix), m_currentGizmoOperation, @@ -1283,8 +1310,10 @@ namespace nexo::editor { glm::value_ptr(transformMatrix), nullptr, snap); - if (ImGuizmo::IsUsing()) - { + isUsingGizmo = ImGuizmo::IsUsing(); + + if (isUsingGizmo) { + // Disable camera movement during gizmo manipulation cameraComponent.active = false; glm::vec3 originalPos = transf->get().pos; @@ -1302,12 +1331,10 @@ namespace nexo::editor { glm::vec3 scaleFactor = newScale / originalScale; // Element-wise division glm::quat rotationDelta = newRot * glm::inverse(originalRot); - // Apply transformation to target entity transf->get().pos = newPos; transf->get().quat = newRot; transf->get().size = newScale; - // Apply same transformation to all other selected entities with transform components for (const auto entity : selectedEntities) { if (entity == targetEntity) continue; // Skip target entity, already transformed @@ -1316,11 +1343,9 @@ namespace nexo::editor { if (m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE) entityTransf->get().pos += positionDelta; - if (m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE) entityTransf->get().quat = rotationDelta * entityTransf->get().quat; - if (m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE) { entityTransf->get().size.x *= scaleFactor.x; entityTransf->get().size.y *= scaleFactor.y; @@ -1328,9 +1353,63 @@ namespace nexo::editor { } } } - } else { + } else if (wasUsingGizmo) { cameraComponent.active = true; + + auto& actionManager = ActionManager::get(); + if (selectedEntities.size() > 1) { + // Multiple entities selected + auto groupAction = actionManager.createActionGroup(); + bool anyChanges = false; + + for (const auto entity : selectedEntities) { + auto entityTransf = coord->tryGetComponent(entity); + if (entityTransf && initialStates.find(entity) != initialStates.end()) { + auto beforeState = initialStates[entity]; + auto afterState = entityTransf->get().save(); + + // Check if the transform actually changed + if (beforeState.position != afterState.position || + beforeState.rotation != afterState.rotation || + beforeState.scale != afterState.scale) { + + std::string operationName; + switch (m_currentGizmoOperation) { + case ImGuizmo::OPERATION::TRANSLATE: operationName = "Move"; break; + case ImGuizmo::OPERATION::ROTATE: operationName = "Rotate"; break; + case ImGuizmo::OPERATION::SCALE: operationName = "Scale"; break; + default: operationName = "Transform"; break; + } + + auto action = std::make_unique>( + entity, beforeState, afterState); + + groupAction->addAction(std::move(action)); + anyChanges = true; + } + } + } + + // Only record if any transforms changed + if (anyChanges) + actionManager.recordAction(std::move(groupAction)); + } else { + auto beforeState = initialStates[targetEntity]; + auto afterState = transf->get().save(); + + // Check if the transform actually changed + if (beforeState.position != afterState.position || + beforeState.rotation != afterState.rotation || + beforeState.scale != afterState.scale) { + actionManager.recordComponentChange( + targetEntity, beforeState, afterState); + } + } + + initialStates.clear(); } + + wasUsingGizmo = isUsingGizmo; } void EditorScene::renderView() diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index bfd9d1c53..78cd4d1e4 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -22,6 +22,7 @@ #include "backends/ImGuiBackend.hpp" #include "IconsFontAwesome.h" #include "ImNexo/Elements.hpp" +#include "context/ActionManager.hpp" #include "imgui.h" #include @@ -333,6 +334,18 @@ namespace nexo::editor { m_windowRegistry.render(); + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyPressed(ImGuiKey_Z)) + { + if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) + { + ActionManager::get().redo(); + } + else + { + ActionManager::get().undo(); + } + } + // Get the commands to display in the bottom bar std::vector possibleCommands; static std::vector lastValidCommands; // Store the last valid set of commands From b4b335380c2a1a44bf181c2a6ccb07ac428eef5f Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 18:02:24 +0900 Subject: [PATCH 213/450] chore(document-windows): add source files --- editor/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index d54038f0e..7d497ba3a 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -15,6 +15,9 @@ set(SRCS editor/src/backends/ImGuiBackend.cpp editor/src/backends/opengl/openglImGuiBackend.cpp editor/src/context/Selector.cpp + editor/src/context/ActionManager.cpp + editor/src/context/ActionHistory.cpp + editor/src/context/ActionGroup.cpp editor/src/ImNexo/EntityProperties.cpp editor/src/ImNexo/Components.cpp editor/src/ImNexo/Elements.cpp From 3674e281fad6741f120b842de159166db29c61d7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 18:14:37 +0900 Subject: [PATCH 214/450] refactor(document-windows): move some function impl in source file --- editor/CMakeLists.txt | 1 + editor/src/context/actions/Action.hpp | 4 - editor/src/context/actions/EntityActions.cpp | 85 +++++++++ editor/src/context/actions/EntityActions.hpp | 175 ++++++------------- editor/src/context/actions/StateAction.hpp | 18 -- engine/src/ecs/Coordinator.cpp | 31 ++++ engine/src/ecs/Coordinator.hpp | 34 +--- 7 files changed, 177 insertions(+), 171 deletions(-) create mode 100644 editor/src/context/actions/EntityActions.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 7d497ba3a..355640497 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -18,6 +18,7 @@ set(SRCS editor/src/context/ActionManager.cpp editor/src/context/ActionHistory.cpp editor/src/context/ActionGroup.cpp + editor/src/context/actions/EntityActions.cpp editor/src/ImNexo/EntityProperties.cpp editor/src/ImNexo/Components.cpp editor/src/ImNexo/Elements.cpp diff --git a/editor/src/context/actions/Action.hpp b/editor/src/context/actions/Action.hpp index 3a37ffb5c..256481172 100644 --- a/editor/src/context/actions/Action.hpp +++ b/editor/src/context/actions/Action.hpp @@ -13,8 +13,6 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include - namespace nexo::editor { /** @@ -24,10 +22,8 @@ namespace nexo::editor { public: virtual ~Action() = default; - // Redo the action after it was undone virtual void redo() = 0; - // Undo the action's effects virtual void undo() = 0; }; diff --git a/editor/src/context/actions/EntityActions.cpp b/editor/src/context/actions/EntityActions.cpp new file mode 100644 index 000000000..bdd1bf651 --- /dev/null +++ b/editor/src/context/actions/EntityActions.cpp @@ -0,0 +1,85 @@ +//// EntityActions.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: 28/04/2025 +// Description: Source file for the entity actions +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EntityActions.hpp" + +namespace nexo::editor { + + std::vector> captureEntityState(ecs::Entity entity) + { + auto& coordinator = Application::getInstance().m_coordinator; + const auto &componentData = coordinator->getAllComponents(entity); + std::vector> result; + + // For components that support the Memento pattern, create mementos + for (const auto& [typeIndex, componentAny] : componentData) { + if (coordinator->supportsMementoPattern(componentAny)) { + std::any memento = coordinator->saveComponent(componentAny); + if (memento.has_value()) { + result.emplace_back(typeIndex, std::move(memento)); + } + } + } + return result; + } + + void restoreComponents(ecs::Entity entity, const std::vector>& mementos) + { + auto &app = getApp(); + auto &coordinator = app.m_coordinator; + for (const auto& [typeIndex, mementoAny] : mementos) { + std::any component = coordinator->restoreComponent(mementoAny, typeIndex); + coordinator->addComponentAny(entity, typeIndex, component); + } + } + + void EntityCreationAction::redo() + { + auto& coordinator = Application::getInstance().m_coordinator; + m_entityId = coordinator->createEntity(); + + restoreComponents(m_entityId, m_mementos); + } + + void EntityCreationAction::undo() + { + m_mementos = captureEntityState(m_entityId); + auto& coordinator = Application::getInstance().m_coordinator; + coordinator->destroyEntity(m_entityId); + } + + EntityDeletionAction::EntityDeletionAction(ecs::Entity entityId) : m_entityId(entityId) + { + m_mementos = captureEntityState(m_entityId); + } + + void EntityDeletionAction::redo() + { + // Simply destroy the entity + auto& coordinator = Application::getInstance().m_coordinator; + coordinator->destroyEntity(m_entityId); + } + + void EntityDeletionAction::undo() + { + auto& coordinator = Application::getInstance().m_coordinator; + + // First recreate the entity with the same ID + // This assumes you have a createEntityWithId method, otherwise + // you'll need to adapt to your coordinator's API + m_entityId = coordinator->createEntity(); + + restoreComponents(m_entityId, m_mementos); + } +} diff --git a/editor/src/context/actions/EntityActions.hpp b/editor/src/context/actions/EntityActions.hpp index e6f535946..1f8c7a5df 100644 --- a/editor/src/context/actions/EntityActions.hpp +++ b/editor/src/context/actions/EntityActions.hpp @@ -7,132 +7,69 @@ namespace nexo::editor { -template -auto getComponentLambda(ecs::Entity entity) { - return [entity]() -> ComponentType& { - return Application::getInstance().m_coordinator->getComponent(entity); - }; -} - -template -class ComponentChangeAction : public Action { - public: - ComponentChangeAction( - 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 - { - auto &app = getApp(); - ComponentType &target = app.m_coordinator->getComponent(m_entity); - target = m_afterState.restore(); - } - - void undo() override - { - auto &app = getApp(); - ComponentType &target = app.m_coordinator->getComponent(m_entity); - target = m_beforeState.restore(); - } - - private: - ecs::Entity m_entity; - typename ComponentType::Memento m_beforeState; - typename ComponentType::Memento m_afterState; -}; - -inline std::vector> captureEntityState(ecs::Entity entity) -{ - auto& coordinator = Application::getInstance().m_coordinator; - const auto &componentData = coordinator->getAllComponents(entity); - std::vector> result; - - // For components that support the Memento pattern, create mementos - for (const auto& [typeIndex, componentAny] : componentData) { - if (coordinator->supportsMementoPattern(componentAny)) { - std::any memento = coordinator->saveComponent(componentAny); - if (memento.has_value()) { - result.emplace_back(typeIndex, std::move(memento)); + template + class ComponentChangeAction : public Action { + public: + ComponentChangeAction( + 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 + { + auto &app = getApp(); + ComponentType &target = app.m_coordinator->getComponent(m_entity); + target = m_afterState.restore(); } - } - } - return result; -} -inline void restoreComponents(ecs::Entity entity, const std::vector>& mementos) -{ - auto &app = getApp(); - auto &coordinator = app.m_coordinator; - for (const auto& [typeIndex, mementoAny] : mementos) { - std::any component = coordinator->restoreComponent(mementoAny, typeIndex); - coordinator->addComponentAny(entity, typeIndex, component); - } -} - -/** - * Stores information needed to undo/redo entity creation - * Relies on engine systems for actual creation/deletion logic - */ -class EntityCreationAction : public Action { -private: - ecs::Entity m_entityId; - std::vector> m_mementos; - -public: - EntityCreationAction(ecs::Entity entityId) - : m_entityId(entityId) {} - - void redo() override { - auto& coordinator = Application::getInstance().m_coordinator; - m_entityId = coordinator->createEntity(); - - restoreComponents(m_entityId, m_mementos); - } - - void undo() override - { - m_mementos = captureEntityState(m_entityId); - auto& coordinator = Application::getInstance().m_coordinator; - coordinator->destroyEntity(m_entityId); - } -}; + void undo() override + { + auto &app = getApp(); + ComponentType &target = app.m_coordinator->getComponent(m_entity); + target = m_beforeState.restore(); + } -/** - * Stores information needed to undo/redo entity deletion - * Relies on engine systems for actual deletion logic - */ - class EntityDeletionAction : public Action { - private: - ecs::Entity m_entityId; - // Store component mementos for those that support the pattern - std::vector> m_mementos; + private: + ecs::Entity m_entity; + typename ComponentType::Memento m_beforeState; + typename ComponentType::Memento m_afterState; + }; - public: - EntityDeletionAction(ecs::Entity entityId) : m_entityId(entityId) - { - m_mementos = captureEntityState(m_entityId); - } + std::vector> captureEntityState(ecs::Entity entity); + void restoreComponents(ecs::Entity entity, const std::vector>& mementos); - void redo() override - { - // Simply destroy the entity - auto& coordinator = Application::getInstance().m_coordinator; - coordinator->destroyEntity(m_entityId); - } + /** + * Stores information needed to undo/redo entity creation + * Relies on engine systems for actual creation/deletion logic + */ + class EntityCreationAction : public Action { + public: + EntityCreationAction(ecs::Entity entityId) + : m_entityId(entityId) {} - void undo() override - { - auto& coordinator = Application::getInstance().m_coordinator; + void redo() override; + void undo() override; - // First recreate the entity with the same ID - // This assumes you have a createEntityWithId method, otherwise - // you'll need to adapt to your coordinator's API - m_entityId = coordinator->createEntity(); + private: + ecs::Entity m_entityId; + std::vector> m_mementos; + }; - restoreComponents(m_entityId, m_mementos); - } - }; + /** + * Stores information needed to undo/redo entity deletion + * Relies on engine systems for actual deletion logic + */ + class EntityDeletionAction : public Action { + public: + EntityDeletionAction(ecs::Entity entityId); + + void redo() override; + void undo() override; + private: + ecs::Entity m_entityId; + // Store component mementos for those that support the pattern + std::vector> m_mementos; + }; } diff --git a/editor/src/context/actions/StateAction.hpp b/editor/src/context/actions/StateAction.hpp index 1e69fbb68..fbac24d4c 100644 --- a/editor/src/context/actions/StateAction.hpp +++ b/editor/src/context/actions/StateAction.hpp @@ -33,24 +33,6 @@ namespace nexo::editor { const typename T::Memento& after ) : m_target(target), m_beforeState(before), m_afterState(after){} - static std::unique_ptr> createFromOperation( - T& target, - std::function operation - ) { - - // Capture before state - auto beforeState = target.save(); - - // Execute operation - operation(); - - // Capture after state - auto afterState = target.save(); - - // Create action - return std::make_unique>(target, beforeState, afterState); - } - void redo() override { m_target = m_afterState.restore(); diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index f6db36fce..c93d76858 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -80,4 +80,35 @@ namespace nexo::ecs { return components; } + + bool Coordinator::supportsMementoPattern(const std::any& component) const + { + auto typeId = std::type_index(component.type()); + auto it = m_supportsMementoPattern.find(typeId); + return (it != m_supportsMementoPattern.end()) && it->second; + } + + std::any Coordinator::saveComponent(const std::any& component) const + { + auto typeId = std::type_index(component.type()); + auto it = m_saveComponentFunctions.find(typeId); + if (it != m_saveComponentFunctions.end()) + return it->second(component); + return std::any(); + } + + std::any Coordinator::restoreComponent(const std::any& memento, const std::type_index& componentType) const + { + auto it = m_restoreComponentFunctions.find(componentType); + if (it != m_restoreComponentFunctions.end()) + return it->second(memento); + return std::any(); + } + + void Coordinator::addComponentAny(Entity entity, const std::type_index& typeIndex, const std::any& component) + { + auto it = m_addComponentFunctions.find(typeIndex); + if (it != m_addComponentFunctions.end()) + it->second(entity, component); + } } diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 2e508d005..d2f8f4d99 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -463,38 +463,12 @@ namespace nexo::ecs { return signature.test(componentType); } - bool supportsMementoPattern(const std::any& component) const - { - auto typeId = std::type_index(component.type()); - auto it = m_supportsMementoPattern.find(typeId); - return (it != m_supportsMementoPattern.end()) && it->second; - } - - std::any saveComponent(const std::any& component) const { - auto typeId = std::type_index(component.type()); - auto it = m_saveComponentFunctions.find(typeId); - if (it != m_saveComponentFunctions.end()) { - return it->second(component); - } - return std::any(); - } + 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); - std::any restoreComponent(const std::any& memento, const std::type_index& componentType) const { - auto it = m_restoreComponentFunctions.find(componentType); - if (it != m_restoreComponentFunctions.end()) { - return it->second(memento); - } - return std::any(); - } - - void addComponentAny(Entity entity, const std::type_index& typeIndex, const std::any& component) { - auto it = m_addComponentFunctions.find(typeIndex); - if (it != m_addComponentFunctions.end()) { - it->second(entity, component); - } - } private: - template void processComponentSignature(Signature& required, Signature& excluded) const { if constexpr (is_exclude_v) { From 8bfac143545ca9bbe55dc7799d34db53268cc7bf Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 20:26:23 +0900 Subject: [PATCH 215/450] refactor(document-windows): split editor scene into multiple source file --- editor/CMakeLists.txt | 8 +- editor/main.cpp | 2 +- editor/src/DocumentWindows/EditorScene.cpp | 1628 ----------------- .../{ => EditorScene}/EditorScene.hpp | 38 +- .../src/DocumentWindows/EditorScene/Gizmo.cpp | 296 +++ .../src/DocumentWindows/EditorScene/Init.cpp | 90 + .../DocumentWindows/EditorScene/Shortcuts.cpp | 751 ++++++++ .../src/DocumentWindows/EditorScene/Show.cpp | 154 ++ .../DocumentWindows/EditorScene/Shutdown.cpp | 22 + .../DocumentWindows/EditorScene/Toolbar.cpp | 421 +++++ .../DocumentWindows/EditorScene/Update.cpp | 131 ++ .../src/DocumentWindows/SceneTreeWindow.cpp | 2 +- .../src/DocumentWindows/SceneTreeWindow.hpp | 2 +- 13 files changed, 1903 insertions(+), 1642 deletions(-) delete mode 100644 editor/src/DocumentWindows/EditorScene.cpp rename editor/src/DocumentWindows/{ => EditorScene}/EditorScene.hpp (89%) create mode 100644 editor/src/DocumentWindows/EditorScene/Gizmo.cpp create mode 100644 editor/src/DocumentWindows/EditorScene/Init.cpp create mode 100644 editor/src/DocumentWindows/EditorScene/Shortcuts.cpp create mode 100644 editor/src/DocumentWindows/EditorScene/Show.cpp create mode 100644 editor/src/DocumentWindows/EditorScene/Shutdown.cpp create mode 100644 editor/src/DocumentWindows/EditorScene/Toolbar.cpp create mode 100644 editor/src/DocumentWindows/EditorScene/Update.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 355640497..496a72fdc 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -37,7 +37,13 @@ set(SRCS editor/src/WindowRegistry.cpp editor/src/DockingRegistry.cpp editor/src/DocumentWindows/ConsoleWindow.cpp - editor/src/DocumentWindows/EditorScene.cpp + editor/src/DocumentWindows/EditorScene/Gizmo.cpp + editor/src/DocumentWindows/EditorScene/Init.cpp + editor/src/DocumentWindows/EditorScene/Shortcuts.cpp + editor/src/DocumentWindows/EditorScene/Show.cpp + editor/src/DocumentWindows/EditorScene/Shutdown.cpp + editor/src/DocumentWindows/EditorScene/Toolbar.cpp + editor/src/DocumentWindows/EditorScene/Update.cpp editor/src/DocumentWindows/SceneTreeWindow.cpp editor/src/DocumentWindows/PopupManager.cpp editor/src/DocumentWindows/InspectorWindow.cpp diff --git a/editor/main.cpp b/editor/main.cpp index 18fe1dc4d..c18364a85 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -14,7 +14,7 @@ #include "src/Editor.hpp" #include "src/DocumentWindows/ConsoleWindow.hpp" -#include "src/DocumentWindows/EditorScene.hpp" +#include "src/DocumentWindows/EditorScene/EditorScene.hpp" #include "src/DocumentWindows/SceneTreeWindow.hpp" #include "src/DocumentWindows/InspectorWindow.hpp" #include "src/DocumentWindows/AssetManagerWindow.hpp" diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp deleted file mode 100644 index 19468ffb7..000000000 --- a/editor/src/DocumentWindows/EditorScene.cpp +++ /dev/null @@ -1,1628 +0,0 @@ -//// EditorScene.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: Guillaume HEIN -// Date: 10/11/2024 -// Description: Source for the editor scene document window -// -/////////////////////////////////////////////////////////////////////////////// - -#include "EditorScene.hpp" - -#include -#include - -#include "ADocumentWindow.hpp" -#include "Application.hpp" -#include "Definitions.hpp" -#include "EntityFactory3D.hpp" -#include "IconsFontAwesome.h" -#include "LightFactory.hpp" -#include "CameraFactory.hpp" -#include "Nexo.hpp" -#include "Texture.hpp" -#include "WindowRegistry.hpp" -#include "components/Camera.hpp" -#include "components/Render.hpp" -#include "components/RenderContext.hpp" -#include "components/Transform.hpp" -#include "components/Uuid.hpp" -#include "components/Editor.hpp" -#include "math/Matrix.hpp" -#include "context/Selector.hpp" -#include "context/actions/StateAction.hpp" -#include "context/actions/EntityActions.hpp" -#include "context/ActionManager.hpp" -#include "utils/String.hpp" -#include "utils/EditorProps.hpp" -#include "ImNexo/Widgets.hpp" -#include "ImNexo/Panels.hpp" - -#include -#include -#include -#include -#include - -namespace nexo::editor { - - void EditorScene::setup() - { - setupWindow(); - setupScene(); - - // ================= GLOBAL STATE ============================= - m_globalState = {static_cast(EditorState::GLOBAL)}; - m_globalState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Add entity", - "A", - [this]{ - this->m_popupManager.openPopup("Add new entity popup"); - }, - nullptr, - nullptr, - false, - } - } - } - ); - m_globalState.registerCommand( - { - "Control context", - "Ctrl", - nullptr, - nullptr, - nullptr, - true, - { - { - "Unhide all", - "H", - [this]{ - auto &app = getApp(); - const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); - for (const auto entity : entities) { - if (app.m_coordinator->entityHasComponent(entity)) { - auto &renderComponent = app.m_coordinator->getComponent(entity); - renderComponent.isRendered = true; - } - } - }, - nullptr, - nullptr, - false, - } - } - } - ); - m_globalState.registerCommand( - { - "Select all", - "A", - [this]{ - auto &selector = Selector::get(); - auto &app = nexo::getApp(); - auto &scene = app.getSceneManager().getScene(m_sceneId); - - selector.clearSelection(); - - for (const auto entity : scene.getEntities()) { - if (entity == m_editorCamera) continue; // Skip editor camera - - const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); - if (uuidComponent) - selector.addToSelection(uuidComponent->get().uuid, entity); - } - m_windowState = m_gizmoState; - }, - nullptr, - nullptr, - false - } - ); - m_windowState = m_globalState; - - // ================= GIZMO STATE ============================= - m_gizmoState = {static_cast(EditorState::GIZMO)}; - m_gizmoState.registerCommand( - { - "Delete", - "X", - [this]{ - auto &selector = Selector::get(); - const auto &selectedEntities = selector.getSelectedEntities(); - auto &app = nexo::getApp(); - auto& actionManager = ActionManager::get(); - auto deleteAction = actionManager.prepareEntityDeletion(selectedEntities[0]); - - for (const auto entity : selectedEntities) - app.deleteEntity(entity); - selector.clearSelection(); - this->m_windowState = m_globalState; - actionManager.recordAction(std::move(deleteAction)); - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoState.registerCommand( - { - "Hide", - "H", - [this]{ - auto &selector = Selector::get(); - const auto &selectedEntities = selector.getSelectedEntities(); - auto &app = nexo::getApp(); - - for (const auto entity : selectedEntities) { - if (app.m_coordinator->entityHasComponent(entity)) { - auto &renderComponent = app.m_coordinator->getComponent(entity); - renderComponent.isRendered = !renderComponent.isRendered; - } - } - selector.clearSelection(); - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoState.registerCommand( - { - "Translate", - "G", - [this]{ - this->m_windowState = m_gizmoTranslateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoState.registerCommand( - { - "Rotate", - "R", - [this]{ - this->m_windowState = m_gizmoRotateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoState.registerCommand( - { - "Scale", - "S", - [this]{ - this->m_windowState = m_gizmoScaleState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Toggle snapping", - "S", - [this]{ - m_snapTranslateOn = true; - m_snapRotateOn = true; - }, - [this]{ - m_snapTranslateOn = false; - m_snapRotateOn = false; - }, - nullptr, - false, - }, - { - "Hide all but selection", - "H", - [this]{ - auto &app = getApp(); - const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); - auto &selector = Selector::get(); - for (const auto entity : entities) { - if (app.m_coordinator->entityHasComponent(entity) && !selector.isEntitySelected(entity)) { - auto &renderComponent = app.m_coordinator->getComponent(entity); - renderComponent.isRendered = false; - } - } - }, - nullptr, - nullptr, - false, - } - } - } - ); - - // ================= TRANSLATE STATE ============================= - m_gizmoTranslateState = {static_cast(EditorState::GIZMO_TRANSLATE)}; - m_gizmoTranslateState.registerCommand( - { - "Universal", - "U", - [this]{ - this->m_windowState = m_gizmoState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoTranslateState.registerCommand( - { - "Translate", - "G", - [this]{ - this->m_windowState = m_gizmoTranslateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - [this]{ - if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) - this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; - else - this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; - }, - false, - } - ); - m_gizmoTranslateState.registerCommand( - { - "Rotate", - "R", - [this]{ - this->m_windowState = m_gizmoRotateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoTranslateState.registerCommand( - { - "Scale", - "S", - [this]{ - this->m_windowState = m_gizmoScaleState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoTranslateState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Exclude X", - "X", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_X); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_X); - }, - nullptr, - false, - }, - { - "Exclude Y", - "Y", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Y); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Y); - }, - nullptr, - false, - }, - { - "Exclude Z", - "Z", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Z); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Z); - }, - nullptr, - false, - }, - { - "Toggle snapping", - "S", - [this]{ - m_snapTranslateOn = true; - }, - [this]{ - m_snapTranslateOn = false; - }, - nullptr, - false, - } - } - } - ); - m_gizmoTranslateState.registerCommand( - { - "Lock X", - "X", - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_X; - }, - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - false, - } - ); - m_gizmoTranslateState.registerCommand( - { - "Lock Y", - "Y", - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Y; - }, - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - false, - } - ); - m_gizmoTranslateState.registerCommand( - { - "Lock Z", - "Z", - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Z; - }, - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - false, - } - ); - - // ================= ROTATE STATE ============================= - m_gizmoRotateState = {static_cast(EditorState::GIZMO_ROTATE)}; - m_gizmoRotateState.registerCommand( - { - "Universal", - "U", - [this]{ - this->m_windowState = m_gizmoState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoRotateState.registerCommand( - { - "Rotate", - "R", - [this]{ - this->m_windowState = m_gizmoRotateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, // << Key pressed - nullptr, // << Key released - [this]{ - if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) - this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; - else - this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; - }, // << Key repeat - false, - } - ); - m_gizmoRotateState.registerCommand( - { - "Translate", - "G", - [this]{ - this->m_windowState = m_gizmoTranslateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoRotateState.registerCommand( - { - "Scale", - "S", - [this]{ - this->m_windowState = m_gizmoScaleState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoRotateState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Exclude X", - "X", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_X); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_X); - }, - nullptr, - false, - }, - { - "Exclude Y", - "Y", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Y); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Y); - }, - nullptr, - false, - }, - { - "Exclude Z", - "Z", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Z); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Z); - }, - nullptr, - false, - }, - { - "Toggle snapping", - "S", - [this]{ - m_snapRotateOn = true; - }, - [this]{ - m_snapRotateOn = false; - }, - nullptr, - false, - }, - } - } - ); - m_gizmoRotateState.registerCommand( - { - "Lock X", - "X", - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_X; - }, - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - false, - } - ); - m_gizmoRotateState.registerCommand( - { - "Lock Y", - "Y", - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Y; - }, - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - false, - } - ); - m_gizmoRotateState.registerCommand( - { - "Lock Z", - "Z", - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Z; - }, - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - false, - } - ); - - // ================= SCALE STATE ============================= - m_gizmoScaleState = {static_cast(EditorState::GIZMO_SCALE)}; - m_gizmoScaleState.registerCommand( - { - "Universal", - "U", - [this]{ - this->m_windowState = m_gizmoState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoScaleState.registerCommand( - { - "Scale", - "S", - [this]{ - this->m_windowState = m_gizmoScaleState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - [this]{ - if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) - this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; - else - this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; - }, - false, - } - ); - m_gizmoScaleState.registerCommand( - { - "Translate", - "G", - [this]{ - this->m_windowState = m_gizmoTranslateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoScaleState.registerCommand( - { - "Rotate", - "R", - [this]{ - this->m_windowState = m_gizmoRotateState; - this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - nullptr, - false, - } - ); - m_gizmoScaleState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Exclude X", - "X", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_X); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_X); - }, - nullptr, - false, - }, - { - "Exclude Y", - "Y", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Y); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Y); - }, - nullptr, - false, - }, - { - "Exclude Z", - "Z", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Z); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Z); - }, - nullptr, - false, - } - } - } - ); - m_gizmoScaleState.registerCommand( - { - "Lock X", - "X", - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_X; - }, - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - false, - } - ); - m_gizmoScaleState.registerCommand( - { - "Lock Y", - "Y", - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Y; - }, - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - false, - } - ); - m_gizmoScaleState.registerCommand( - { - "Lock Z", - "Z", - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Z; - }, - [this]{ - this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - false, - } - ); - } - - void EditorScene::setupScene() - { - auto &app = getApp(); - - m_sceneId = static_cast(app.getSceneManager().createScene(m_windowName)); - renderer::FramebufferSpecs framebufferSpecs; - framebufferSpecs.attachments = { - renderer::FrameBufferTextureFormats::RGBA8, renderer::FrameBufferTextureFormats::RED_INTEGER, renderer::FrameBufferTextureFormats::Depth - }; - framebufferSpecs.width = static_cast(m_viewSize.x); - framebufferSpecs.height = static_cast(m_viewSize.y); - const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs); - m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget); - auto &cameraComponent = app.m_coordinator->getComponent(m_editorCamera); - cameraComponent.render = true; - app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_editorCamera)); - components::PerspectiveCameraController controller; - Application::m_coordinator->addComponent(static_cast(m_editorCamera), controller); - components::EditorCameraTag editorCameraTag; - Application::m_coordinator->addComponent(m_editorCamera, editorCameraTag); - m_activeCamera = m_editorCamera; - - m_sceneUuid = app.getSceneManager().getScene(m_sceneId).getUuid(); - if (m_defaultScene) - loadDefaultEntities(); - } - - void EditorScene::loadDefaultEntities() const - { - auto &app = getApp(); - scene::Scene &scene = app.getSceneManager().getScene(m_sceneId); - const ecs::Entity ambientLight = LightFactory::createAmbientLight({0.5f, 0.5f, 0.5f}); - scene.addEntity(ambientLight); - const ecs::Entity pointLight = LightFactory::createPointLight({2.0f, 5.0f, 0.0f}); - utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); - scene.addEntity(pointLight); - const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.2f, -1.0f, -0.3f}); - scene.addEntity(directionalLight); - const ecs::Entity spotLight = LightFactory::createSpotLight({-2.0f, 5.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}); - utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); - scene.addEntity(spotLight); - const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, 0.25f, 0.0f}, {20.0f, 0.5f, 20.0f}, - {0.0f, 0.0f, 0.0f}, {0.05f * 1.7, 0.09f * 1.35, 0.13f * 1.45, 1.0f}); - app.getSceneManager().getScene(m_sceneId).addEntity(basicCube); - } - - void EditorScene::setupWindow() - { - constexpr auto size = ImVec2(1280, 720); - m_viewSize = size; - } - - void EditorScene::shutdown() - { - // Should probably check if it is necessary to delete the scene here ? (const for now) - } - - void EditorScene::handleKeyEvents() const - { - // Will be implemeneted later - } - - void EditorScene::setCamera(ecs::Entity cameraId) - { - auto &app = getApp(); - auto &oldCameraComponent = app.m_coordinator->getComponent(m_activeCamera); - oldCameraComponent.active = false; - oldCameraComponent.render = false; - m_activeCamera = cameraId; - } - - void EditorScene::initialToolbarSetup(const float buttonWidth, const float buttonHeight) - { - ImVec2 toolbarPos = m_viewPosition; - toolbarPos.x += 10.0f; - - ImGui::SetCursorScreenPos(toolbarPos); - - ImVec2 toolbarSize = ImVec2(m_viewSize.x - buttonWidth, 50.0f); - - 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); - - ImGui::SetCursorPosY((ImGui::GetWindowHeight() - ImGui::GetFrameHeight()) * 0.5f); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 0)); - } - - bool EditorScene::renderToolbarButton(const std::string &uniqueId, const std::string &icon, const std::string &tooltip, const std::vector & gradientStop, bool *rightClicked) - { - constexpr float buttonWidth = 35.0f; - constexpr float buttonHeight = 35.0f; - 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); - 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, -5.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); - }, - .tooltip = "Create Cube" - } - }; - 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 - } - // Snap on scale is kinda strange, the IsOver is not able to detect it, so for now we disable it - // { - // .uniqueId = "toggle_scale_snap", - // .icon = ICON_FA_EXPAND, - // .onClick = [this]() - // { - // this->m_snapScaleOn = !m_snapScaleOn; - // }, - // .onRightClick = [this]() - // { - // this->m_popupManager.openPopup("Snap settings popup", ImVec2(400, 180)); - // }, - // .tooltip = "Toggle Scale Snap", - // .buttonGradient = (m_snapScaleOn) ? m_selectedGradient : buttonGradient - // } - }; - ImNexo::ButtonDropDown(snapButtonPos, buttonSize, buttonProps, showSnapMenu); - } - - void EditorScene::snapSettingsPopup() - { - 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); - 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); - // Empty columns to match the first table's structure - ImGui::TableSetupColumn("##Empty1", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##Empty2", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - ImNexo::RowDragFloat1("Rotate Snap", "", &this->m_angleSnap); - ImGui::EndTable(); - } - ImGui::Spacing(); - ImGui::Spacing(); - - float buttonWidth = 120.0f; - float windowWidth = ImGui::GetWindowSize().x; - ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); - - if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) - { - m_popupManager.closePopupInContext(); - } - ImGui::Unindent(10.0f); - ImGui::PopStyleVar(); - m_popupManager.closePopup(); - } - } - - void EditorScene::gridSettingsPopup() - { - if (m_popupManager.showPopupModal("Grid settings")) - { - auto &app = getApp(); - components::RenderContext::GridParams &gridSettings = - app.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); - ImNexo::RowDragFloat1("Grid size", "", &gridSettings.gridSize, 50.0f, 150.0f); - 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"); - ImNexo::RowDragFloat1("Cell size", "", &gridSettings.cellSize, 0.1f, 20.0f, 0.02f); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("The size of the internal cells"); - ImGui::EndTable(); - } - - ImGui::Spacing(); - ImGui::Spacing(); - - float buttonWidth = 120.0f; - float windowWidth = ImGui::GetWindowSize().x; - ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); - - if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) - { - m_popupManager.closePopupInContext(); - } - ImGui::Unindent(10.0f); - ImGui::PopStyleVar(); - m_popupManager.closePopup(); - } - } - - void EditorScene::renderEditorCameraToolbarButton() - { - auto &app = getApp(); - auto &selector = Selector::get(); - bool editorMode = m_activeCamera == m_editorCamera; - if (m_activeCamera == m_editorCamera) { - if (renderToolbarButton("editor_camera", ICON_FA_CAMERA, "Edit Editor Camera Setting", m_buttonGradient)) { - const auto &uuidComponent = app.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 = app.m_coordinator->getComponent(m_activeCamera); - oldCameraComponent.active = false; - oldCameraComponent.render = false; - m_activeCamera = m_editorCamera; - auto &editorCameraComponent = app.m_coordinator->getComponent(m_activeCamera); - editorCameraComponent.render = true; - editorCameraComponent.active = true; - } - } - } - - bool EditorScene::renderGizmoModeToolbarButton(const bool showGizmoModeMenu, ImNexo::ButtonProps &activeGizmoMode, ImNexo::ButtonProps &inactiveGizmoMode) - { - static const ImNexo::ButtonProps gizmoLocalModeButtonProps = {"local_coords", ICON_FA_CROSSHAIRS, [this]() {this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL;}, nullptr, "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; - inactiveGizmoMode = gizmoWorldModeButtonProps; - } else { - activeGizmoMode = gizmoWorldModeButtonProps; - inactiveGizmoMode = gizmoLocalModeButtonProps; - } - return renderToolbarButton(activeGizmoMode.uniqueId, activeGizmoMode.icon, - activeGizmoMode.tooltip, showGizmoModeMenu ? m_selectedGradient : m_buttonGradient); - } - - void EditorScene::renderToolbar() - { - auto &app = getApp(); - constexpr float buttonWidth = 35.0f; - constexpr float buttonHeight = 35.0f; - constexpr ImVec2 buttonSize{buttonWidth, buttonHeight}; - ImVec2 originalCursorPos = ImGui::GetCursorPos(); - - initialToolbarSetup(buttonWidth, buttonHeight); - - // -------------------------------- BUTTONS ------------------------------- - // -------- Add primitve button -------- - // This can open a submenu, see at the end - 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; - - ImGui::SameLine(); - - // -------- Editor camera settings / Switch back to editor camera button -------- - renderEditorCameraToolbarButton(); - - ImGui::SameLine(); - - // -------- Gizmo operation button -------- - static const ImNexo::ButtonProps gizmoTranslateButtonProps = ImNexo::ButtonProps{"translate", ICON_FA_ARROWS, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE;}, nullptr, "Translate"}; - static const ImNexo::ButtonProps gizmoRotateButtonProps = ImNexo::ButtonProps{"rotate", ICON_FA_REFRESH, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE;}, nullptr, "Rotate"}; - static const ImNexo::ButtonProps gizmoScaleButtonProps = ImNexo::ButtonProps{"scale", ICON_FA_EXPAND, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE;}, nullptr, "Scale"}; - static const ImNexo::ButtonProps gizmoUniversalButtonProps = ImNexo::ButtonProps{"universal", ICON_FA_ARROWS_ALT, [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; - } - - 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; - - ImGui::SameLine(); - - // -------- Gizmo operation button -------- - ImNexo::ButtonProps activeGizmoMode; - ImNexo::ButtonProps inactiveGizmoMode; - ImVec2 changeGizmoModePos = ImGui::GetCursorScreenPos(); - static bool showGizmoModeMenu = false; - 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(); - 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; - - ImGui::SameLine(); - - // -------- Grid enabled button -------- - bool rightClicked = false; - components::RenderContext::GridParams &gridParams = app.m_coordinator->getSingletonComponent().gridParams; - if (renderToolbarButton("grid_enabled", ICON_FA_TH_LARGE, "Enable / Disable grid", gridParams.enabled ? m_selectedGradient : m_buttonGradient, &rightClicked)) - { - gridParams.enabled = !gridParams.enabled; - - } - 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 - 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_snapToGrid; - } - - ImGui::SameLine(); - - // -------- Enable wireframe button -------- - if (renderToolbarButton("wireframe", ICON_FA_CUBE, "Enable / Disable wireframe", m_wireframeEnabled ? m_selectedGradient : m_buttonGradient)) - { - m_wireframeEnabled = !m_wireframeEnabled; - } - - ImGui::SameLine(); - - // -------- Play button button -------- - renderToolbarButton("play", ICON_FA_PLAY, "Play scene", m_buttonGradient); - - ImGui::PopStyleVar(); - ImGui::EndChild(); - ImGui::PopStyleColor(); - - // -------------------------------- SUB-MENUS ------------------------------- - // -------- Primitives sub-menus -------- - if (showPrimitiveMenu) - { - renderPrimitiveSubMenu(addPrimButtonPos, buttonSize, showPrimitiveMenu); - } - - // -------- Gizmo operation sub-menu -------- - 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); - } - - // -------- Snap settings popup -------- - snapSettingsPopup(); - - // -------- Grid settings popup -------- - gridSettingsPopup(); - - // IMPORTANT: Restore original cursor position so we don't affect layout - ImGui::SetCursorPos(originalCursorPos); - } - - ImGuizmo::OPERATION EditorScene::getLastGuizmoOperation() - { - for (int bitPos = 0; bitPos <= 13; bitPos++) - { - // Create a mask for this bit position - ImGuizmo::OPERATION op = static_cast(1u << bitPos); - - // Check if this bit is set - if (ImGuizmo::IsOver(op)) - return op; - } - return ImGuizmo::OPERATION::UNIVERSAL; - } - - void EditorScene::renderGizmo() - { - const auto &coord = nexo::Application::m_coordinator; - auto const &selector = Selector::get(); - if (selector.getPrimarySelectionType() == SelectionType::SCENE || - selector.getSelectedScene() != m_sceneId || - !selector.hasSelection()) - return; - - // Get all selected entities and find the first one with a transform component - const auto &selectedEntities = selector.getSelectedEntities(); - ecs::Entity targetEntity = selector.getPrimaryEntity(); - auto transf = coord->tryGetComponent(targetEntity); - - if (!transf) { - for (const auto entity : selectedEntities) { - if (entity != targetEntity) { - auto entityTransf = coord->tryGetComponent(entity); - if (entityTransf) { - targetEntity = entity; - transf = entityTransf; - break; - } - } - } - } - - if (!transf) - return; - - const auto &transformCameraComponent = coord->getComponent(m_activeCamera); - auto &cameraComponent = coord->getComponent(m_activeCamera); - - ImGuizmo::SetOrthographic(cameraComponent.type == components::CameraType::ORTHOGRAPHIC); - ImGuizmo::SetDrawlist(); - ImGuizmo::SetID(static_cast(targetEntity)); - ImGuizmo::SetRect(m_viewPosition.x, m_viewPosition.y, m_viewSize.x, m_viewSize.y); - - glm::mat4 viewMatrix = cameraComponent.getViewMatrix(transformCameraComponent); - glm::mat4 projectionMatrix = cameraComponent.getProjectionMatrix(); - - const glm::mat4 rotationMat = glm::toMat4(transf->get().quat); - glm::mat4 transformMatrix = glm::translate(glm::mat4(1.0f), transf->get().pos) * - rotationMat * - glm::scale(glm::mat4(1.0f), {transf->get().size.x, transf->get().size.y, transf->get().size.z}); - - static ImGuizmo::OPERATION lastOperation; - if (!ImGuizmo::IsUsing()) { - lastOperation = getLastGuizmoOperation(); - } - - float *snap = nullptr; - if (m_snapTranslateOn && lastOperation & ImGuizmo::OPERATION::TRANSLATE) { - snap = &m_snapTranslate.x; - } else if (m_snapRotateOn && lastOperation & ImGuizmo::OPERATION::ROTATE) { - snap = &m_angleSnap; - } - - // Track ImGuizmo state across frames - static bool wasUsingGizmo = false; - bool isUsingGizmo = ImGuizmo::IsUsing(); - - // Store initial states when starting to use gizmo - static std::unordered_map initialStates; - - // Check if we're just starting to use the gizmo this frame - if (!wasUsingGizmo && ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGuizmo::IsOver()) { - initialStates.clear(); - initialStates[targetEntity] = transf->get().save(); - for (const auto entity : selectedEntities) { - if (entity != targetEntity) { - auto entityTransf = coord->tryGetComponent(entity); - if (entityTransf) - initialStates[entity] = entityTransf->get().save(); - } - } - } - - ImGuizmo::Enable(true); - ImGuizmo::Manipulate(glm::value_ptr(viewMatrix), glm::value_ptr(projectionMatrix), - m_currentGizmoOperation, - m_currentGizmoMode, - glm::value_ptr(transformMatrix), - nullptr, snap); - - isUsingGizmo = ImGuizmo::IsUsing(); - - if (isUsingGizmo) { - // Disable camera movement during gizmo manipulation - cameraComponent.active = false; - - glm::vec3 originalPos = transf->get().pos; - glm::quat originalRot = transf->get().quat; - glm::vec3 originalScale = transf->get().size; - - glm::vec3 newPos; - glm::vec3 newScale; - glm::quat newRot; - - math::decomposeTransformQuat(transformMatrix, newPos, newRot, newScale); - - // Calculate deltas - glm::vec3 positionDelta = newPos - originalPos; - glm::vec3 scaleFactor = newScale / originalScale; // Element-wise division - glm::quat rotationDelta = newRot * glm::inverse(originalRot); - - transf->get().pos = newPos; - transf->get().quat = newRot; - transf->get().size = newScale; - - for (const auto entity : selectedEntities) { - if (entity == targetEntity) continue; // Skip target entity, already transformed - - auto entityTransf = coord->tryGetComponent(entity); - if (entityTransf) { - if (m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE) - entityTransf->get().pos += positionDelta; - - if (m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE) - entityTransf->get().quat = rotationDelta * entityTransf->get().quat; - - if (m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE) { - entityTransf->get().size.x *= scaleFactor.x; - entityTransf->get().size.y *= scaleFactor.y; - entityTransf->get().size.z *= scaleFactor.z; - } - } - } - } else if (wasUsingGizmo) { - cameraComponent.active = true; - - auto& actionManager = ActionManager::get(); - if (selectedEntities.size() > 1) { - // Multiple entities selected - auto groupAction = actionManager.createActionGroup(); - bool anyChanges = false; - - for (const auto entity : selectedEntities) { - auto entityTransf = coord->tryGetComponent(entity); - if (entityTransf && initialStates.find(entity) != initialStates.end()) { - auto beforeState = initialStates[entity]; - auto afterState = entityTransf->get().save(); - - // Check if the transform actually changed - if (beforeState.position != afterState.position || - beforeState.rotation != afterState.rotation || - beforeState.scale != afterState.scale) { - - std::string operationName; - switch (m_currentGizmoOperation) { - case ImGuizmo::OPERATION::TRANSLATE: operationName = "Move"; break; - case ImGuizmo::OPERATION::ROTATE: operationName = "Rotate"; break; - case ImGuizmo::OPERATION::SCALE: operationName = "Scale"; break; - default: operationName = "Transform"; break; - } - - auto action = std::make_unique>( - entity, beforeState, afterState); - - groupAction->addAction(std::move(action)); - anyChanges = true; - } - } - } - - // Only record if any transforms changed - if (anyChanges) - actionManager.recordAction(std::move(groupAction)); - } else { - auto beforeState = initialStates[targetEntity]; - auto afterState = transf->get().save(); - - // Check if the transform actually changed - if (beforeState.position != afterState.position || - beforeState.rotation != afterState.rotation || - beforeState.scale != afterState.scale) { - actionManager.recordComponentChange( - targetEntity, beforeState, afterState); - } - } - - initialStates.clear(); - } - - wasUsingGizmo = isUsingGizmo; - } - - void EditorScene::renderView() - { - const auto viewPortOffset = ImGui::GetCursorPos(); - auto &cameraComponent = Application::m_coordinator->getComponent(m_activeCamera); - const ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail(); - - // Resize handling - if ((viewportPanelSize.x > 0 && viewportPanelSize.y > 0) && (m_viewSize.x != viewportPanelSize.x || m_viewSize.y != viewportPanelSize.y)) - { - cameraComponent.resize(static_cast(viewportPanelSize.x), - static_cast(viewportPanelSize.y)); - - m_viewSize.x = viewportPanelSize.x; - m_viewSize.y = viewportPanelSize.y; - } - - // Render framebuffer - const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); - ImGui::Image(static_cast(static_cast(textureId)), m_viewSize, ImVec2(0, 1), ImVec2(1, 0)); - - const auto windowSize = ImGui::GetWindowSize(); - auto minBounds = ImGui::GetWindowPos(); - - minBounds.x += viewPortOffset.x; - minBounds.y += viewPortOffset.y; - - const ImVec2 maxBounds = {minBounds.x + windowSize.x, minBounds.y + windowSize.y}; - m_viewportBounds[0] = minBounds; - m_viewportBounds[1] = maxBounds; - } - - void EditorScene::show() - { - 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 = m_windowName + std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId); - - if (ImGui::Begin(sceneWindowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse)) - { - firstDockSetup(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId)); - auto &app = getApp(); - - // Add some spacing after the toolbar - ImGui::Dummy(ImVec2(0, 5)); - m_viewPosition = ImGui::GetCursorScreenPos(); - - m_focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); - m_hovered = ImGui::IsWindowHovered(ImGuiFocusedFlags_RootAndChildWindows); - app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused); - if (m_focused && selector.getSelectedScene() != m_sceneId) - { - selector.setSelectedScene(m_sceneId); - selector.clearSelection(); - } - - if (m_activeCamera == -1) - { - // No active camera, render the text at the center of the screen - ImVec2 textSize = ImGui::CalcTextSize("No active camera"); - auto textPos = ImVec2((m_viewSize.x - textSize.x) / 2, (m_viewSize.y - textSize.y) / 2); - - ImGui::SetCursorScreenPos(textPos); - ImGui::Text("No active camera"); - } - else - { - renderView(); - renderGizmo(); - renderToolbar(); - - } - - if (m_popupManager.showPopup("Add new entity popup")) - { - auto &app = Application::getInstance(); - auto &sceneManager = app.getSceneManager(); - const auto sceneId = m_sceneId; - - // --- Primitives submenu --- - if (ImGui::BeginMenu("Primitives")) { - if (ImGui::MenuItem("Cube")) { - const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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); - } - ImGui::EndMenu(); - } - - // --- Model item (with file‑dialog) --- - if (ImGui::MenuItem("Model")) { - //TODO: import model - } - - // --- Lights submenu --- - 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); - } - if (ImGui::MenuItem("Point")) { - const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); - utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); - sceneManager.getScene(sceneId).addEntity(pointLight); - } - if (ImGui::MenuItem("Spot")) { - const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); - utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); - sceneManager.getScene(sceneId).addEntity(spotLight); - } - ImGui::EndMenu(); - } - - // --- Camera item --- - if (ImGui::MenuItem("Camera")) { - m_popupManager.openPopupWithCallback("Popup camera inspector", [this]() { - ImNexo::CameraInspector(this->m_sceneId, this->m_viewSize); - }, ImVec2(1440,900)); - } - m_popupManager.closePopup(); - } - } - ImGui::End(); - ImGui::PopStyleVar(); - } - - void EditorScene::update() - { - auto &selector = Selector::get(); - if (!m_opened || m_activeCamera == -1) - return; - if (m_focused && m_hovered) - handleKeyEvents(); - SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; - runEngine(m_sceneId, RenderingType::FRAMEBUFFER, sceneType); - - auto const &cameraComponent = Application::m_coordinator->getComponent(static_cast(m_activeCamera)); - - // Handle mouse clicks for selection - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsUsing() && m_focused) - { - auto [mx, my] = ImGui::GetMousePos(); - mx -= m_viewportBounds[0].x; - my -= m_viewportBounds[0].y; - - // Flip the y-coordinate to match opengl texture format - my = m_viewSize.y - my; - - // Check if mouse is inside viewport - if (!(mx >= 0 && my >= 0 && mx < m_viewSize.x && my < m_viewSize.y)) - return; - - cameraComponent.m_renderTarget->bind(); - int entityId = cameraComponent.m_renderTarget->getPixel(1, static_cast(mx), static_cast(my)); - cameraComponent.m_renderTarget->unbind(); - - // Check for multi-selection key modifiers - bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); - bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); - - if (entityId == -1) { - // Clicked on empty space - clear selection unless shift/ctrl is held - if (!isShiftPressed && !isCtrlPressed) { - selector.clearSelection(); - m_windowState = m_globalState; - } - return; - } - - const auto uuid = Application::m_coordinator->tryGetComponent(entityId); - if (uuid) { - auto &app = getApp(); - - // Determine selection type - SelectionType selType = SelectionType::ENTITY; - if (app.m_coordinator->entityHasComponent(entityId)) { - selType = SelectionType::CAMERA; - } else if (app.m_coordinator->entityHasComponent(entityId)) { - selType = SelectionType::DIR_LIGHT; - } else if (app.m_coordinator->entityHasComponent(entityId)) { - selType = SelectionType::POINT_LIGHT; - } else if (app.m_coordinator->entityHasComponent(entityId)) { - selType = SelectionType::SPOT_LIGHT; - } else if (app.m_coordinator->entityHasComponent(entityId)) { - selType = SelectionType::AMBIENT_LIGHT; - } - - // Handle different selection modes - if (isCtrlPressed) - selector.toggleSelection(uuid->get().uuid, entityId, selType); - else if (isShiftPressed) - selector.addToSelection(uuid->get().uuid, entityId, selType); - else - selector.selectEntity(uuid->get().uuid, entityId, selType); - - // Update window state based on primary selection's transform mode - if (selector.hasSelection()) { - if (m_currentGizmoOperation == ImGuizmo::OPERATION::TRANSLATE) - m_windowState = m_gizmoTranslateState; - else if (m_currentGizmoOperation == ImGuizmo::OPERATION::ROTATE) - m_windowState = m_gizmoRotateState; - else if (m_currentGizmoOperation == ImGuizmo::OPERATION::SCALE) - m_windowState = m_gizmoScaleState; - else - m_windowState = m_gizmoState; - } - - selector.setSelectedScene(m_sceneId); - } - } - } - -} diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp similarity index 89% rename from editor/src/DocumentWindows/EditorScene.hpp rename to editor/src/DocumentWindows/EditorScene/EditorScene.hpp index 8e92a781d..ed985e6dd 100644 --- a/editor/src/DocumentWindows/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -18,7 +18,7 @@ #include "IDocumentWindow.hpp" #include "core/scene/SceneManager.hpp" #include "inputs/WindowState.hpp" -#include "PopupManager.hpp" +#include "../PopupManager.hpp" #include #include #include "ImNexo/Widgets.hpp" @@ -145,6 +145,13 @@ namespace nexo::editor { */ void setupScene(); + void setupGlobalState(); + void setupGizmoState(); + void setupGizmoTranslateState(); + void setupGizmoRotateState(); + void setupGizmoScaleState(); + void setupShortcuts(); + /** * @brief Populates the scene with default entities. * @@ -153,15 +160,6 @@ namespace nexo::editor { */ void loadDefaultEntities() const; - /** - * @brief Handles keyboard input events for the scene. - * - * Processes key presses for navigation, selection, and other scene-specific - * operations when the scene window is focused. - * @note not implemented yet - */ - void handleKeyEvents() const; - /** * @brief Renders the toolbar overlay within the main scene view. * @@ -274,6 +272,19 @@ namespace nexo::editor { * If the gizmo is actively manipulated, the entity's transform component is updated with the new values. */ void renderGizmo(); + void setupGizmoContext(const components::TransformComponent& cameraTransform, + const components::CameraComponent& camera); + float* getSnapSettingsForOperation(ImGuizmo::OPERATION operation); + void captureInitialTransformStates(const std::vector& entities); + void applyTransformToEntities( + ecs::Entity sourceEntity, + const components::TransformComponent& sourceTransform, + const components::TransformComponent& newTransform, + const std::vector& targetEntities); + void createTransformUndoActions(const std::vector& entities); + static bool s_wasUsingGizmo; + static ImGuizmo::OPERATION s_lastOperation; + static std::unordered_map s_initialTransformStates; /** * @brief Renders the main viewport showing the 3D scene. @@ -282,6 +293,13 @@ namespace nexo::editor { * rendered scene, and updates viewport bounds for input handling. */ void renderView(); + void renderNoActiveCamera(); + void renderNewEntityPopup(); + + void handleSelection(); + int sampleEntityTexture(float mx, float my); + void updateSelection(int entityId, bool isShiftPressed, bool isCtrlPressed); + void updateWindowState(); enum class EditorState { GLOBAL, diff --git a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp new file mode 100644 index 000000000..90161ce1b --- /dev/null +++ b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp @@ -0,0 +1,296 @@ +//// Gizmo.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: 28/04/2025 +// Description: Source file for the gizmo rendering +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EditorScene.hpp" +#include "context/Selector.hpp" +#include "context/ActionManager.hpp" +#include "math/Matrix.hpp" + +#include + +namespace nexo::editor { + // Class-level variables for tracking gizmo state between frames + bool EditorScene::s_wasUsingGizmo = false; + ImGuizmo::OPERATION EditorScene::s_lastOperation = ImGuizmo::OPERATION::UNIVERSAL; + std::unordered_map EditorScene::s_initialTransformStates; + + static ImGuizmo::OPERATION getActiveGuizmoOperation() + { + for (int bitPos = 0; bitPos <= 13; bitPos++) + { + ImGuizmo::OPERATION op = static_cast(1u << bitPos); + if (ImGuizmo::IsOver(op)) + return op; + } + return ImGuizmo::OPERATION::UNIVERSAL; + } + + static std::optional findEntityWithTransform(const std::vector& entities) + { + const auto& coord = nexo::Application::m_coordinator; + + for (const auto& entity : entities) { + if (coord->tryGetComponent(entity)) { + return entity; + } + } + + return std::nullopt; + } + + void EditorScene::setupGizmoContext(const components::TransformComponent& cameraTransform, + const components::CameraComponent& camera) + { + ImGuizmo::SetOrthographic(camera.type == components::CameraType::ORTHOGRAPHIC); + ImGuizmo::SetDrawlist(); + ImGuizmo::SetRect(m_viewPosition.x, m_viewPosition.y, m_viewSize.x, m_viewSize.y); + ImGuizmo::Enable(true); + } + + static glm::mat4 buildEntityTransformMatrix(const components::TransformComponent& transform) + { + const glm::mat4 rotationMat = glm::toMat4(transform.quat); + return glm::translate(glm::mat4(1.0f), transform.pos) * + rotationMat * + glm::scale(glm::mat4(1.0f), transform.size); + } + + float* EditorScene::getSnapSettingsForOperation(ImGuizmo::OPERATION operation) + { + if (m_snapTranslateOn && operation & ImGuizmo::OPERATION::TRANSLATE) { + return &m_snapTranslate.x; + } else if (m_snapRotateOn && operation & ImGuizmo::OPERATION::ROTATE) { + return &m_angleSnap; + } + return nullptr; + } + + void EditorScene::captureInitialTransformStates(const std::vector& entities) + { + const auto& coord = nexo::Application::m_coordinator; + s_initialTransformStates.clear(); + + for (const auto& entity : entities) { + auto transform = coord->tryGetComponent(entity); + if (transform) { + s_initialTransformStates[entity] = transform->get().save(); + } + } + } + + void EditorScene::applyTransformToEntities( + ecs::Entity sourceEntity, + const components::TransformComponent& sourceTransform, + const components::TransformComponent& newTransform, + const std::vector& targetEntities) + { + const auto& coord = nexo::Application::m_coordinator; + + // Calculate transformation deltas + glm::vec3 positionDelta = newTransform.pos - sourceTransform.pos; + glm::vec3 scaleFactor = newTransform.size / sourceTransform.size; + glm::quat rotationDelta = newTransform.quat * glm::inverse(sourceTransform.quat); + + // Apply transforms to all selected entities except the source + for (const auto& entity : targetEntities) { + if (entity == sourceEntity) continue; + + auto entityTransform = coord->tryGetComponent(entity); + if (!entityTransform) continue; + + // Apply relevant transformations based on current operation + if (m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE) { + entityTransform->get().pos += positionDelta; + } + + if (m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE) { + entityTransform->get().quat = rotationDelta * entityTransform->get().quat; + } + + if (m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE) { + entityTransform->get().size.x *= scaleFactor.x; + entityTransform->get().size.y *= scaleFactor.y; + entityTransform->get().size.z *= scaleFactor.z; + } + } + } + + + static bool hasTransformChanged(const components::TransformComponent::Memento& before, + const components::TransformComponent::Memento& after) + { + return before.position != after.position || + before.rotation != after.rotation || + before.scale != after.scale; + } + + void EditorScene::createTransformUndoActions(const std::vector& entities) + { + const auto& coord = nexo::Application::m_coordinator; + auto& actionManager = ActionManager::get(); + + // If multiple entities selected, create a group action + if (entities.size() > 1) { + auto groupAction = actionManager.createActionGroup(); + bool anyChanges = false; + + for (const auto& entity : entities) { + auto transform = coord->tryGetComponent(entity); + if (!transform) continue; + + auto it = s_initialTransformStates.find(entity); + if (it == s_initialTransformStates.end()) continue; + + auto beforeState = it->second; + auto afterState = transform->get().save(); + + // Check if anything actually changed + if (hasTransformChanged(beforeState, afterState)) { + auto action = std::make_unique>( + entity, beforeState, afterState); + groupAction->addAction(std::move(action)); + anyChanges = true; + } + } + + if (anyChanges) { + actionManager.recordAction(std::move(groupAction)); + } + } + // Single entity selected - simpler action + else if (entities.size() == 1) { + auto entity = entities[0]; + auto transform = coord->tryGetComponent(entity); + + if (transform && s_initialTransformStates.count(entity)) { + auto beforeState = s_initialTransformStates[entity]; + auto afterState = transform->get().save(); + + if (hasTransformChanged(beforeState, afterState)) { + actionManager.recordComponentChange( + entity, beforeState, afterState); + } + } + } + + // Reset stored states + s_initialTransformStates.clear(); + } + + void EditorScene::renderGizmo() + { + const auto& coord = nexo::Application::m_coordinator; + auto const& selector = Selector::get(); + + // Skip if no valid selection + 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(); + + auto primaryTransform = coord->tryGetComponent(primaryEntity); + if (!primaryTransform) { + auto entityWithTransform = findEntityWithTransform(selectedEntities); + if (!entityWithTransform) return; // No entity with transform found + + primaryEntity = *entityWithTransform; + primaryTransform = coord->tryGetComponent(primaryEntity); + } + + // Camera setup + const auto& cameraTransform = coord->getComponent(m_activeCamera); + auto& camera = coord->getComponent(m_activeCamera); + + // Configure ImGuizmo + setupGizmoContext(cameraTransform, camera); + ImGuizmo::SetID(static_cast(primaryEntity)); + + // Prepare matrices + glm::mat4 viewMatrix = camera.getViewMatrix(cameraTransform); + glm::mat4 projectionMatrix = camera.getProjectionMatrix(); + glm::mat4 transformMatrix = buildEntityTransformMatrix(primaryTransform->get()); + + // Track which operation is active + if (!ImGuizmo::IsUsing()) { + s_lastOperation = getActiveGuizmoOperation(); + } + + // Get snap settings if applicable + float* snap = getSnapSettingsForOperation(s_lastOperation); + + // Track ImGuizmo usage state + bool isUsingGizmo = ImGuizmo::IsUsing(); + + // Capture initial state when starting to use gizmo + if (!s_wasUsingGizmo && ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGuizmo::IsOver()) { + captureInitialTransformStates(selectedEntities); + } + + // Perform the actual manipulation + ImGuizmo::Manipulate( + glm::value_ptr(viewMatrix), + glm::value_ptr(projectionMatrix), + m_currentGizmoOperation, + m_currentGizmoMode, + glm::value_ptr(transformMatrix), + nullptr, + snap + ); + + // Update isUsingGizmo after manipulation + isUsingGizmo = ImGuizmo::IsUsing(); + + if (isUsingGizmo) { + // Disable camera movement during manipulation + camera.active = false; + + // Extract the original transform values + components::TransformComponent originalTransform = primaryTransform->get(); + + // Extract the new transform values from the matrix + glm::vec3 newPos; + glm::vec3 newScale; + glm::quat newRot; + math::decomposeTransformQuat(transformMatrix, newPos, newRot, newScale); + + // Update the primary entity's transform + primaryTransform->get().pos = newPos; + primaryTransform->get().quat = newRot; + primaryTransform->get().size = newScale; + + // Apply changes to other selected entities + applyTransformToEntities( + primaryEntity, + originalTransform, + primaryTransform->get(), + selectedEntities + ); + } + else if (s_wasUsingGizmo) { + // Re-enable camera when done + camera.active = true; + + // Create undo/redo actions + createTransformUndoActions(selectedEntities); + } + + // Update state for next frame + s_wasUsingGizmo = isUsingGizmo; + } +} diff --git a/editor/src/DocumentWindows/EditorScene/Init.cpp b/editor/src/DocumentWindows/EditorScene/Init.cpp new file mode 100644 index 000000000..b8c5ccbd3 --- /dev/null +++ b/editor/src/DocumentWindows/EditorScene/Init.cpp @@ -0,0 +1,90 @@ +//// Init.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: 28/04/2025 +// Description: Source file for the init functions of the editor scene +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EditorScene.hpp" +#include "CameraFactory.hpp" +#include "LightFactory.hpp" +#include "EntityFactory3D.hpp" +#include "utils/EditorProps.hpp" + +namespace nexo::editor { + + void EditorScene::setup() + { + setupWindow(); + setupScene(); + setupShortcuts(); + } + + void EditorScene::setupScene() + { + auto &app = getApp(); + + m_sceneId = static_cast(app.getSceneManager().createScene(m_windowName)); + renderer::FramebufferSpecs framebufferSpecs; + framebufferSpecs.attachments = { + renderer::FrameBufferTextureFormats::RGBA8, renderer::FrameBufferTextureFormats::RED_INTEGER, renderer::FrameBufferTextureFormats::Depth + }; + framebufferSpecs.width = static_cast(m_viewSize.x); + framebufferSpecs.height = static_cast(m_viewSize.y); + const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs); + m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget); + auto &cameraComponent = app.m_coordinator->getComponent(m_editorCamera); + cameraComponent.render = true; + app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_editorCamera)); + components::PerspectiveCameraController controller; + Application::m_coordinator->addComponent(static_cast(m_editorCamera), controller); + components::EditorCameraTag editorCameraTag; + Application::m_coordinator->addComponent(m_editorCamera, editorCameraTag); + m_activeCamera = m_editorCamera; + + m_sceneUuid = app.getSceneManager().getScene(m_sceneId).getUuid(); + if (m_defaultScene) + loadDefaultEntities(); + } + + void EditorScene::loadDefaultEntities() const + { + auto &app = getApp(); + scene::Scene &scene = app.getSceneManager().getScene(m_sceneId); + const ecs::Entity ambientLight = LightFactory::createAmbientLight({0.5f, 0.5f, 0.5f}); + scene.addEntity(ambientLight); + const ecs::Entity pointLight = LightFactory::createPointLight({2.0f, 5.0f, 0.0f}); + utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); + scene.addEntity(pointLight); + const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.2f, -1.0f, -0.3f}); + scene.addEntity(directionalLight); + const ecs::Entity spotLight = LightFactory::createSpotLight({-2.0f, 5.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}); + utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); + scene.addEntity(spotLight); + const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, 0.25f, 0.0f}, {20.0f, 0.5f, 20.0f}, + {0.0f, 0.0f, 0.0f}, {0.05f * 1.7, 0.09f * 1.35, 0.13f * 1.45, 1.0f}); + app.getSceneManager().getScene(m_sceneId).addEntity(basicCube); + } + + void EditorScene::setupWindow() + { + constexpr auto size = ImVec2(1280, 720); + m_viewSize = size; + } + + void EditorScene::setCamera(ecs::Entity cameraId) + { + auto &app = getApp(); + auto &oldCameraComponent = app.m_coordinator->getComponent(m_activeCamera); + oldCameraComponent.active = false; + oldCameraComponent.render = false; + m_activeCamera = cameraId; + } +} diff --git a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp new file mode 100644 index 000000000..cb0f5cfa0 --- /dev/null +++ b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp @@ -0,0 +1,751 @@ +//// Shortcuts.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: 28/04/2025 +// Description: Source file for the shortcuts init of the editor scene +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EditorScene.hpp" +#include "context/Selector.hpp" +#include "context/ActionManager.hpp" +#include "components/Uuid.hpp" + +namespace nexo::editor { + + void EditorScene::setupGlobalState() + { + // ================= GLOBAL STATE ============================= + m_globalState = {static_cast(EditorState::GLOBAL)}; + m_globalState.registerCommand( + { + "Shift context", + "Shift", + nullptr, + nullptr, + nullptr, + true, + { + { + "Add entity", + "A", + [this]{ + this->m_popupManager.openPopup("Add new entity popup"); + }, + nullptr, + nullptr, + false, + } + } + } + ); + m_globalState.registerCommand( + { + "Control context", + "Ctrl", + nullptr, + nullptr, + nullptr, + true, + { + { + "Unhide all", + "H", + [this]{ + auto &app = getApp(); + const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); + for (const auto entity : entities) { + if (app.m_coordinator->entityHasComponent(entity)) { + auto &renderComponent = app.m_coordinator->getComponent(entity); + renderComponent.isRendered = true; + } + } + }, + nullptr, + nullptr, + false, + } + } + } + ); + m_globalState.registerCommand( + { + "Select all", + "A", + [this]{ + auto &selector = Selector::get(); + auto &app = nexo::getApp(); + auto &scene = app.getSceneManager().getScene(m_sceneId); + + selector.clearSelection(); + + for (const auto entity : scene.getEntities()) { + if (entity == m_editorCamera) continue; // Skip editor camera + + const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); + if (uuidComponent) + selector.addToSelection(uuidComponent->get().uuid, entity); + } + m_windowState = m_gizmoState; + }, + nullptr, + nullptr, + false + } + ); + } + + void EditorScene::setupGizmoState() + { + // ================= GIZMO STATE ============================= + m_gizmoState = {static_cast(EditorState::GIZMO)}; + m_gizmoState.registerCommand( + { + "Delete", + "Delete", + [this]{ + auto &selector = Selector::get(); + const auto &selectedEntities = selector.getSelectedEntities(); + auto &app = nexo::getApp(); + auto& actionManager = ActionManager::get(); + if (selectedEntities.size() > 1) { + auto actionGroup = actionManager.createActionGroup(); + for (const auto entity : selectedEntities) { + actionGroup->addAction(actionManager.prepareEntityDeletion(entity)); + app.deleteEntity(entity); + } + actionManager.recordAction(std::move(actionGroup)); + } else { + auto deleteAction = actionManager.prepareEntityDeletion(selectedEntities[0]); + app.deleteEntity(selectedEntities[0]); + actionManager.recordAction(std::move(deleteAction)); + } + selector.clearSelection(); + this->m_windowState = m_globalState; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoState.registerCommand( + { + "Hide", + "H", + [this]{ + auto &selector = Selector::get(); + const auto &selectedEntities = selector.getSelectedEntities(); + auto &app = nexo::getApp(); + auto& actionManager = ActionManager::get(); + if (selectedEntities.size() > 1) { + auto actionGroup = actionManager.createActionGroup(); + for (const auto entity : selectedEntities) { + auto &renderComponent = app.m_coordinator->getComponent(entity); + auto beforeState = renderComponent.save(); + renderComponent.isRendered = !renderComponent.isRendered; + auto afterState = renderComponent.save(); + actionGroup->addAction(std::make_unique>( + entity, beforeState, afterState)); + } + actionManager.recordAction(std::move(actionGroup)); + } else { + auto &renderComponent = app.m_coordinator->getComponent(selectedEntities[0]); + auto beforeState = renderComponent.save(); + renderComponent.isRendered = !renderComponent.isRendered; + auto afterState = renderComponent.save(); + actionManager.recordAction(std::move(std::make_unique>( + selectedEntities[0], beforeState, afterState))); + } + selector.clearSelection(); + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoState.registerCommand( + { + "Translate", + "G", + [this]{ + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoState.registerCommand( + { + "Rotate", + "R", + [this]{ + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoState.registerCommand( + { + "Scale", + "S", + [this]{ + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoState.registerCommand( + { + "Shift context", + "Shift", + nullptr, + nullptr, + nullptr, + true, + { + { + "Toggle snapping", + "S", + [this]{ + m_snapTranslateOn = true; + m_snapRotateOn = true; + }, + [this]{ + m_snapTranslateOn = false; + m_snapRotateOn = false; + }, + nullptr, + false, + }, + { + "Hide all but selection", + "H", + [this]{ + auto &app = getApp(); + const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); + auto &selector = Selector::get(); + for (const auto entity : entities) { + if (app.m_coordinator->entityHasComponent(entity) && !selector.isEntitySelected(entity)) { + auto &renderComponent = app.m_coordinator->getComponent(entity); + renderComponent.isRendered = false; + } + } + }, + nullptr, + nullptr, + false, + } + } + } + ); + } + + void EditorScene::setupGizmoTranslateState() + { + + // ================= TRANSLATE STATE ============================= + m_gizmoTranslateState = {static_cast(EditorState::GIZMO_TRANSLATE)}; + m_gizmoTranslateState.registerCommand( + { + "Universal", + "U", + [this]{ + this->m_windowState = m_gizmoState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Translate", + "G", + [this]{ + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + nullptr, + [this]{ + if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) + this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; + else + this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; + }, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Rotate", + "R", + [this]{ + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Scale", + "S", + [this]{ + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Shift context", + "Shift", + nullptr, + nullptr, + nullptr, + true, + { + { + "Exclude X", + "X", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_X); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_X); + }, + nullptr, + false, + }, + { + "Exclude Y", + "Y", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Y); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Y); + }, + nullptr, + false, + }, + { + "Exclude Z", + "Z", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Z); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Z); + }, + nullptr, + false, + }, + { + "Toggle snapping", + "S", + [this]{ + m_snapTranslateOn = true; + }, + [this]{ + m_snapTranslateOn = false; + }, + nullptr, + false, + } + } + } + ); + m_gizmoTranslateState.registerCommand( + { + "Lock X", + "X", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_X; + }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + nullptr, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Lock Y", + "Y", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Y; + }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + nullptr, + false, + } + ); + m_gizmoTranslateState.registerCommand( + { + "Lock Z", + "Z", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Z; + }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + nullptr, + false, + } + ); + } + + void EditorScene::setupGizmoRotateState() + { + // ================= ROTATE STATE ============================= + m_gizmoRotateState = {static_cast(EditorState::GIZMO_ROTATE)}; + m_gizmoRotateState.registerCommand( + { + "Universal", + "U", + [this]{ + this->m_windowState = m_gizmoState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Rotate", + "R", + [this]{ + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, // << Key pressed + nullptr, // << Key released + [this]{ + if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) + this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; + else + this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; + }, // << Key repeat + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Translate", + "G", + [this]{ + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Scale", + "S", + [this]{ + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Shift context", + "Shift", + nullptr, + nullptr, + nullptr, + true, + { + { + "Exclude X", + "X", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_X); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_X); + }, + nullptr, + false, + }, + { + "Exclude Y", + "Y", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Y); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Y); + }, + nullptr, + false, + }, + { + "Exclude Z", + "Z", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Z); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Z); + }, + nullptr, + false, + }, + { + "Toggle snapping", + "S", + [this]{ + m_snapRotateOn = true; + }, + [this]{ + m_snapRotateOn = false; + }, + nullptr, + false, + }, + } + } + ); + m_gizmoRotateState.registerCommand( + { + "Lock X", + "X", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_X; + }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + nullptr, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Lock Y", + "Y", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Y; + }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + nullptr, + false, + } + ); + m_gizmoRotateState.registerCommand( + { + "Lock Z", + "Z", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Z; + }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + nullptr, + false, + } + ); + } + + void EditorScene::setupGizmoScaleState() + { + // ================= SCALE STATE ============================= + m_gizmoScaleState = {static_cast(EditorState::GIZMO_SCALE)}; + m_gizmoScaleState.registerCommand( + { + "Universal", + "U", + [this]{ + this->m_windowState = m_gizmoState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Scale", + "S", + [this]{ + this->m_windowState = m_gizmoScaleState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + nullptr, + [this]{ + if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) + this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; + else + this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; + }, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Translate", + "G", + [this]{ + this->m_windowState = m_gizmoTranslateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Rotate", + "R", + [this]{ + this->m_windowState = m_gizmoRotateState; + this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; + }, + nullptr, + nullptr, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Shift context", + "Shift", + nullptr, + nullptr, + nullptr, + true, + { + { + "Exclude X", + "X", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_X); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_X); + }, + nullptr, + false, + }, + { + "Exclude Y", + "Y", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Y); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Y); + }, + nullptr, + false, + }, + { + "Exclude Z", + "Z", + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Z); + }, + [this]{ + m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Z); + }, + nullptr, + false, + } + } + } + ); + m_gizmoScaleState.registerCommand( + { + "Lock X", + "X", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_X; + }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + nullptr, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Lock Y", + "Y", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Y; + }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + nullptr, + false, + } + ); + m_gizmoScaleState.registerCommand( + { + "Lock Z", + "Z", + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Z; + }, + [this]{ + this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; + }, + nullptr, + false, + } + ); + } + + void EditorScene::setupShortcuts() + { + setupGlobalState(); + m_windowState = m_globalState; + + setupGizmoState(); + setupGizmoTranslateState(); + setupGizmoRotateState(); + setupGizmoScaleState(); + } +} diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp new file mode 100644 index 000000000..d946e0d45 --- /dev/null +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -0,0 +1,154 @@ +//// Show.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: 28/04/2025 +// Description: Source file for the editor scene rendering +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EditorScene.hpp" +#include "context/Selector.hpp" +#include "EntityFactory3D.hpp" +#include "LightFactory.hpp" +#include "IconsFontAwesome.h" +#include "ImNexo/Panels.hpp" +#include "utils/EditorProps.hpp" + +namespace nexo::editor { + + void EditorScene::renderNoActiveCamera() + { + // No active camera, render the text at the center of the screen + ImVec2 textSize = ImGui::CalcTextSize("No active camera"); + auto textPos = ImVec2((m_viewSize.x - textSize.x) / 2, (m_viewSize.y - textSize.y) / 2); + + ImGui::SetCursorScreenPos(textPos); + ImGui::Text("No active camera"); + } + + void EditorScene::renderNewEntityPopup() + { + auto &app = Application::getInstance(); + auto &sceneManager = app.getSceneManager(); + const auto sceneId = m_sceneId; + + // --- Primitives submenu --- + if (ImGui::BeginMenu("Primitives")) { + if (ImGui::MenuItem("Cube")) { + const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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); + } + ImGui::EndMenu(); + } + + // --- Model item (with file‑dialog) --- + if (ImGui::MenuItem("Model")) { + //TODO: import model + } + + // --- Lights submenu --- + 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); + } + if (ImGui::MenuItem("Point")) { + const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); + utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); + sceneManager.getScene(sceneId).addEntity(pointLight); + } + if (ImGui::MenuItem("Spot")) { + const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); + utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); + sceneManager.getScene(sceneId).addEntity(spotLight); + } + ImGui::EndMenu(); + } + + // --- Camera item --- + if (ImGui::MenuItem("Camera")) { + m_popupManager.openPopupWithCallback("Popup camera inspector", [this]() { + ImNexo::CameraInspector(this->m_sceneId, this->m_viewSize); + }, ImVec2(1440,900)); + } + m_popupManager.closePopup(); + } + + void EditorScene::renderView() + { + const auto viewPortOffset = ImGui::GetCursorPos(); + auto &cameraComponent = Application::m_coordinator->getComponent(m_activeCamera); + const ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail(); + + // Resize handling + if ((viewportPanelSize.x > 0 && viewportPanelSize.y > 0) && (m_viewSize.x != viewportPanelSize.x || m_viewSize.y != viewportPanelSize.y)) + { + cameraComponent.resize(static_cast(viewportPanelSize.x), + static_cast(viewportPanelSize.y)); + + m_viewSize.x = viewportPanelSize.x; + m_viewSize.y = viewportPanelSize.y; + } + + // Render framebuffer + const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); + ImGui::Image(static_cast(static_cast(textureId)), m_viewSize, ImVec2(0, 1), ImVec2(1, 0)); + + const auto windowSize = ImGui::GetWindowSize(); + auto minBounds = ImGui::GetWindowPos(); + + minBounds.x += viewPortOffset.x; + minBounds.y += viewPortOffset.y; + + const ImVec2 maxBounds = {minBounds.x + windowSize.x, minBounds.y + windowSize.y}; + m_viewportBounds[0] = minBounds; + m_viewportBounds[1] = maxBounds; + } + + void EditorScene::show() + { + 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 = m_windowName + std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId); + + if (ImGui::Begin(sceneWindowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse)) + { + firstDockSetup(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId)); + auto &app = getApp(); + + // Add some spacing after the toolbar + ImGui::Dummy(ImVec2(0, 5)); + m_viewPosition = ImGui::GetCursorScreenPos(); + + m_focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); + m_hovered = ImGui::IsWindowHovered(ImGuiFocusedFlags_RootAndChildWindows); + app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused); + if (m_focused && selector.getSelectedScene() != m_sceneId) { + selector.setSelectedScene(m_sceneId); + selector.clearSelection(); + } + + if (m_activeCamera == -1) + renderNoActiveCamera(); + else { + renderView(); + renderGizmo(); + renderToolbar(); + } + + if (m_popupManager.showPopup("Add new entity popup")) + renderNewEntityPopup(); + } + ImGui::End(); + ImGui::PopStyleVar(); + } +} diff --git a/editor/src/DocumentWindows/EditorScene/Shutdown.cpp b/editor/src/DocumentWindows/EditorScene/Shutdown.cpp new file mode 100644 index 000000000..14c412d8d --- /dev/null +++ b/editor/src/DocumentWindows/EditorScene/Shutdown.cpp @@ -0,0 +1,22 @@ +//// Shutdown.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: 28/04/2025 +// Description: Source file for the shutdown of the editor scene +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EditorScene.hpp" + +namespace nexo::editor { + void EditorScene::shutdown() + { + // Should probably check if it is necessary to delete the scene here ? + } +} diff --git a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp new file mode 100644 index 000000000..93caa7090 --- /dev/null +++ b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp @@ -0,0 +1,421 @@ +//// Toolbar.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: 28/04/2025 +// Description: Source file for the toobal rendering of the editor scene +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EditorScene.hpp" +#include "EntityFactory3D.hpp" +#include "IconsFontAwesome.h" +#include "context/Selector.hpp" +#include "components/Uuid.hpp" + +namespace nexo::editor { + + void EditorScene::initialToolbarSetup(const float buttonWidth, const float buttonHeight) + { + ImVec2 toolbarPos = m_viewPosition; + toolbarPos.x += 10.0f; + + ImGui::SetCursorScreenPos(toolbarPos); + + ImVec2 toolbarSize = ImVec2(m_viewSize.x - buttonWidth, 50.0f); + + 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); + + ImGui::SetCursorPosY((ImGui::GetWindowHeight() - ImGui::GetFrameHeight()) * 0.5f); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 0)); + } + + bool EditorScene::renderToolbarButton(const std::string &uniqueId, const std::string &icon, const std::string &tooltip, const std::vector & gradientStop, bool *rightClicked) + { + constexpr float buttonWidth = 35.0f; + constexpr float buttonHeight = 35.0f; + 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); + 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, -5.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); + }, + .tooltip = "Create Cube" + } + }; + 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 + } + // Snap on scale is kinda strange, the IsOver is not able to detect it, so for now we disable it + // { + // .uniqueId = "toggle_scale_snap", + // .icon = ICON_FA_EXPAND, + // .onClick = [this]() + // { + // this->m_snapScaleOn = !m_snapScaleOn; + // }, + // .onRightClick = [this]() + // { + // this->m_popupManager.openPopup("Snap settings popup", ImVec2(400, 180)); + // }, + // .tooltip = "Toggle Scale Snap", + // .buttonGradient = (m_snapScaleOn) ? m_selectedGradient : buttonGradient + // } + }; + ImNexo::ButtonDropDown(snapButtonPos, buttonSize, buttonProps, showSnapMenu); + } + + void EditorScene::snapSettingsPopup() + { + 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); + 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); + // Empty columns to match the first table's structure + ImGui::TableSetupColumn("##Empty1", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Empty2", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + ImNexo::RowDragFloat1("Rotate Snap", "", &this->m_angleSnap); + ImGui::EndTable(); + } + ImGui::Spacing(); + ImGui::Spacing(); + + float buttonWidth = 120.0f; + float windowWidth = ImGui::GetWindowSize().x; + ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); + + if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) + { + m_popupManager.closePopupInContext(); + } + ImGui::Unindent(10.0f); + ImGui::PopStyleVar(); + m_popupManager.closePopup(); + } + } + + void EditorScene::gridSettingsPopup() + { + if (m_popupManager.showPopupModal("Grid settings")) + { + auto &app = getApp(); + components::RenderContext::GridParams &gridSettings = + app.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); + ImNexo::RowDragFloat1("Grid size", "", &gridSettings.gridSize, 50.0f, 150.0f); + 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"); + ImNexo::RowDragFloat1("Cell size", "", &gridSettings.cellSize, 0.1f, 20.0f, 0.02f); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("The size of the internal cells"); + ImGui::EndTable(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + + float buttonWidth = 120.0f; + float windowWidth = ImGui::GetWindowSize().x; + ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); + + if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) + { + m_popupManager.closePopupInContext(); + } + ImGui::Unindent(10.0f); + ImGui::PopStyleVar(); + m_popupManager.closePopup(); + } + } + + void EditorScene::renderEditorCameraToolbarButton() + { + auto &app = getApp(); + auto &selector = Selector::get(); + bool editorMode = m_activeCamera == m_editorCamera; + if (m_activeCamera == m_editorCamera) { + if (renderToolbarButton("editor_camera", ICON_FA_CAMERA, "Edit Editor Camera Setting", m_buttonGradient)) { + const auto &uuidComponent = app.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 = app.m_coordinator->getComponent(m_activeCamera); + oldCameraComponent.active = false; + oldCameraComponent.render = false; + m_activeCamera = m_editorCamera; + auto &editorCameraComponent = app.m_coordinator->getComponent(m_activeCamera); + editorCameraComponent.render = true; + editorCameraComponent.active = true; + } + } + } + + bool EditorScene::renderGizmoModeToolbarButton(const bool showGizmoModeMenu, ImNexo::ButtonProps &activeGizmoMode, ImNexo::ButtonProps &inactiveGizmoMode) + { + static const ImNexo::ButtonProps gizmoLocalModeButtonProps = {"local_coords", ICON_FA_CROSSHAIRS, [this]() {this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL;}, nullptr, "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; + inactiveGizmoMode = gizmoWorldModeButtonProps; + } else { + activeGizmoMode = gizmoWorldModeButtonProps; + inactiveGizmoMode = gizmoLocalModeButtonProps; + } + return renderToolbarButton(activeGizmoMode.uniqueId, activeGizmoMode.icon, + activeGizmoMode.tooltip, showGizmoModeMenu ? m_selectedGradient : m_buttonGradient); + } + + void EditorScene::renderToolbar() + { + auto &app = getApp(); + constexpr float buttonWidth = 35.0f; + constexpr float buttonHeight = 35.0f; + constexpr ImVec2 buttonSize{buttonWidth, buttonHeight}; + ImVec2 originalCursorPos = ImGui::GetCursorPos(); + + initialToolbarSetup(buttonWidth, buttonHeight); + + // -------------------------------- BUTTONS ------------------------------- + // -------- Add primitve button -------- + // This can open a submenu, see at the end + 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; + + ImGui::SameLine(); + + // -------- Editor camera settings / Switch back to editor camera button -------- + renderEditorCameraToolbarButton(); + + ImGui::SameLine(); + + // -------- Gizmo operation button -------- + static const ImNexo::ButtonProps gizmoTranslateButtonProps = ImNexo::ButtonProps{"translate", ICON_FA_ARROWS, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE;}, nullptr, "Translate"}; + static const ImNexo::ButtonProps gizmoRotateButtonProps = ImNexo::ButtonProps{"rotate", ICON_FA_REFRESH, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE;}, nullptr, "Rotate"}; + static const ImNexo::ButtonProps gizmoScaleButtonProps = ImNexo::ButtonProps{"scale", ICON_FA_EXPAND, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE;}, nullptr, "Scale"}; + static const ImNexo::ButtonProps gizmoUniversalButtonProps = ImNexo::ButtonProps{"universal", ICON_FA_ARROWS_ALT, [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; + } + + 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; + + ImGui::SameLine(); + + // -------- Gizmo operation button -------- + ImNexo::ButtonProps activeGizmoMode; + ImNexo::ButtonProps inactiveGizmoMode; + ImVec2 changeGizmoModePos = ImGui::GetCursorScreenPos(); + static bool showGizmoModeMenu = false; + 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(); + 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; + + ImGui::SameLine(); + + // -------- Grid enabled button -------- + bool rightClicked = false; + components::RenderContext::GridParams &gridParams = app.m_coordinator->getSingletonComponent().gridParams; + if (renderToolbarButton("grid_enabled", ICON_FA_TH_LARGE, "Enable / Disable grid", gridParams.enabled ? m_selectedGradient : m_buttonGradient, &rightClicked)) + { + gridParams.enabled = !gridParams.enabled; + + } + 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 + 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_snapToGrid; + } + + ImGui::SameLine(); + + // -------- Enable wireframe button -------- + if (renderToolbarButton("wireframe", ICON_FA_CUBE, "Enable / Disable wireframe", m_wireframeEnabled ? m_selectedGradient : m_buttonGradient)) + { + m_wireframeEnabled = !m_wireframeEnabled; + } + + ImGui::SameLine(); + + // -------- Play button button -------- + renderToolbarButton("play", ICON_FA_PLAY, "Play scene", m_buttonGradient); + + ImGui::PopStyleVar(); + ImGui::EndChild(); + ImGui::PopStyleColor(); + + // -------------------------------- SUB-MENUS ------------------------------- + // -------- Primitives sub-menus -------- + if (showPrimitiveMenu) + { + renderPrimitiveSubMenu(addPrimButtonPos, buttonSize, showPrimitiveMenu); + } + + // -------- Gizmo operation sub-menu -------- + 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); + } + + // -------- Snap settings popup -------- + snapSettingsPopup(); + + // -------- Grid settings popup -------- + gridSettingsPopup(); + + // IMPORTANT: Restore original cursor position so we don't affect layout + ImGui::SetCursorPos(originalCursorPos); + } +} diff --git a/editor/src/DocumentWindows/EditorScene/Update.cpp b/editor/src/DocumentWindows/EditorScene/Update.cpp new file mode 100644 index 000000000..b58e13268 --- /dev/null +++ b/editor/src/DocumentWindows/EditorScene/Update.cpp @@ -0,0 +1,131 @@ +//// Update.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: 28/04/2025 +// Description: Source file for the update of the editor scene +// +/////////////////////////////////////////////////////////////////////////////// + +#include "EditorScene.hpp" +#include "context/Selector.hpp" +#include "components/Uuid.hpp" + +namespace nexo::editor { + int EditorScene::sampleEntityTexture(float mx, float my) + { + const auto &coord = getApp().m_coordinator; + const auto &cameraComponent = coord->getComponent(static_cast(m_activeCamera)); + + cameraComponent.m_renderTarget->bind(); + int entityId = cameraComponent.m_renderTarget->getPixel(1, static_cast(mx), static_cast(my)); + cameraComponent.m_renderTarget->unbind(); + return entityId; + } + + static SelectionType getSelectionType(int entityId) + { + const auto &coord = getApp().m_coordinator; + SelectionType selType = SelectionType::ENTITY; + if (coord->entityHasComponent(entityId)) { + selType = SelectionType::CAMERA; + } else if (coord->entityHasComponent(entityId)) { + selType = SelectionType::DIR_LIGHT; + } else if (coord->entityHasComponent(entityId)) { + selType = SelectionType::POINT_LIGHT; + } else if (coord->entityHasComponent(entityId)) { + selType = SelectionType::SPOT_LIGHT; + } else if (coord->entityHasComponent(entityId)) { + selType = SelectionType::AMBIENT_LIGHT; + } + return selType; + } + + void EditorScene::updateWindowState() + { + auto &selector = Selector::get(); + if (selector.hasSelection()) { + if (m_currentGizmoOperation == ImGuizmo::OPERATION::TRANSLATE) + m_windowState = m_gizmoTranslateState; + else if (m_currentGizmoOperation == ImGuizmo::OPERATION::ROTATE) + m_windowState = m_gizmoRotateState; + else if (m_currentGizmoOperation == ImGuizmo::OPERATION::SCALE) + m_windowState = m_gizmoScaleState; + else + m_windowState = m_gizmoState; + } + } + + void EditorScene::updateSelection(int entityId, bool isShiftPressed, bool isCtrlPressed) + { + const auto &coord = getApp().m_coordinator; + const auto uuid = coord->tryGetComponent(entityId); + if (!uuid) + return; + + // Determine selection type + SelectionType selType = getSelectionType(entityId); + auto &selector = Selector::get(); + + // Handle different selection modes + if (isCtrlPressed) + selector.toggleSelection(uuid->get().uuid, entityId, selType); + else if (isShiftPressed) + selector.addToSelection(uuid->get().uuid, entityId, selType); + else + selector.selectEntity(uuid->get().uuid, entityId, selType); + + updateWindowState(); + selector.setSelectedScene(m_sceneId); + } + + void EditorScene::handleSelection() + { + auto [mx, my] = ImGui::GetMousePos(); + mx -= m_viewportBounds[0].x; + my -= m_viewportBounds[0].y; + + // Flip the y-coordinate to match opengl texture format + my = m_viewSize.y - my; + + // Check if mouse is inside viewport + if (!(mx >= 0 && my >= 0 && mx < m_viewSize.x && my < m_viewSize.y)) + return; + int entityId = sampleEntityTexture(mx, my); + + // Check for multi-selection key modifiers + bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); + 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 + if (!isShiftPressed && !isCtrlPressed) { + selector.clearSelection(); + m_windowState = m_globalState; + } + return; + } + + updateSelection(entityId, isShiftPressed, isCtrlPressed); + } + + void EditorScene::update() + { + auto &selector = Selector::get(); + if (!m_opened || m_activeCamera == -1) + return; + SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; + runEngine(m_sceneId, RenderingType::FRAMEBUFFER, sceneType); + + + // Handle mouse clicks for selection + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsUsing() && m_focused) + handleSelection(); + } +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index 43a8ebfdf..3c33c2225 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -24,7 +24,7 @@ #include #include -#include "EditorScene.hpp" +#include "DocumentWindows/EditorScene/EditorScene.hpp" #include "components/Camera.hpp" #include "components/Light.hpp" #include "components/Render.hpp" diff --git a/editor/src/DocumentWindows/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow.hpp index 523df00fd..fb423fe88 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.hpp @@ -14,7 +14,7 @@ #pragma once #include "ADocumentWindow.hpp" -#include "EditorScene.hpp" +#include "DocumentWindows/EditorScene/EditorScene.hpp" #include "IconsFontAwesome.h" #include "Nexo.hpp" #include "core/scene/SceneManager.hpp" From d0c5ddf5c0e30cd5ec63e8d260b1050a1424f0d1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 20:36:56 +0900 Subject: [PATCH 216/450] refactor(document-windows): split asset manager window into multiple source files --- editor/CMakeLists.txt | 5 +- editor/main.cpp | 2 +- .../{ => AssetManager}/AssetManagerWindow.hpp | 1 - .../src/DocumentWindows/AssetManager/Init.cpp | 41 ++++ .../Show.cpp} | 192 ++++++++---------- .../DocumentWindows/AssetManager/Shutdown.cpp | 24 +++ .../DocumentWindows/AssetManager/Update.cpp | 24 +++ 7 files changed, 175 insertions(+), 114 deletions(-) rename editor/src/DocumentWindows/{ => AssetManager}/AssetManagerWindow.hpp (97%) create mode 100644 editor/src/DocumentWindows/AssetManager/Init.cpp rename editor/src/DocumentWindows/{AssetManagerWindow.cpp => AssetManager/Show.cpp} (78%) create mode 100644 editor/src/DocumentWindows/AssetManager/Shutdown.cpp create mode 100644 editor/src/DocumentWindows/AssetManager/Update.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 496a72fdc..7349daca8 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -44,6 +44,10 @@ set(SRCS editor/src/DocumentWindows/EditorScene/Shutdown.cpp editor/src/DocumentWindows/EditorScene/Toolbar.cpp editor/src/DocumentWindows/EditorScene/Update.cpp + editor/src/DocumentWindows/AssetManager/Init.cpp + editor/src/DocumentWindows/AssetManager/Show.cpp + editor/src/DocumentWindows/AssetManager/Shutdown.cpp + editor/src/DocumentWindows/AssetManager/Update.cpp editor/src/DocumentWindows/SceneTreeWindow.cpp editor/src/DocumentWindows/PopupManager.cpp editor/src/DocumentWindows/InspectorWindow.cpp @@ -57,7 +61,6 @@ set(SRCS editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp editor/src/DocumentWindows/EntityProperties/CameraController.cpp editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp - editor/src/DocumentWindows/AssetManagerWindow.cpp ) # Windows App Icon diff --git a/editor/main.cpp b/editor/main.cpp index c18364a85..4cc11b40e 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -17,7 +17,7 @@ #include "src/DocumentWindows/EditorScene/EditorScene.hpp" #include "src/DocumentWindows/SceneTreeWindow.hpp" #include "src/DocumentWindows/InspectorWindow.hpp" -#include "src/DocumentWindows/AssetManagerWindow.hpp" +#include "src/DocumentWindows/AssetManager/AssetManagerWindow.hpp" #include "src/DocumentWindows/MaterialInspector.hpp" #include diff --git a/editor/src/DocumentWindows/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp similarity index 97% rename from editor/src/DocumentWindows/AssetManagerWindow.hpp rename to editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index d665eb5cb..e09f3274b 100644 --- a/editor/src/DocumentWindows/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -73,7 +73,6 @@ namespace nexo::editor { void drawAssetsGrid(); void drawAsset(const assets::GenericAssetRef& asset, int index, const ImVec2& itemPos, const ImVec2& itemSize); void handleSelection(int index, bool isSelected); - ImU32 getAssetTypeOverlayColor(assets::AssetType type) const; }; } // namespace nexo::editor diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp new file mode 100644 index 000000000..b4421035b --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -0,0 +1,41 @@ +//// Init.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: 28/04/2025 +// Description: Source file for the setup of the asset manager window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "AssetManagerWindow.hpp" +#include "assets/AssetCatalog.hpp" +#include "assets/Assets/Model/ModelImporter.hpp" +#include "assets/Assets/Texture/TextureImporter.hpp" +#include "Path.hpp" + +namespace nexo::editor { + void AssetManagerWindow::setup() + { + auto& catalog = assets::AssetCatalog::getInstance(); + const auto asset = new assets::Model(); + catalog.registerAsset(assets::AssetLocation("my_package::My_Model@foo/bar/"), asset); + + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf"); + assets::ImporterFileInput fileInput{path}; + auto assetRef9mn = importer.importAssetAuto(assets::AssetLocation("my_package::9mn@foo/bar/"), fileInput); + } + { + assets::AssetImporter importer; + std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/logo_nexo.png"); + assets::ImporterFileInput fileInput{path}; + auto textureRef = importer.importAsset(assets::AssetLocation("nexo_logo@foo/bar/"), fileInput); + } + } +} diff --git a/editor/src/DocumentWindows/AssetManagerWindow.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp similarity index 78% rename from editor/src/DocumentWindows/AssetManagerWindow.cpp rename to editor/src/DocumentWindows/AssetManager/Show.cpp index 571d64e1a..9c9d9a462 100644 --- a/editor/src/DocumentWindows/AssetManagerWindow.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -1,4 +1,4 @@ -//// AssetManagerWindow.cpp /////////////////////////////////////////////////// +//// Show.cpp /////////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -6,70 +6,31 @@ // zzz zzz zzz z zzzz zzzz zzzz zzzz // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // -// Author: Guillaume HEIN -// Date: 18/11/2024 -// Description: Source file for the AssetManagerWindow class +// Author: Mehdy MORVAN +// Date: 28/04/2025 +// Description: Source file for the rendering of the asset manager window // /////////////////////////////////////////////////////////////////////////////// #include "AssetManagerWindow.hpp" -#include -#include -#include -#include -#include -#include -#include -#include +#include "assets/AssetCatalog.hpp" +#include "IconsFontAwesome.h" namespace nexo::editor { - - void AssetManagerWindow::setup() { - auto& catalog = assets::AssetCatalog::getInstance(); - - const auto asset = new assets::Model(); - - catalog.registerAsset(assets::AssetLocation("my_package::My_Model@foo/bar/"), asset); - - - - { - assets::AssetImporter importer; - std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf"); - assets::ImporterFileInput fileInput{path}; - auto assetRef9mn = importer.importAssetAuto(assets::AssetLocation("my_package::9mn@foo/bar/"), fileInput); - } - { - assets::AssetImporter importer; - std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/logo_nexo.png"); - assets::ImporterFileInput fileInput{path}; - auto textureRef = importer.importAsset(assets::AssetLocation("nexo_logo@foo/bar/"), fileInput); + void AssetManagerWindow::drawMenuBar() + { + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("Options")) { + ImGui::SliderFloat("Icon Size", &m_layout.size.iconSize, 32.0f, 128.0f, "%.0f"); + ImGui::SliderInt("Icon Spacing", &m_layout.size.iconSpacing, 0, 32); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); } } - void AssetManagerWindow::shutdown() { - } - - void AssetManagerWindow::show() { - ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); - ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, ImGuiWindowFlags_MenuBar); - firstDockSetup(NEXO_WND_USTRID_ASSET_MANAGER); - - drawMenuBar(); - - float availWidth = ImGui::GetContentRegionAvail().x; - calculateLayout(availWidth); - - drawAssetsGrid(); - - ImGui::End(); - } - - void AssetManagerWindow::update() { - // Update logic if necessary - } - - void AssetManagerWindow::calculateLayout(float availWidth) { + void AssetManagerWindow::calculateLayout(float availWidth) + { // Sizes m_layout.size.columnCount = std::max(static_cast(availWidth / (m_layout.size.iconSize + m_layout.size.iconSpacing)), 1); m_layout.size.itemSize = ImVec2(m_layout.size.iconSize + ImGui::GetFontSize() * 1.5f, m_layout.size.iconSize + ImGui::GetFontSize() * 1.7f); @@ -91,39 +52,46 @@ namespace nexo::editor { m_layout.color.titleText = ImGui::GetColorU32(ImGuiCol_Text); } - void AssetManagerWindow::drawMenuBar() { - if (ImGui::BeginMenuBar()) { - if (ImGui::BeginMenu("Options")) { - ImGui::SliderFloat("Icon Size", &m_layout.size.iconSize, 32.0f, 128.0f, "%.0f"); - ImGui::SliderInt("Icon Spacing", &m_layout.size.iconSpacing, 0, 32); - ImGui::EndMenu(); + void AssetManagerWindow::handleSelection(int index, bool isSelected) + { + LOG(NEXO_INFO, "Asset {} {}", index, isSelected ? "deselected" : "selected"); + if (ImGui::GetIO().KeyCtrl) { + if (isSelected) + m_selectedAssets.erase(index); + else + m_selectedAssets.insert(index); + } else if (ImGui::GetIO().KeyShift) { + const int latestSelected = m_selectedAssets.empty() ? 0 : *m_selectedAssets.rbegin(); + if (latestSelected <= index) { + for (int i = latestSelected ; i <= index; ++i) { + m_selectedAssets.insert(i); + } + } else { + for (int i = index; i <= latestSelected; ++i) { + m_selectedAssets.insert(i); + } } - ImGui::EndMenuBar(); + } else { + m_selectedAssets.clear(); + m_selectedAssets.insert(index); } } - void AssetManagerWindow::drawAssetsGrid() { - ImVec2 startPos = ImGui::GetCursorScreenPos(); - - ImGuiListClipper clipper; - const auto assets = assets::AssetCatalog::getInstance().getAssets(); - clipper.Begin(assets.size(), m_layout.size.itemStep.y); - while (clipper.Step()) { - for (int lineIdx = clipper.DisplayStart; lineIdx < clipper.DisplayEnd; ++lineIdx) { - int startIdx = lineIdx * m_layout.size.columnCount; - int endIdx = std::min(startIdx + m_layout.size.columnCount, static_cast(assets.size())); - - for (int i = startIdx; i < endIdx; ++i) { - ImVec2 itemPos = ImVec2(startPos.x + (i % m_layout.size.columnCount) * m_layout.size.itemStep.x, - startPos.y + (i / m_layout.size.columnCount) * m_layout.size.itemStep.y); - drawAsset(assets[i], i, itemPos, m_layout.size.itemSize); - } - } + static ImU32 getAssetTypeOverlayColor(assets::AssetType type) + { + switch (type) { + case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255); + case assets::AssetType::MODEL: return IM_COL32(70, 170, 70, 255); + default: return IM_COL32(0, 0, 0, 0); } - clipper.End(); } - void AssetManagerWindow::drawAsset(const assets::GenericAssetRef& asset, int index, const ImVec2& itemPos, const ImVec2& itemSize) { + void AssetManagerWindow::drawAsset( + const assets::GenericAssetRef& asset, + int index, + const ImVec2& itemPos, + const ImVec2& itemSize + ) { auto assetData = asset.lock(); if (!assetData) return; @@ -182,41 +150,43 @@ namespace nexo::editor { } ImGui::PopID(); - - } - void AssetManagerWindow::handleSelection(int index, bool isSelected) + void AssetManagerWindow::drawAssetsGrid() { - LOG(NEXO_INFO, "Asset {} {}", index, isSelected ? "deselected" : "selected"); - if (ImGui::GetIO().KeyCtrl) { - if (isSelected) - m_selectedAssets.erase(index); - else - m_selectedAssets.insert(index); - } else if (ImGui::GetIO().KeyShift) { - const int latestSelected = m_selectedAssets.empty() ? 0 : *m_selectedAssets.rbegin(); - if (latestSelected <= index) { - for (int i = latestSelected ; i <= index; ++i) { - m_selectedAssets.insert(i); - } - } else { - for (int i = index; i <= latestSelected; ++i) { - m_selectedAssets.insert(i); + ImVec2 startPos = ImGui::GetCursorScreenPos(); + + ImGuiListClipper clipper; + const auto assets = assets::AssetCatalog::getInstance().getAssets(); + clipper.Begin(assets.size(), m_layout.size.itemStep.y); + while (clipper.Step()) { + for (int lineIdx = clipper.DisplayStart; lineIdx < clipper.DisplayEnd; ++lineIdx) { + int startIdx = lineIdx * m_layout.size.columnCount; + int endIdx = std::min(startIdx + m_layout.size.columnCount, static_cast(assets.size())); + + for (int i = startIdx; i < endIdx; ++i) { + ImVec2 itemPos = ImVec2(startPos.x + (i % m_layout.size.columnCount) * m_layout.size.itemStep.x, + startPos.y + (i / m_layout.size.columnCount) * m_layout.size.itemStep.y); + drawAsset(assets[i], i, itemPos, m_layout.size.itemSize); } } - } else { - m_selectedAssets.clear(); - m_selectedAssets.insert(index); } + clipper.End(); } - ImU32 AssetManagerWindow::getAssetTypeOverlayColor(assets::AssetType type) const { - switch (type) { - case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255); - case assets::AssetType::MODEL: return IM_COL32(70, 170, 70, 255); - default: return IM_COL32(0, 0, 0, 0); - } - } + void AssetManagerWindow::show() + { + ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); + ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, ImGuiWindowFlags_MenuBar); + firstDockSetup(NEXO_WND_USTRID_ASSET_MANAGER); -} // namespace nexo::editor + drawMenuBar(); + + float availWidth = ImGui::GetContentRegionAvail().x; + calculateLayout(availWidth); + + drawAssetsGrid(); + + ImGui::End(); + } +} diff --git a/editor/src/DocumentWindows/AssetManager/Shutdown.cpp b/editor/src/DocumentWindows/AssetManager/Shutdown.cpp new file mode 100644 index 000000000..0bd9b07e8 --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/Shutdown.cpp @@ -0,0 +1,24 @@ +//// Shutdown.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: 28/04/2025 +// Description: Source file for the shutdown of the asset manager window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "AssetManagerWindow.hpp" + +namespace nexo::editor { + + void AssetManagerWindow::shutdown() + { + //Nothing to do in the shutdown for now + } + +} diff --git a/editor/src/DocumentWindows/AssetManager/Update.cpp b/editor/src/DocumentWindows/AssetManager/Update.cpp new file mode 100644 index 000000000..475078433 --- /dev/null +++ b/editor/src/DocumentWindows/AssetManager/Update.cpp @@ -0,0 +1,24 @@ +//// Update.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: 28/04/2025 +// Description: Source file for the update function of the asset manager window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "AssetManagerWindow.hpp" + +namespace nexo::editor { + + void AssetManagerWindow::update() + { + // Nothing to do for now + } + +} From 2129356031a89a703731901a18717a0aea69b9c4 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 20:37:12 +0900 Subject: [PATCH 217/450] style(document-windows): change comment for more clarity --- engine/src/ecs/Coordinator.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index c93d76858..0df1c40c6 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -47,13 +47,9 @@ namespace nexo::ecs { { std::vector types; - // Get the entity's signature which already tells us which components it has Signature signature = m_entityManager->getSignature(entity); - // We need a mapping from component type IDs to type_index - // This could be stored as a member variable and populated during registerComponent - - // If we have that mapping: + // We have a mapping from component type IDs to type_index for (ComponentType type = 0; type < MAX_COMPONENT_TYPE; ++type) { if (signature.test(type) && m_typeIDtoTypeIndex.contains(type)) { types.emplace_back(m_typeIDtoTypeIndex.at(type)); From 5ce2f8296e531607649872814a2adf7c91dfa2b2 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 21:03:22 +0900 Subject: [PATCH 218/450] refactor(document-windows): split console window into multiple source files --- editor/CMakeLists.txt | 7 +- editor/main.cpp | 2 +- editor/src/DocumentWindows/ConsoleWindow.cpp | 375 ------------------ .../{ => ConsoleWindow}/ConsoleWindow.hpp | 36 +- .../DocumentWindows/ConsoleWindow/Init.cpp | 50 +++ .../src/DocumentWindows/ConsoleWindow/Log.cpp | 59 +++ .../DocumentWindows/ConsoleWindow/Show.cpp | 147 +++++++ .../ConsoleWindow/Shutdown.cpp | 24 ++ .../DocumentWindows/ConsoleWindow/Update.cpp | 22 + .../DocumentWindows/ConsoleWindow/Utils.cpp | 118 ++++++ 10 files changed, 461 insertions(+), 379 deletions(-) delete mode 100644 editor/src/DocumentWindows/ConsoleWindow.cpp rename editor/src/DocumentWindows/{ => ConsoleWindow}/ConsoleWindow.hpp (85%) create mode 100644 editor/src/DocumentWindows/ConsoleWindow/Init.cpp create mode 100644 editor/src/DocumentWindows/ConsoleWindow/Log.cpp create mode 100644 editor/src/DocumentWindows/ConsoleWindow/Show.cpp create mode 100644 editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp create mode 100644 editor/src/DocumentWindows/ConsoleWindow/Update.cpp create mode 100644 editor/src/DocumentWindows/ConsoleWindow/Utils.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 7349daca8..94f49be45 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -36,7 +36,6 @@ set(SRCS editor/src/Editor.cpp editor/src/WindowRegistry.cpp editor/src/DockingRegistry.cpp - editor/src/DocumentWindows/ConsoleWindow.cpp editor/src/DocumentWindows/EditorScene/Gizmo.cpp editor/src/DocumentWindows/EditorScene/Init.cpp editor/src/DocumentWindows/EditorScene/Shortcuts.cpp @@ -48,6 +47,12 @@ set(SRCS editor/src/DocumentWindows/AssetManager/Show.cpp editor/src/DocumentWindows/AssetManager/Shutdown.cpp editor/src/DocumentWindows/AssetManager/Update.cpp + editor/src/DocumentWindows/ConsoleWindow/Init.cpp + editor/src/DocumentWindows/ConsoleWindow/Log.cpp + editor/src/DocumentWindows/ConsoleWindow/Show.cpp + editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp + editor/src/DocumentWindows/ConsoleWindow/Update.cpp + editor/src/DocumentWindows/ConsoleWindow/Utils.cpp editor/src/DocumentWindows/SceneTreeWindow.cpp editor/src/DocumentWindows/PopupManager.cpp editor/src/DocumentWindows/InspectorWindow.cpp diff --git a/editor/main.cpp b/editor/main.cpp index 4cc11b40e..1952323d3 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -13,7 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "src/Editor.hpp" -#include "src/DocumentWindows/ConsoleWindow.hpp" +#include "src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp" #include "src/DocumentWindows/EditorScene/EditorScene.hpp" #include "src/DocumentWindows/SceneTreeWindow.hpp" #include "src/DocumentWindows/InspectorWindow.hpp" diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp deleted file mode 100644 index f7d1a3b49..000000000 --- a/editor/src/DocumentWindows/ConsoleWindow.cpp +++ /dev/null @@ -1,375 +0,0 @@ -//// ConsoleWindow.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: Guillaume HEIN -// Date: 10/11/2024 -// Description: Source file for the console window class -// -/////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ConsoleWindow.hpp" -#include "ImNexo/Elements.hpp" -#include "Editor.hpp" -#include "Logger.hpp" -#include "Path.hpp" -#include "utils/FileSystem.hpp" - -#include - -namespace nexo::editor { - /** - * @brief Converts a loguru verbosity level to its corresponding string label. - * - * This function maps a given loguru verbosity level to a predefined string representation, - * such as "[FATAL]", "[ERROR]", "[WARNING]", "[INFO]", "[INVALID]", "[DEBUG]", or "[DEV]". - * If the provided level does not match any known values, it returns "[UNKNOWN]". - * - * @param level The loguru verbosity level to convert. - * @return std::string The string label corresponding to the provided verbosity level. - */ - static inline 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]"; - } - } - - /** - * @brief Converts a custom LogLevel to its corresponding loguru::Verbosity level. - * - * Maps each supported LogLevel to a specific loguru verbosity constant. If the provided - * level does not match any known value, the function returns loguru::Verbosity_INVALID. - * - * @param level The custom logging level to convert. - * @return The equivalent loguru verbosity level. - */ - 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; - } - return loguru::Verbosity_INVALID; - } - - /** - * @brief Returns the color corresponding to a log verbosity level. - * - * Maps the given loguru::Verbosity level to a specific ImVec4 color used for rendering log messages in the console. - * - Fatal and error messages are shown in red. - * - Warnings use yellow. - * - Informational messages appear in blue. - * - Debug levels display distinct pink and purple hues. - * The default color is white for any unrecognized verbosity levels. - * - * @param level The verbosity level for which the corresponding color is computed. - * @return ImVec4 The color associated with the specified verbosity level. - */ - static constexpr ImVec4 getVerbosityColor(loguru::Verbosity level) - { - ImVec4 color; - - switch (level) - { - case loguru::Verbosity_FATAL: // Red - 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); - break; // Yellow - 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 - } - return color; - } - - static std::string generateLogFilePath() - { - auto now = std::time(nullptr); - auto tm = *std::localtime(&now); - - std::ostringstream ss; - ss << "../logs/NEXO-"; - ss << std::put_time(&tm, "%Y-%m-%d-%H%M%S"); - ss << ".log"; - - return ss.str(); - } - - - 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; - console->addLog(newMessage); - } - - - ConsoleWindow::ConsoleWindow(const std::string &windowName, WindowRegistry ®istry) : ADocumentWindow(windowName, registry) - { - loguru::add_callback(LOGURU_CALLBACK_NAME, &ConsoleWindow::loguruCallback, - this, loguru::Verbosity_MAX); - - auto engineLogCallback = [](const LogLevel level, const std::string &message) { - const auto loguruLevel = nexoLevelToLoguruLevel(level); - VLOG_F(loguruLevel, "%s", message.c_str()); - }; - Logger::setCallback(engineLogCallback); - m_logFilePath = Path::resolvePathRelativeToExe(generateLogFilePath()).string(); - 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 - } - - void ConsoleWindow::shutdown() - { - clearLog(); - } - - void ConsoleWindow::addLog(const LogMessage &message) - { - if (m_logs.size() >= m_maxLogCapacity) { - m_bufferLogsToExport.push_back(message); - m_logs.erase(m_logs.begin()); - } - - if (m_bufferLogsToExport.size() > m_maxBufferLogToExportCapacity) { - exportLogsBuffered(); - m_bufferLogsToExport.clear(); - } - - m_logs.push_back(message); - } - - void ConsoleWindow::clearLog() - { - exportLogsBuffered(); - 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; - } - } - - m_logs.clear(); - } - - template - void ConsoleWindow::addLog(const char *fmt, Args &&... args) - { - try { - char buffer[1024]; - int result = snprintf(buffer, sizeof(buffer), fmt, std::forward(args)...); - if (result < 0) - return; - - LogMessage newMessage; - newMessage.verbosity = loguru::Verbosity_1; - newMessage.message = std::string(buffer); - newMessage.prefix = ""; - m_logs.push_back(newMessage); - } catch (const std::exception &e) { - LogMessage newMessage; - newMessage.verbosity = loguru::Verbosity_ERROR; - - char errorBuffer[1024]; - snprintf(errorBuffer, sizeof(errorBuffer), "Error formatting log message: %s", e.what()); - newMessage.message = std::string(errorBuffer); - - newMessage.prefix = ""; - m_logs.push_back(newMessage); - } - - m_scrollToBottom = true; - } - - void ConsoleWindow::executeCommand(const char *commandLine) - { - m_commands.emplace_back(commandLine); - addLog("%s", commandLine); - } - - void ConsoleWindow::calcLogPadding() - { - m_logPadding = 0.0f; - - 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; - } - m_logPadding += ImGui::GetStyle().ItemSpacing.x; - } - - - void ConsoleWindow::displayLog(loguru::Verbosity verbosity, const std::string &msg) const - { - ImVec4 color = getVerbosityColor(verbosity); - ImGui::PushStyleColor(ImGuiCol_Text, color); - - const std::string tag = verbosityToString(verbosity); - ImGui::TextUnformatted(tag.c_str()); - ImGui::PopStyleColor(); - - ImGui::SameLine(); - ImGui::SetCursorPosX(m_logPadding); - - ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x); - ImGui::TextWrapped("%s", msg.c_str()); - ImGui::PopTextWrapPos(); - } - - void ConsoleWindow::exportLogsBuffered() - { - 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; - } - } - - void ConsoleWindow::showVerbositySettingsPopup() - { - ImGui::Text("Select Verbosity Levels"); - ImGui::Separator(); - - 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) - { - bool selected = (m_selectedVerbosityLevels.contains(level)); - if (ImGui::Checkbox(name, &selected)) - { - if (selected) - { - m_selectedVerbosityLevels.insert(level); - calcLogPadding(); - } else - { - m_selectedVerbosityLevels.erase(level); - calcLogPadding(); - } - } - } - - ImGui::Separator(); - ImGui::Checkbox("File logging", &m_exportLog); - if (ImNexo::Button("Open log folder")) - utils::openFolder(Path::resolvePathRelativeToExe("../logs").string()); - - ImGui::EndPopup(); - } - - void ConsoleWindow::show() - { - ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - ImGui::Begin(ICON_FA_FILE_TEXT " Console" NEXO_WND_USTRID_CONSOLE, &m_opened, ImGuiWindowFlags_NoCollapse); - firstDockSetup(NEXO_WND_USTRID_CONSOLE); - - const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); - ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerHeight), false, ImGuiWindowFlags_HorizontalScrollbar); - - if (m_logPadding == 0.0f) - calcLogPadding(); - - auto id = 0; - 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); - m_scrollToBottom = false; - - ImGui::EndChild(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60.0f); - - 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 (ImGui::BeginPopup("VerbositySettings")) - showVerbositySettingsPopup(); - - ImGui::End(); - } - - void ConsoleWindow::update() - { - //No need to update anything - } -} diff --git a/editor/src/DocumentWindows/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp similarity index 85% rename from editor/src/DocumentWindows/ConsoleWindow.hpp rename to editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp index d23f33a39..304564b21 100644 --- a/editor/src/DocumentWindows/ConsoleWindow.hpp +++ b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp @@ -19,6 +19,11 @@ namespace nexo::editor { + std::string verbosityToString(const loguru::Verbosity level); + loguru::Verbosity nexoLevelToLoguruLevel(const LogLevel level); + const ImVec4 getVerbosityColor(loguru::Verbosity level); + std::string generateLogFilePath(); + constexpr auto LOGURU_CALLBACK_NAME = "GEE"; /** @@ -154,8 +159,35 @@ namespace nexo::editor { * @param fmt Format string similar to printf * @param args Arguments for the format string */ - template - void addLog(const char* fmt, Args&&... args); + // Add this implementation to your ConsoleWindow.hpp file + template + void addLog(const char *fmt, Args &&... args) + { + try { + char buffer[1024]; + int result = snprintf(buffer, sizeof(buffer), fmt, std::forward(args)...); + if (result < 0) + return; + + LogMessage newMessage; + newMessage.verbosity = loguru::Verbosity_1; + newMessage.message = std::string(buffer); + newMessage.prefix = ""; + m_logs.push_back(newMessage); + } catch (const std::exception &e) { + LogMessage newMessage; + newMessage.verbosity = loguru::Verbosity_ERROR; + + char errorBuffer[1024]; + snprintf(errorBuffer, sizeof(errorBuffer), "Error formatting log message: %s", e.what()); + 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. diff --git a/editor/src/DocumentWindows/ConsoleWindow/Init.cpp b/editor/src/DocumentWindows/ConsoleWindow/Init.cpp new file mode 100644 index 000000000..acc11aa19 --- /dev/null +++ b/editor/src/DocumentWindows/ConsoleWindow/Init.cpp @@ -0,0 +1,50 @@ +//// Init.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: 28/04/2025 +// Description: Source file for the setup function of the console window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ConsoleWindow.hpp" +#include "Path.hpp" + +namespace nexo::editor { + 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; + console->addLog(newMessage); + } + + + ConsoleWindow::ConsoleWindow(const std::string &windowName, WindowRegistry ®istry) : ADocumentWindow(windowName, registry) + { + loguru::add_callback(LOGURU_CALLBACK_NAME, &ConsoleWindow::loguruCallback, + this, loguru::Verbosity_MAX); + + auto engineLogCallback = [](const LogLevel level, const std::string &message) { + const auto loguruLevel = nexoLevelToLoguruLevel(level); + VLOG_F(loguruLevel, "%s", message.c_str()); + }; + Logger::setCallback(engineLogCallback); + m_logFilePath = Path::resolvePathRelativeToExe(generateLogFilePath()).string(); + 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 + } +} diff --git a/editor/src/DocumentWindows/ConsoleWindow/Log.cpp b/editor/src/DocumentWindows/ConsoleWindow/Log.cpp new file mode 100644 index 000000000..5810d3a98 --- /dev/null +++ b/editor/src/DocumentWindows/ConsoleWindow/Log.cpp @@ -0,0 +1,59 @@ +//// Log.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: 28/04/2025 +// Description: Source file for the log function of the console window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ConsoleWindow.hpp" +#include + +namespace nexo::editor { + + void ConsoleWindow::addLog(const LogMessage &message) + { + if (m_logs.size() >= m_maxLogCapacity) { + m_bufferLogsToExport.push_back(message); + m_logs.erase(m_logs.begin()); + } + + if (m_bufferLogsToExport.size() > m_maxBufferLogToExportCapacity) { + exportLogsBuffered(); + m_bufferLogsToExport.clear(); + } + + m_logs.push_back(message); + } + + void ConsoleWindow::clearLog() + { + exportLogsBuffered(); + 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; + } + } + + m_logs.clear(); + } + + void ConsoleWindow::exportLogsBuffered() + { + 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; + } + } +} diff --git a/editor/src/DocumentWindows/ConsoleWindow/Show.cpp b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp new file mode 100644 index 000000000..fd007bfb6 --- /dev/null +++ b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp @@ -0,0 +1,147 @@ +//// Show.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: 28/04/2025 +// Description: Source file for the rendering of the console window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ConsoleWindow.hpp" +#include "IconsFontAwesome.h" +#include "ImNexo/Elements.hpp" +#include "utils/FileSystem.hpp" +#include "Path.hpp" + +namespace nexo::editor { + + void ConsoleWindow::showVerbositySettingsPopup() + { + ImGui::Text("Select Verbosity Levels"); + ImGui::Separator(); + + 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) + { + bool selected = (m_selectedVerbosityLevels.contains(level)); + if (ImGui::Checkbox(name, &selected)) + { + if (selected) + { + m_selectedVerbosityLevels.insert(level); + calcLogPadding(); + } else + { + m_selectedVerbosityLevels.erase(level); + calcLogPadding(); + } + } + } + + ImGui::Separator(); + ImGui::Checkbox("File logging", &m_exportLog); + if (ImNexo::Button("Open log folder")) + utils::openFolder(Path::resolvePathRelativeToExe("../logs").string()); + + ImGui::EndPopup(); + } + + void ConsoleWindow::executeCommand(const char *commandLine) + { + m_commands.emplace_back(commandLine); + addLog("%s", commandLine); + } + + void ConsoleWindow::calcLogPadding() + { + m_logPadding = 0.0f; + + 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; + } + m_logPadding += ImGui::GetStyle().ItemSpacing.x; + } + + void ConsoleWindow::displayLog(loguru::Verbosity verbosity, const std::string &msg) const + { + ImVec4 color = getVerbosityColor(verbosity); + ImGui::PushStyleColor(ImGuiCol_Text, color); + + const std::string tag = verbosityToString(verbosity); + ImGui::TextUnformatted(tag.c_str()); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImGui::SetCursorPosX(m_logPadding); + + ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x); + ImGui::TextWrapped("%s", msg.c_str()); + ImGui::PopTextWrapPos(); + } + + void ConsoleWindow::show() + { + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); + ImGui::Begin(ICON_FA_FILE_TEXT " Console" NEXO_WND_USTRID_CONSOLE, &m_opened, ImGuiWindowFlags_NoCollapse); + firstDockSetup(NEXO_WND_USTRID_CONSOLE); + + const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerHeight), false, ImGuiWindowFlags_HorizontalScrollbar); + + if (m_logPadding == 0.0f) + calcLogPadding(); + + auto id = 0; + 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); + m_scrollToBottom = false; + + ImGui::EndChild(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60.0f); + + 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 (ImGui::BeginPopup("VerbositySettings")) + showVerbositySettingsPopup(); + + ImGui::End(); + } +} diff --git a/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp b/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp new file mode 100644 index 000000000..a2bb7ec7f --- /dev/null +++ b/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp @@ -0,0 +1,24 @@ +//// Shutdown.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: 28/04/2025 +// Description: Source file for the shutdown of the console window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ConsoleWindow.hpp" + +namespace nexo::editor { + + void ConsoleWindow::shutdown() + { + clearLog(); + } + +} diff --git a/editor/src/DocumentWindows/ConsoleWindow/Update.cpp b/editor/src/DocumentWindows/ConsoleWindow/Update.cpp new file mode 100644 index 000000000..25c0711db --- /dev/null +++ b/editor/src/DocumentWindows/ConsoleWindow/Update.cpp @@ -0,0 +1,22 @@ +//// Update.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: 28/04/2025 +// Description: Source file for the update of the console window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ConsoleWindow.hpp" + +namespace nexo::editor { + void ConsoleWindow::update() + { + //No need to update anything + } +} diff --git a/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp new file mode 100644 index 000000000..426d23f3e --- /dev/null +++ b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp @@ -0,0 +1,118 @@ +//// Utils.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: 28/04/2025 +// Description: Source file for the utils methods +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ConsoleWindow.hpp" + +namespace nexo::editor { + /** + * @brief Converts a loguru verbosity level to its corresponding string label. + * + * This function maps a given loguru verbosity level to a predefined string representation, + * such as "[FATAL]", "[ERROR]", "[WARNING]", "[INFO]", "[INVALID]", "[DEBUG]", or "[DEV]". + * If the provided level does not match any known values, it returns "[UNKNOWN]". + * + * @param level The loguru verbosity level to convert. + * @return std::string The string label corresponding to the provided verbosity level. + */ + 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]"; + } + } + + /** + * @brief Converts a custom LogLevel to its corresponding loguru::Verbosity level. + * + * Maps each supported LogLevel to a specific loguru verbosity constant. If the provided + * level does not match any known value, the function returns loguru::Verbosity_INVALID. + * + * @param level The custom logging level to convert. + * @return The equivalent loguru verbosity level. + */ + 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; + } + return loguru::Verbosity_INVALID; + } + + /** + * @brief Returns the color corresponding to a log verbosity level. + * + * Maps the given loguru::Verbosity level to a specific ImVec4 color used for rendering log messages in the console. + * - Fatal and error messages are shown in red. + * - Warnings use yellow. + * - Informational messages appear in blue. + * - Debug levels display distinct pink and purple hues. + * The default color is white for any unrecognized verbosity levels. + * + * @param level The verbosity level for which the corresponding color is computed. + * @return ImVec4 The color associated with the specified verbosity level. + */ + const ImVec4 getVerbosityColor(loguru::Verbosity level) + { + ImVec4 color; + + switch (level) + { + case loguru::Verbosity_FATAL: // Red + 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); + break; // Yellow + 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 + } + return color; + } + + std::string generateLogFilePath() + { + auto now = std::time(nullptr); + auto tm = *std::localtime(&now); + + std::ostringstream ss; + ss << "../logs/NEXO-"; + ss << std::put_time(&tm, "%Y-%m-%d-%H%M%S"); + ss << ".log"; + + return ss.str(); + } +} From 44276d143475ad39e83cf88cdb3c9f6e139a48e5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 21:15:49 +0900 Subject: [PATCH 219/450] refactor(document-windows): split inspector window and material inspector into multiple source file --- editor/CMakeLists.txt | 10 +- editor/main.cpp | 4 +- .../EntityProperties/RenderProperty.cpp | 4 +- .../DocumentWindows/InspectorWindow/Init.cpp | 38 ++++++ .../{ => InspectorWindow}/InspectorWindow.hpp | 2 +- .../Show.cpp} | 110 ++++++------------ .../InspectorWindow/Shutdown.cpp | 24 ++++ .../InspectorWindow/Update.cpp | 24 ++++ .../MaterialInspector/Init.cpp | 24 ++++ .../MaterialInspector.hpp | 0 .../Show.cpp} | 66 ++++------- .../MaterialInspector/Shutdown.cpp | 24 ++++ .../MaterialInspector/Update.cpp | 24 ++++ .../src/DocumentWindows/SceneTreeWindow.cpp | 2 +- 14 files changed, 228 insertions(+), 128 deletions(-) create mode 100644 editor/src/DocumentWindows/InspectorWindow/Init.cpp rename editor/src/DocumentWindows/{ => InspectorWindow}/InspectorWindow.hpp (99%) rename editor/src/DocumentWindows/{InspectorWindow.cpp => InspectorWindow/Show.cpp} (64%) create mode 100644 editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp create mode 100644 editor/src/DocumentWindows/InspectorWindow/Update.cpp create mode 100644 editor/src/DocumentWindows/MaterialInspector/Init.cpp rename editor/src/DocumentWindows/{ => MaterialInspector}/MaterialInspector.hpp (100%) rename editor/src/DocumentWindows/{MaterialInspector.cpp => MaterialInspector/Show.cpp} (61%) create mode 100644 editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp create mode 100644 editor/src/DocumentWindows/MaterialInspector/Update.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 94f49be45..d2d0c150d 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -53,10 +53,16 @@ set(SRCS editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp editor/src/DocumentWindows/ConsoleWindow/Update.cpp editor/src/DocumentWindows/ConsoleWindow/Utils.cpp + editor/src/DocumentWindows/InspectorWindow/Init.cpp + editor/src/DocumentWindows/InspectorWindow/Show.cpp + editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp + editor/src/DocumentWindows/InspectorWindow/Update.cpp + editor/src/DocumentWindows/MaterialInspector/Init.cpp + editor/src/DocumentWindows/MaterialInspector/Show.cpp + editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp + editor/src/DocumentWindows/MaterialInspector/Update.cpp editor/src/DocumentWindows/SceneTreeWindow.cpp editor/src/DocumentWindows/PopupManager.cpp - editor/src/DocumentWindows/InspectorWindow.cpp - editor/src/DocumentWindows/MaterialInspector.cpp editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp diff --git a/editor/main.cpp b/editor/main.cpp index 1952323d3..0fd5cc5bf 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -16,9 +16,9 @@ #include "src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp" #include "src/DocumentWindows/EditorScene/EditorScene.hpp" #include "src/DocumentWindows/SceneTreeWindow.hpp" -#include "src/DocumentWindows/InspectorWindow.hpp" +#include "src/DocumentWindows/InspectorWindow/InspectorWindow.hpp" #include "src/DocumentWindows/AssetManager/AssetManagerWindow.hpp" -#include "src/DocumentWindows/MaterialInspector.hpp" +#include "src/DocumentWindows/MaterialInspector/MaterialInspector.hpp" #include #include diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index bf76c19c1..b9b9a4ee6 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -22,8 +22,8 @@ #include "utils/ScenePreview.hpp" #include "components/Camera.hpp" #include "components/Render.hpp" -#include "DocumentWindows/InspectorWindow.hpp" -#include "DocumentWindows/MaterialInspector.hpp" +#include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" +#include "DocumentWindows/MaterialInspector/MaterialInspector.hpp" #include "ImNexo/Panels.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/Components.hpp" diff --git a/editor/src/DocumentWindows/InspectorWindow/Init.cpp b/editor/src/DocumentWindows/InspectorWindow/Init.cpp new file mode 100644 index 000000000..1f274327b --- /dev/null +++ b/editor/src/DocumentWindows/InspectorWindow/Init.cpp @@ -0,0 +1,38 @@ +//// Init.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: 28/04/2025 +// Description: Source file for the inspector window setup +// +/////////////////////////////////////////////////////////////////////////////// + +#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" + +namespace nexo::editor { + + void InspectorWindow::setup() + { + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + registerProperty(); + } +} diff --git a/editor/src/DocumentWindows/InspectorWindow.hpp b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp similarity index 99% rename from editor/src/DocumentWindows/InspectorWindow.hpp rename to editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp index b1cad6469..c3d7d5f2e 100644 --- a/editor/src/DocumentWindows/InspectorWindow.hpp +++ b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp @@ -25,7 +25,7 @@ namespace nexo::editor { class InspectorWindow final : public ADocumentWindow { public: using ADocumentWindow::ADocumentWindow; - ~InspectorWindow() override; + ~InspectorWindow() override = default; /** * @brief Initializes the property handlers for various entity component types. diff --git a/editor/src/DocumentWindows/InspectorWindow.cpp b/editor/src/DocumentWindows/InspectorWindow/Show.cpp similarity index 64% rename from editor/src/DocumentWindows/InspectorWindow.cpp rename to editor/src/DocumentWindows/InspectorWindow/Show.cpp index 675eb16d8..e0dbc0100 100644 --- a/editor/src/DocumentWindows/InspectorWindow.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Show.cpp @@ -1,3 +1,4 @@ +//// Show.cpp /////////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -5,87 +6,18 @@ // zzz zzz zzz z zzzz zzzz zzzz zzzz // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // -// Author: Marie GIACOMEL -// Date: 23/11/2024 -// Description: Inspector window source file +// Author: Mehdy MORVAN +// Date: 28/04/2025 +// Description: Source file for the inspector window rendering // /////////////////////////////////////////////////////////////////////////////// #include "InspectorWindow.hpp" - -#include -#include -#include -#include - -#include "Application.hpp" -#include "ImNexo/Elements.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 "components/Transform.hpp" -#include "utils/ScenePreview.hpp" -#include "components/Camera.hpp" -#include "components/Light.hpp" #include "context/Selector.hpp" -#include "core/scene/SceneManager.hpp" - -extern ImGuiID g_materialInspectorDockID; - -namespace nexo::editor -{ - - InspectorWindow::~InspectorWindow() = default; - - void InspectorWindow::setup() - { - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); - registerProperty(); - } - - void InspectorWindow::shutdown() - { - // Nothing to clear for now - } - - void InspectorWindow::show() - { - ImGui::Begin(ICON_FA_SLIDERS " Inspector" NEXO_WND_USTRID_INSPECTOR, &m_opened, ImGuiWindowFlags_NoCollapse); - firstDockSetup(NEXO_WND_USTRID_INSPECTOR); - auto const &selector = Selector::get(); - - if (selector.getPrimarySelectionType() == SelectionType::SCENE) { - // Scene selection stays the same - only show the selected scene - showSceneProperties(selector.getSelectedScene()); - } - else if (selector.hasSelection()) { - const ecs::Entity primaryEntity = selector.getPrimaryEntity(); - - 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()); - ImGui::PopStyleColor(); - ImGui::Separator(); - } - - showEntityProperties(primaryEntity); - } +#include "ImNexo/Components.hpp" +#include "IconsFontAwesome.h" - ImGui::End(); - } +namespace nexo::editor { void InspectorWindow::showSceneProperties(const scene::SceneId sceneId) const { @@ -136,8 +68,32 @@ namespace nexo::editor } } - void InspectorWindow::update() + void InspectorWindow::show() { - // Nothing to update here + ImGui::Begin(ICON_FA_SLIDERS " Inspector" NEXO_WND_USTRID_INSPECTOR, &m_opened, ImGuiWindowFlags_NoCollapse); + firstDockSetup(NEXO_WND_USTRID_INSPECTOR); + auto const &selector = Selector::get(); + + if (selector.getPrimarySelectionType() == SelectionType::SCENE) { + // Scene selection stays the same - only show the selected scene + showSceneProperties(selector.getSelectedScene()); + } + else if (selector.hasSelection()) { + const ecs::Entity primaryEntity = selector.getPrimaryEntity(); + + 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()); + ImGui::PopStyleColor(); + ImGui::Separator(); + } + + showEntityProperties(primaryEntity); + } + + ImGui::End(); } + } diff --git a/editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp b/editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp new file mode 100644 index 000000000..cadc04517 --- /dev/null +++ b/editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp @@ -0,0 +1,24 @@ +//// Shutdown.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: 28/04/2025 +// Description: Source file the inspector window shutdown +// +/////////////////////////////////////////////////////////////////////////////// + +#include "InspectorWindow.hpp" + +namespace nexo::editor { + + void InspectorWindow::shutdown() + { + // Nothing to clear for now + } + +} diff --git a/editor/src/DocumentWindows/InspectorWindow/Update.cpp b/editor/src/DocumentWindows/InspectorWindow/Update.cpp new file mode 100644 index 000000000..bea86f4f6 --- /dev/null +++ b/editor/src/DocumentWindows/InspectorWindow/Update.cpp @@ -0,0 +1,24 @@ +//// Update.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: 28/04/2025 +// Description: Source file the inspector window update +// +/////////////////////////////////////////////////////////////////////////////// + +#include "InspectorWindow.hpp" + +namespace nexo::editor { + + void InspectorWindow::update() + { + // Nothing to update here + } + +} diff --git a/editor/src/DocumentWindows/MaterialInspector/Init.cpp b/editor/src/DocumentWindows/MaterialInspector/Init.cpp new file mode 100644 index 000000000..0a8792880 --- /dev/null +++ b/editor/src/DocumentWindows/MaterialInspector/Init.cpp @@ -0,0 +1,24 @@ +//// Init.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: 28/04/2025 +// Description: Source file for the material inspector setup +// +/////////////////////////////////////////////////////////////////////////////// + +#include "MaterialInspector.hpp" + +namespace nexo::editor { + + void MaterialInspector::setup() + { + // No need to setup anything + } + +} diff --git a/editor/src/DocumentWindows/MaterialInspector.hpp b/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp similarity index 100% rename from editor/src/DocumentWindows/MaterialInspector.hpp rename to editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp diff --git a/editor/src/DocumentWindows/MaterialInspector.cpp b/editor/src/DocumentWindows/MaterialInspector/Show.cpp similarity index 61% rename from editor/src/DocumentWindows/MaterialInspector.cpp rename to editor/src/DocumentWindows/MaterialInspector/Show.cpp index 8a95335b2..78925b262 100644 --- a/editor/src/DocumentWindows/MaterialInspector.cpp +++ b/editor/src/DocumentWindows/MaterialInspector/Show.cpp @@ -1,4 +1,4 @@ -//// MaterialInspector.cpp /////////////////////////////////////////////////////////////// +//// Show.cpp /////////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -7,35 +7,20 @@ // zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz // // Author: Mehdy MORVAN -// Date: 25/03/2025 -// Description: Source file for the material inspector window +// Date: 28/04/2025 +// Description: Source file for the material inspector rendering // /////////////////////////////////////////////////////////////////////////////// #include "MaterialInspector.hpp" -#include "DocumentWindows/InspectorWindow.hpp" -#include "Exception.hpp" -#include "components/Render.hpp" -#include "components/Render3D.hpp" #include "utils/ScenePreview.hpp" -#include "components/Camera.hpp" -#include "context/Selector.hpp" -#include "exceptions/Exceptions.hpp" +#include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" #include "ImNexo/Panels.hpp" +#include "context/Selector.hpp" namespace nexo::editor { - void MaterialInspector::setup() - { - // No need to setup anything - } - - void MaterialInspector::shutdown() - { - // No need to delete anything since the destructor of the framebuffer will handle it - } - - void MaterialInspector::renderMaterialInspector(int selectedEntity) + void MaterialInspector::renderMaterialInspector(int selectedEntity) { bool &materialModified = m_materialModified; static utils::ScenePreviewOut previewParams; @@ -52,7 +37,7 @@ namespace nexo::editor { { m_ecsEntity = -1; } - } + } if (m_ecsEntity == -1) return; @@ -85,28 +70,23 @@ namespace nexo::editor { void MaterialInspector::show() { - auto const &selector = Selector::get(); - const int selectedEntity = selector.getPrimaryEntity(); - auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); - if (!inspectorWindow) - return; + auto const &selector = Selector::get(); + const int selectedEntity = selector.getPrimaryEntity(); + 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; + { + 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)) - { - firstDockSetup(NEXO_WND_USTRID_MATERIAL_INSPECTOR); - renderMaterialInspector(selectedEntity); - } - ImGui::End(); - } - } - - void MaterialInspector::update() - { - // No need to update anything + if (ImGui::Begin("Material Inspector" NEXO_WND_USTRID_MATERIAL_INSPECTOR, &inspectorWindow->getSubInspectorVisibility(), window_flags)) + { + firstDockSetup(NEXO_WND_USTRID_MATERIAL_INSPECTOR); + renderMaterialInspector(selectedEntity); + } + ImGui::End(); + } } } diff --git a/editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp b/editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp new file mode 100644 index 000000000..f5f26157a --- /dev/null +++ b/editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp @@ -0,0 +1,24 @@ +//// Shutdown.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: 28/04/2025 +// Description: Source file for the material inspector shutdown +// +/////////////////////////////////////////////////////////////////////////////// + +#include "MaterialInspector.hpp" + +namespace nexo::editor { + + void MaterialInspector::shutdown() + { + // No need to delete anything since the destructor of the framebuffer will handle it + } + +} diff --git a/editor/src/DocumentWindows/MaterialInspector/Update.cpp b/editor/src/DocumentWindows/MaterialInspector/Update.cpp new file mode 100644 index 000000000..ad8af8908 --- /dev/null +++ b/editor/src/DocumentWindows/MaterialInspector/Update.cpp @@ -0,0 +1,24 @@ +//// Update.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: 28/04/2025 +// Description: Source file for the material inspector update +// +/////////////////////////////////////////////////////////////////////////////// + +#include "MaterialInspector.hpp" + +namespace nexo::editor { + + void MaterialInspector::update() + { + // No need to update anything + } + +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp index 3c33c2225..50c266d39 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow.cpp @@ -16,7 +16,7 @@ #include "Coordinator.hpp" #include "utils/Config.hpp" #include "utils/EditorProps.hpp" -#include "DocumentWindows/InspectorWindow.hpp" +#include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" #include "Primitive.hpp" #include "Path.hpp" From 91cbdaa575de480e8e127f65d8a72f8dcc7a218f Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 28 Apr 2025 21:32:45 +0900 Subject: [PATCH 220/450] refactor(document-windows): split scene tree window into multiple source file --- editor/CMakeLists.txt | 10 +- editor/main.cpp | 2 +- .../src/DocumentWindows/SceneTreeWindow.cpp | 709 ------------------ .../SceneTreeWindow/Hovering.cpp | 55 ++ .../DocumentWindows/SceneTreeWindow/Init.cpp | 24 + .../SceneTreeWindow/NodeHandling.cpp | 125 +++ .../SceneTreeWindow/Rename.cpp | 53 ++ .../SceneTreeWindow/SceneCreation.cpp | 87 +++ .../{ => SceneTreeWindow}/SceneTreeWindow.hpp | 4 +- .../SceneTreeWindow/Selection.cpp | 217 ++++++ .../DocumentWindows/SceneTreeWindow/Show.cpp | 175 +++++ .../SceneTreeWindow/Shutdown.cpp | 24 + .../SceneTreeWindow/Update.cpp | 80 ++ 13 files changed, 852 insertions(+), 713 deletions(-) delete mode 100644 editor/src/DocumentWindows/SceneTreeWindow.cpp create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/Init.cpp create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp rename editor/src/DocumentWindows/{ => SceneTreeWindow}/SceneTreeWindow.hpp (99%) create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/Show.cpp create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/Update.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index d2d0c150d..70bfe7c59 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -61,7 +61,15 @@ set(SRCS editor/src/DocumentWindows/MaterialInspector/Show.cpp editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp editor/src/DocumentWindows/MaterialInspector/Update.cpp - editor/src/DocumentWindows/SceneTreeWindow.cpp + editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp + editor/src/DocumentWindows/SceneTreeWindow/Init.cpp + editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp + editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp + editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp + editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp + editor/src/DocumentWindows/SceneTreeWindow/Show.cpp + editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp + editor/src/DocumentWindows/SceneTreeWindow/Update.cpp editor/src/DocumentWindows/PopupManager.cpp editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp diff --git a/editor/main.cpp b/editor/main.cpp index 0fd5cc5bf..824448303 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -15,7 +15,7 @@ #include "src/Editor.hpp" #include "src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp" #include "src/DocumentWindows/EditorScene/EditorScene.hpp" -#include "src/DocumentWindows/SceneTreeWindow.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" diff --git a/editor/src/DocumentWindows/SceneTreeWindow.cpp b/editor/src/DocumentWindows/SceneTreeWindow.cpp deleted file mode 100644 index 50c266d39..000000000 --- a/editor/src/DocumentWindows/SceneTreeWindow.cpp +++ /dev/null @@ -1,709 +0,0 @@ -// -// 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: Guillaume HEIN -// Date: 13/11/2024 -// Description: Source for the scene tree document window -// -/////////////////////////////////////////////////////////////////////////////// - -#include "SceneTreeWindow.hpp" -#include "ADocumentWindow.hpp" -#include "Coordinator.hpp" -#include "utils/Config.hpp" -#include "utils/EditorProps.hpp" -#include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" -#include "Primitive.hpp" -#include "Path.hpp" - -#include -#include -#include - -#include "DocumentWindows/EditorScene/EditorScene.hpp" -#include "components/Camera.hpp" -#include "components/Light.hpp" -#include "components/Render.hpp" -#include "components/SceneComponents.hpp" -#include "components/Transform.hpp" -#include "components/Uuid.hpp" -#include "context/Selector.hpp" -#include "LightFactory.hpp" -#include "ImNexo/Panels.hpp" - -namespace nexo::editor { - - SceneTreeWindow::~SceneTreeWindow() = default; - - void SceneTreeWindow::setup() - { - // Nothing to setup - } - - void SceneTreeWindow::shutdown() - { - // Nothing to shutdown - } - - void SceneTreeWindow::handleRename(SceneObject &obj) - { - ImGui::BeginGroup(); - ImGui::TextUnformatted(ObjectTypeToIcon.at(obj.type).c_str()); - ImGui::SameLine(); - - char buffer[256]; - const std::string editableName = obj.uiName.substr(ObjectTypeToIcon.at(obj.type).size()); - buffer[sizeof(buffer) - 1] = '\0'; - strncpy(buffer, editableName.c_str(), sizeof(buffer)); - - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove border - 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); - auto &selector = Selector::get(); - selector.setUiHandle(obj.uuid, obj.uiName); - 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(); - ImGui::PopStyleVar(3); - ImGui::EndGroup(); - } - - void SceneTreeWindow::handleHovering(const SceneObject &obj) const - { - if (obj.type == SelectionType::CAMERA) { - static bool cameraHoveredLastFrame = false; - if (ImGui::IsItemHovered()) { - cameraHovered(obj); - cameraHoveredLastFrame = true; - } else if (cameraHoveredLastFrame) { - cameraHoveredLastFrame = false; - auto &cameraComponent = Application::getInstance().m_coordinator->getComponent(obj.data.entity); - cameraComponent.render = false; - } - } - } - - bool SceneTreeWindow::handleSelection(const SceneObject &obj, const std::string &uniqueLabel, - const ImGuiTreeNodeFlags baseFlags) const - { - const bool nodeOpen = ImGui::TreeNodeEx(uniqueLabel.c_str(), baseFlags); - if (!nodeOpen) - return nodeOpen; - - if (ImGui::IsItemClicked()) - { - auto &selector = Selector::get(); - bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); - bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); - - if (isCtrlPressed) - selector.toggleSelection(obj.uuid, obj.data.entity, obj.type); - else if (isShiftPressed) - selector.addToSelection(obj.uuid, obj.data.entity, obj.type); - else - selector.selectEntity(obj.uuid, obj.data.entity, obj.type); - selector.setSelectedScene(obj.data.sceneProperties.sceneId); - } - return nodeOpen; - } - - void SceneTreeWindow::sceneSelected([[maybe_unused]] const SceneObject &obj) - { - if (ImGui::MenuItem("Delete Scene")) { - auto &app = Application::getInstance(); - auto &selector = Selector::get(); - selector.clearSelection(); - const std::string &sceneName = selector.getUiHandle(obj.uuid, obj.uiName); - m_windowRegistry.unregisterWindow(sceneName); - app.getSceneManager().deleteScene(obj.data.sceneProperties.sceneId); - } - - // ---- Add Entity submenu ---- - if (ImGui::BeginMenu("Add Entity")) { - auto &app = Application::getInstance(); - auto &sceneManager = app.getSceneManager(); - const auto sceneId = obj.data.sceneProperties.sceneId; - - // --- Primitives submenu --- - if (ImGui::BeginMenu("Primitives")) { - if (ImGui::MenuItem("Cube")) { - const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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); - } - ImGui::EndMenu(); - } - - // --- Model item (with file‑dialog) --- - if (ImGui::MenuItem("Model")) { - //TODO: import model - } - - // --- Lights submenu --- - 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); - } - if (ImGui::MenuItem("Point")) { - const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); - utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); - sceneManager.getScene(sceneId).addEntity(pointLight); - } - if (ImGui::MenuItem("Spot")) { - const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); - utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); - sceneManager.getScene(sceneId).addEntity(spotLight); - } - ImGui::EndMenu(); - } - - // --- Camera item --- - if (ImGui::MenuItem("Camera")) { - m_popupManager.openPopupWithCallback("Popup camera inspector", [this, obj]() { - const auto &editorScenes = m_windowRegistry.getWindows(); - for (const auto &scene : editorScenes) { - if (scene->getSceneId() == obj.data.sceneProperties.sceneId) { - ImNexo::CameraInspector(obj.data.sceneProperties.sceneId, scene->getViewportSize()); - break; - } - } - }, ImVec2(1440,900)); - } - - ImGui::EndMenu(); - } - } - - void SceneTreeWindow::lightSelected(const SceneObject &obj) const - { - 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(); - bool multipleSelected = selectedEntities.size() > 1; - - std::string menuText = multipleSelected ? - "Delete Selected Lights (" + std::to_string(selectedEntities.size()) + ")" : - "Delete Light"; - - if (ImGui::MenuItem(menuText.c_str())) - { - if (multipleSelected) { - // Delete all selected lights - for (const auto& entityId : selectedEntities) { - app.deleteEntity(entityId); - } - selector.clearSelection(); - } else { - // Delete just this light - selector.clearSelection(); - app.deleteEntity(obj.data.entity); - } - } - } - - void SceneTreeWindow::cameraHovered(const SceneObject &obj) const - { - auto &app = Application::getInstance(); - auto &cameraComponent = app.m_coordinator->getComponent(obj.data.entity); - - if (cameraComponent.m_renderTarget) - { - ImGui::BeginTooltip(); - constexpr float previewSize = 200.0f; - cameraComponent.render = true; - const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); - - ImGui::Image( - static_cast(static_cast(textureId)), - ImVec2(previewSize, previewSize), - ImVec2(0, 1), ImVec2(1, 0) // Flip Y coordinates for OpenGL texture - ); - - ImGui::EndTooltip(); - } - } - - void SceneTreeWindow::cameraSelected(const SceneObject &obj) const - { - 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(); - bool multipleSelected = selectedEntities.size() > 1; - - std::string deleteMenuText = multipleSelected ? - "Delete Selected Cameras (" + std::to_string(selectedEntities.size()) + ")" : - "Delete Camera"; - - if (ImGui::MenuItem(deleteMenuText.c_str())) - { - if (multipleSelected) { - // Delete all selected cameras - for (const auto& entityId : selectedEntities) { - app.deleteEntity(entityId); - } - selector.clearSelection(); - } else { - // Delete just this camera - selector.clearSelection(); - app.deleteEntity(obj.data.entity); - } - } - - // Switch to camera only makes sense for a single camera - if (!multipleSelected && ImGui::MenuItem("Switch to")) - { - auto &cameraComponent = app.m_coordinator->getComponent(obj.data.entity); - cameraComponent.render = true; - cameraComponent.active = true; - const auto &scenes = m_windowRegistry.getWindows(); - for (const auto &scene : scenes) { - if (scene->getSceneId() == obj.data.sceneProperties.sceneId) { - scene->setCamera(obj.data.entity); - break; - } - } - } - } - - void SceneTreeWindow::entitySelected(const SceneObject &obj) const - { - auto &selector = Selector::get(); - auto &app = nexo::getApp(); - - // Check if we're operating on a single item or multiple items - const auto& selectedEntities = selector.getSelectedEntities(); - bool multipleSelected = selectedEntities.size() > 1; - - std::string menuText = multipleSelected ? - "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : - "Delete Entity"; - - if (ImGui::MenuItem(menuText.c_str())) - { - if (multipleSelected) { - // Delete all selected entities - for (const auto& entityId : selectedEntities) { - app.deleteEntity(entityId); - } - selector.clearSelection(); - } else { - // Delete just this entity - selector.clearSelection(); - app.deleteEntity(obj.data.entity); - } - } - } - - void SceneTreeWindow::showNode(SceneObject &object) - { - 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; - - // Check if this object is selected - auto const &selector = Selector::get(); - bool isSelected = selector.isEntitySelected(object.data.entity); - - if (isSelected) - baseFlags |= ImGuiTreeNodeFlags_Selected; - - bool nodeOpen = false; - const std::string uniqueLabel = object.uiName; - - // If the user wishes to rename handle the rename, else handle the selection - if (m_renameTarget && m_renameTarget->first == object.type && m_renameTarget->second == object.uuid) - handleRename(object); - else - nodeOpen = handleSelection(object, uniqueLabel, baseFlags); - - handleHovering(object); - - // Handles the right click on each different type of object - if (object.type != SelectionType::NONE && ImGui::BeginPopupContextItem(uniqueLabel.c_str())) - { - // Only show rename option for the primary selected entity or for non-selected entities - if ((!isSelected || (isSelected && selector.getPrimaryEntity() == object.data.entity)) && - ImGui::MenuItem("Rename")) - { - m_renameTarget = {object.type, object.uuid}; - m_renameBuffer = object.uiName; - } - - if (object.type == SelectionType::SCENE) - sceneSelected(object); - else if (object.type == SelectionType::DIR_LIGHT || object.type == SelectionType::POINT_LIGHT || object.type == SelectionType::SPOT_LIGHT) - lightSelected(object); - else if (object.type == SelectionType::CAMERA) - cameraSelected(object); - else if (object.type == SelectionType::ENTITY) - entitySelected(object); - ImGui::EndPopup(); - } - - // Go further into the tree - if (nodeOpen && !leaf) - { - for (auto &child: object.children) - { - showNode(child); - } - ImGui::TreePop(); - } - } - - void SceneTreeWindow::sceneContextMenu() - { - if (m_popupManager.showPopup("Scene Tree Context Menu")) - { - if (ImGui::MenuItem("Create Scene")) - m_popupManager.openPopup("Create New Scene"); - m_popupManager.closePopup(); - } - - if (m_popupManager.showPopupModal("Popup camera inspector")) { - m_popupManager.runPopupCallback("Popup camera inspector"); - m_popupManager.closePopup(); - } - } - - bool SceneTreeWindow::setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName) - { - ImGuiWindow* floatingWindow = ImGui::FindWindowByName(floatingWindowName.c_str()); - if (!floatingWindow) - return false; - - // Create a new docking node - auto newDockId = ImGui::GetID("##DockNode"); - - // Configure the docking node - ImGui::DockBuilderRemoveNode(newDockId); - ImGui::DockBuilderAddNode(newDockId, ImGuiDockNodeFlags_None); - - // Set node size and position based on the floating window - ImVec2 windowPos = floatingWindow->Pos; - ImVec2 windowSize = floatingWindow->Size; - ImGui::DockBuilderSetNodeSize(newDockId, windowSize); - ImGui::DockBuilderSetNodePos(newDockId, windowPos); - - // Dock the windows to this node - ImGui::DockBuilderDockWindow(floatingWindowName.c_str(), newDockId); - ImGui::DockBuilderFinish(newDockId); - - // Update the registry with the new dock IDs - m_windowRegistry.setDockId(floatingWindowName, newDockId); - m_windowRegistry.setDockId(newSceneName, newDockId); - return true; - } - - bool SceneTreeWindow::handleSceneCreation(const std::string &newSceneName) - { - if (newSceneName.empty()) { - LOG(NEXO_WARN, "Scene name is empty !"); - return false; - } - - auto newScene = std::make_shared(newSceneName, m_windowRegistry); - newScene->setDefault(); - newScene->setup(); - m_windowRegistry.registerWindow(newScene); - - auto currentEditorSceneWindow = m_windowRegistry.getWindows(); - // If no editor scene is open, check the config file - if (currentEditorSceneWindow.empty()) { - const std::vector &editorSceneInConfig = findAllEditorScenes(); - if (!editorSceneInConfig.empty()) { - auto dockId = m_windowRegistry.getDockId(editorSceneInConfig[0]); - if (!dockId) - return false; - m_windowRegistry.setDockId(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId()), *dockId); - return true; - } - // If nothing is present in config file, simply let it float - return false; - } - - // Else we retrieve the first active editor scene - const std::string windowName = std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(currentEditorSceneWindow[0]->getSceneId()); - 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 (!dockId) { - setupNewDockSpaceNode(windowName, std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId())); - return true; - } - m_windowRegistry.setDockId(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId()), *dockId); - return true; - } - - void SceneTreeWindow::sceneCreationMenu() - { - 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")) { - if (handleSceneCreation(sceneNameBuffer)) { - memset(sceneNameBuffer, 0, sizeof(sceneNameBuffer)); - m_popupManager.closePopupInContext(); - } - } - - ImGui::SameLine(); - if (ImNexo::Button("Cancel")) - m_popupManager.closePopupInContext(); - - m_popupManager.closePopup(); - } - - void SceneTreeWindow::show() - { - ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - 300, 20), ImGuiCond_FirstUseEver); - 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)) - { - firstDockSetup(NEXO_WND_USTRID_SCENE_TREE); - - auto &selector = Selector::get(); - bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); - - // Ctrl+A to select all entities in current scene - if (isCtrlPressed && ImGui::IsKeyPressed(ImGuiKey_A) && ImGui::IsWindowFocused()) { - // Get current scene ID - int currentSceneId = selector.getSelectedScene(); - if (currentSceneId != -1) { - auto &app = nexo::getApp(); - auto &scene = app.getSceneManager().getScene(currentSceneId); - - selector.clearSelection(); - - // Add all entities in the scene to selection - for (const auto entity : scene.getEntities()) { - const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); - if (uuidComponent) { - selector.addToSelection(uuidComponent->get().uuid, entity); - } - } - } - } - - // Opens the right click popup when no items are hovered - 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 - const auto& selectedEntities = selector.getSelectedEntities(); - if (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); - } - sceneContextMenu(); - sceneCreationMenu(); - } - ImGui::End(); - } - - // Node creation methods - SceneObject SceneTreeWindow::newSceneNode(const std::string &sceneName, const scene::SceneId sceneId, const WindowId uiId) const - { - SceneObject sceneNode; - 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); - return sceneNode; - } - - void SceneTreeWindow::newLightNode(SceneObject &lightNode, const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity, const std::string &uiName) const - { - const SceneProperties sceneProperties{sceneId, uiId}; - lightNode.data.sceneProperties = sceneProperties; - lightNode.data.entity = lightEntity; - auto &selector = Selector::get(); - const auto entityUuid = Application::m_coordinator->tryGetComponent(lightEntity); - 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) const - { - SceneObject lightNode; - 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 lightNode; - lightNode.type = SelectionType::DIR_LIGHT; - 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 lightNode; - lightNode.type = SelectionType::SPOT_LIGHT; - 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 lightNode; - lightNode.type = SelectionType::POINT_LIGHT; - const std::string uiName = std::format("{}Point light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbPointLights); - newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); - return lightNode; - } - - SceneObject SceneTreeWindow::newCameraNode(const scene::SceneId sceneId, const WindowId uiId, - const ecs::Entity cameraEntity) const - { - SceneObject cameraNode; - const std::string uiName = ObjectTypeToIcon.at(SelectionType::CAMERA) + std::string("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.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); - } else - cameraNode.uiName = uiName; - return cameraNode; - } - - SceneObject SceneTreeWindow::newEntityNode(const scene::SceneId sceneId, const WindowId uiId, - const ecs::Entity entity) const - { - auto &selector = Selector::get(); - SceneObject entityNode; - const std::string uiName = std::format("{}{}", ObjectTypeToIcon.at(SelectionType::ENTITY), entity); - entityNode.type = SelectionType::ENTITY; - entityNode.data.sceneProperties = SceneProperties{sceneId, uiId}; - entityNode.data.entity = entity; - const auto entityUuid = nexo::Application::m_coordinator->tryGetComponent(entity); - if (entityUuid) - { - entityNode.uuid = entityUuid->get().uuid; - entityNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); - } - else - entityNode.uiName = uiName; - return entityNode; - } - - void SceneTreeWindow::update() - { - root_.uiName = "Scene Tree"; - root_.data.entity = -1; - root_.type = SelectionType::NONE; - root_.children.clear(); - m_nbPointLights = 0; - m_nbDirLights = 0; - m_nbSpotLights = 0; - - // Retrieves the scenes that are displayed on the GUI - const auto &scenes = m_windowRegistry.getWindows(); - std::map sceneNodes; - for (const auto &scene : scenes) - { - sceneNodes[scene->getSceneId()] = newSceneNode(scene->getWindowName(), scene->getSceneId(), windowId); - } - - generateNodes( - sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newAmbientLightNode(sceneId, uiId, entity); - }); - generateNodes( - sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newDirectionalLightNode(sceneId, uiId, entity); - }); - generateNodes( - sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newPointLightNode(sceneId, uiId, entity); - }); - generateNodes( - sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newSpotLightNode(sceneId, uiId, entity); - }); - - generateNodes>( - sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newCameraNode(sceneId, uiId, entity); - }); - - generateNodes< - components::RenderComponent, - components::TransformComponent, - components::SceneTag, - ecs::Exclude, - ecs::Exclude, - ecs::Exclude>( - sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newEntityNode(sceneId, uiId, entity); - }); - - for (const auto &[_, sceneNode] : sceneNodes) - { - root_.children.push_back(sceneNode); - } - } -} diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp new file mode 100644 index 000000000..86e02b329 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp @@ -0,0 +1,55 @@ +//// Hovering.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: 28/04/2025 +// Description: Source file for the hovering handling of the scene tree window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" + +namespace nexo::editor { + + void SceneTreeWindow::cameraHovered(const SceneObject &obj) const + { + auto &app = Application::getInstance(); + auto &cameraComponent = app.m_coordinator->getComponent(obj.data.entity); + + if (cameraComponent.m_renderTarget) + { + ImGui::BeginTooltip(); + constexpr float previewSize = 200.0f; + cameraComponent.render = true; + const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); + + ImGui::Image( + static_cast(static_cast(textureId)), + ImVec2(previewSize, previewSize), + ImVec2(0, 1), ImVec2(1, 0) // Flip Y coordinates for OpenGL texture + ); + + ImGui::EndTooltip(); + } + } + + void SceneTreeWindow::handleHovering(const SceneObject &obj) const + { + if (obj.type == SelectionType::CAMERA) { + static bool cameraHoveredLastFrame = false; + if (ImGui::IsItemHovered()) { + cameraHovered(obj); + cameraHoveredLastFrame = true; + } else if (cameraHoveredLastFrame) { + cameraHoveredLastFrame = false; + auto &cameraComponent = Application::getInstance().m_coordinator->getComponent(obj.data.entity); + cameraComponent.render = false; + } + } + } +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp new file mode 100644 index 000000000..fdd217f0f --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp @@ -0,0 +1,24 @@ +//// Init.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: 28/04/2025 +// Description: Source file for the scene tree window setup +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" + +namespace nexo::editor { + + void SceneTreeWindow::setup() + { + // Nothing to setup + } + +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp b/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp new file mode 100644 index 000000000..d12d8f4e0 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp @@ -0,0 +1,125 @@ +//// NodeHandling.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: 28/04/2025 +// Description: Source file for the node handling in the scene tree window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" +#include "components/Uuid.hpp" + +namespace nexo::editor { + + // Node creation methods + SceneObject SceneTreeWindow::newSceneNode(const std::string &sceneName, const scene::SceneId sceneId, const WindowId uiId) const + { + SceneObject sceneNode; + 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); + return sceneNode; + } + + void SceneTreeWindow::newLightNode(SceneObject &lightNode, const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity, const std::string &uiName) const + { + const SceneProperties sceneProperties{sceneId, uiId}; + lightNode.data.sceneProperties = sceneProperties; + lightNode.data.entity = lightEntity; + auto &selector = Selector::get(); + const auto entityUuid = Application::m_coordinator->tryGetComponent(lightEntity); + 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) const + { + SceneObject lightNode; + 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 lightNode; + lightNode.type = SelectionType::DIR_LIGHT; + 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 lightNode; + lightNode.type = SelectionType::SPOT_LIGHT; + 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 lightNode; + lightNode.type = SelectionType::POINT_LIGHT; + const std::string uiName = std::format("{}Point light {}", ObjectTypeToIcon.at(lightNode.type), ++m_nbPointLights); + newLightNode(lightNode, sceneId, uiId, lightEntity, uiName); + return lightNode; + } + + SceneObject SceneTreeWindow::newCameraNode(const scene::SceneId sceneId, const WindowId uiId, + const ecs::Entity cameraEntity) const + { + SceneObject cameraNode; + const std::string uiName = ObjectTypeToIcon.at(SelectionType::CAMERA) + std::string("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.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); + } else + cameraNode.uiName = uiName; + return cameraNode; + } + + SceneObject SceneTreeWindow::newEntityNode(const scene::SceneId sceneId, const WindowId uiId, + const ecs::Entity entity) const + { + auto &selector = Selector::get(); + SceneObject entityNode; + const std::string uiName = std::format("{}{}", ObjectTypeToIcon.at(SelectionType::ENTITY), entity); + entityNode.type = SelectionType::ENTITY; + entityNode.data.sceneProperties = SceneProperties{sceneId, uiId}; + entityNode.data.entity = entity; + const auto entityUuid = nexo::Application::m_coordinator->tryGetComponent(entity); + if (entityUuid) + { + entityNode.uuid = entityUuid->get().uuid; + entityNode.uiName = selector.getUiHandle(entityUuid->get().uuid, uiName); + } + else + entityNode.uiName = uiName; + return entityNode; + } +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp new file mode 100644 index 000000000..40b2f2ad8 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp @@ -0,0 +1,53 @@ +//// Rename.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: 28/04/2025 +// Description: Source file for the rename handling of the scene tree window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" + +namespace nexo::editor { + + void SceneTreeWindow::handleRename(SceneObject &obj) + { + ImGui::BeginGroup(); + ImGui::TextUnformatted(ObjectTypeToIcon.at(obj.type).c_str()); + ImGui::SameLine(); + + char buffer[256]; + const std::string editableName = obj.uiName.substr(ObjectTypeToIcon.at(obj.type).size()); + buffer[sizeof(buffer) - 1] = '\0'; + strncpy(buffer, editableName.c_str(), sizeof(buffer)); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove border + 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); + auto &selector = Selector::get(); + selector.setUiHandle(obj.uuid, obj.uiName); + 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(); + ImGui::PopStyleVar(3); + ImGui::EndGroup(); + } + +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp new file mode 100644 index 000000000..aa8b5cbb0 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp @@ -0,0 +1,87 @@ +//// SceneCreation.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: 28/04/2025 +// Description: Source file for the scene creation of the scene tree window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" +#include "utils/Config.hpp" + +namespace nexo::editor { + + bool SceneTreeWindow::setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName) + { + ImGuiWindow* floatingWindow = ImGui::FindWindowByName(floatingWindowName.c_str()); + if (!floatingWindow) + return false; + + // Create a new docking node + auto newDockId = ImGui::GetID("##DockNode"); + + // Configure the docking node + ImGui::DockBuilderRemoveNode(newDockId); + ImGui::DockBuilderAddNode(newDockId, ImGuiDockNodeFlags_None); + + // Set node size and position based on the floating window + ImVec2 windowPos = floatingWindow->Pos; + ImVec2 windowSize = floatingWindow->Size; + ImGui::DockBuilderSetNodeSize(newDockId, windowSize); + ImGui::DockBuilderSetNodePos(newDockId, windowPos); + + // Dock the windows to this node + ImGui::DockBuilderDockWindow(floatingWindowName.c_str(), newDockId); + ImGui::DockBuilderFinish(newDockId); + + // Update the registry with the new dock IDs + m_windowRegistry.setDockId(floatingWindowName, newDockId); + m_windowRegistry.setDockId(newSceneName, newDockId); + return true; + } + + bool SceneTreeWindow::handleSceneCreation(const std::string &newSceneName) + { + if (newSceneName.empty()) { + LOG(NEXO_WARN, "Scene name is empty !"); + return false; + } + + auto newScene = std::make_shared(newSceneName, m_windowRegistry); + newScene->setDefault(); + newScene->setup(); + m_windowRegistry.registerWindow(newScene); + + auto currentEditorSceneWindow = m_windowRegistry.getWindows(); + // If no editor scene is open, check the config file + if (currentEditorSceneWindow.empty()) { + const std::vector &editorSceneInConfig = findAllEditorScenes(); + if (!editorSceneInConfig.empty()) { + auto dockId = m_windowRegistry.getDockId(editorSceneInConfig[0]); + if (!dockId) + return false; + m_windowRegistry.setDockId(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId()), *dockId); + return true; + } + // If nothing is present in config file, simply let it float + return false; + } + + // Else we retrieve the first active editor scene + const std::string windowName = std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(currentEditorSceneWindow[0]->getSceneId()); + 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 (!dockId) { + setupNewDockSpaceNode(windowName, std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId())); + return true; + } + m_windowRegistry.setDockId(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId()), *dockId); + return true; + } +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp similarity index 99% rename from editor/src/DocumentWindows/SceneTreeWindow.hpp rename to editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp index fb423fe88..b3aa1e093 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp @@ -20,7 +20,7 @@ #include "core/scene/SceneManager.hpp" #include "context/Selector.hpp" -#include "PopupManager.hpp" +#include "../PopupManager.hpp" #include #include @@ -90,7 +90,7 @@ namespace nexo::editor { class SceneTreeWindow : public ADocumentWindow { public: using ADocumentWindow::ADocumentWindow; - ~SceneTreeWindow() override; + ~SceneTreeWindow() override = default; // No-op method in this class void setup() override; diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp new file mode 100644 index 000000000..e06957974 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp @@ -0,0 +1,217 @@ +//// Selection.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: 28/04/2025 +// Description: Source file for the selection of the scene tree +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" +#include "EntityFactory3D.hpp" +#include "LightFactory.hpp" +#include "utils/EditorProps.hpp" +#include "ImNexo/Panels.hpp" + +namespace nexo::editor { + + void SceneTreeWindow::entitySelected(const SceneObject &obj) const + { + auto &selector = Selector::get(); + auto &app = nexo::getApp(); + + // Check if we're operating on a single item or multiple items + const auto& selectedEntities = selector.getSelectedEntities(); + bool multipleSelected = selectedEntities.size() > 1; + + std::string menuText = multipleSelected ? + "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : + "Delete Entity"; + + if (ImGui::MenuItem(menuText.c_str())) + { + if (multipleSelected) { + // Delete all selected entities + for (const auto& entityId : selectedEntities) { + app.deleteEntity(entityId); + } + selector.clearSelection(); + } else { + // Delete just this entity + selector.clearSelection(); + app.deleteEntity(obj.data.entity); + } + } + } + + + void SceneTreeWindow::cameraSelected(const SceneObject &obj) const + { + 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(); + bool multipleSelected = selectedEntities.size() > 1; + + std::string deleteMenuText = multipleSelected ? + "Delete Selected Cameras (" + std::to_string(selectedEntities.size()) + ")" : + "Delete Camera"; + + if (ImGui::MenuItem(deleteMenuText.c_str())) + { + if (multipleSelected) { + // Delete all selected cameras + for (const auto& entityId : selectedEntities) { + app.deleteEntity(entityId); + } + selector.clearSelection(); + } else { + // Delete just this camera + selector.clearSelection(); + app.deleteEntity(obj.data.entity); + } + } + + // Switch to camera only makes sense for a single camera + if (!multipleSelected && ImGui::MenuItem("Switch to")) + { + auto &cameraComponent = app.m_coordinator->getComponent(obj.data.entity); + cameraComponent.render = true; + cameraComponent.active = true; + const auto &scenes = m_windowRegistry.getWindows(); + for (const auto &scene : scenes) { + if (scene->getSceneId() == obj.data.sceneProperties.sceneId) { + scene->setCamera(obj.data.entity); + break; + } + } + } + } + + void SceneTreeWindow::lightSelected(const SceneObject &obj) const + { + 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(); + bool multipleSelected = selectedEntities.size() > 1; + + std::string menuText = multipleSelected ? + "Delete Selected Lights (" + std::to_string(selectedEntities.size()) + ")" : + "Delete Light"; + + if (ImGui::MenuItem(menuText.c_str())) + { + if (multipleSelected) { + // Delete all selected lights + for (const auto& entityId : selectedEntities) { + app.deleteEntity(entityId); + } + selector.clearSelection(); + } else { + // Delete just this light + selector.clearSelection(); + app.deleteEntity(obj.data.entity); + } + } + } + + void SceneTreeWindow::sceneSelected([[maybe_unused]] const SceneObject &obj) + { + if (ImGui::MenuItem("Delete Scene")) { + auto &app = Application::getInstance(); + auto &selector = Selector::get(); + selector.clearSelection(); + const std::string &sceneName = selector.getUiHandle(obj.uuid, obj.uiName); + m_windowRegistry.unregisterWindow(sceneName); + app.getSceneManager().deleteScene(obj.data.sceneProperties.sceneId); + } + + // ---- Add Entity submenu ---- + if (ImGui::BeginMenu("Add Entity")) { + auto &app = Application::getInstance(); + auto &sceneManager = app.getSceneManager(); + const auto sceneId = obj.data.sceneProperties.sceneId; + + // --- Primitives submenu --- + if (ImGui::BeginMenu("Primitives")) { + if (ImGui::MenuItem("Cube")) { + const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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); + } + ImGui::EndMenu(); + } + + // --- Model item (with file‑dialog) --- + if (ImGui::MenuItem("Model")) { + //TODO: import model + } + + // --- Lights submenu --- + 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); + } + if (ImGui::MenuItem("Point")) { + const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); + utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); + sceneManager.getScene(sceneId).addEntity(pointLight); + } + if (ImGui::MenuItem("Spot")) { + const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); + utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); + sceneManager.getScene(sceneId).addEntity(spotLight); + } + ImGui::EndMenu(); + } + + // --- Camera item --- + if (ImGui::MenuItem("Camera")) { + m_popupManager.openPopupWithCallback("Popup camera inspector", [this, obj]() { + const auto &editorScenes = m_windowRegistry.getWindows(); + for (const auto &scene : editorScenes) { + if (scene->getSceneId() == obj.data.sceneProperties.sceneId) { + ImNexo::CameraInspector(obj.data.sceneProperties.sceneId, scene->getViewportSize()); + break; + } + } + }, ImVec2(1440,900)); + } + + ImGui::EndMenu(); + } + } + + bool SceneTreeWindow::handleSelection(const SceneObject &obj, const std::string &uniqueLabel, + const ImGuiTreeNodeFlags baseFlags) const + { + const bool nodeOpen = ImGui::TreeNodeEx(uniqueLabel.c_str(), baseFlags); + if (!nodeOpen) + return nodeOpen; + + if (ImGui::IsItemClicked()) + { + auto &selector = Selector::get(); + bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); + bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + + if (isCtrlPressed) + selector.toggleSelection(obj.uuid, obj.data.entity, obj.type); + else if (isShiftPressed) + selector.addToSelection(obj.uuid, obj.data.entity, obj.type); + else + selector.selectEntity(obj.uuid, obj.data.entity, obj.type); + selector.setSelectedScene(obj.data.sceneProperties.sceneId); + } + return nodeOpen; + } +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp new file mode 100644 index 000000000..0c0ac27c2 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -0,0 +1,175 @@ +//// Show.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: 28/04/2025 +// Description: Source file for the scene tree window rendering +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" +#include "components/Uuid.hpp" + +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"); + m_popupManager.closePopup(); + } + + if (m_popupManager.showPopupModal("Popup camera inspector")) { + m_popupManager.runPopupCallback("Popup camera inspector"); + m_popupManager.closePopup(); + } + } + + void SceneTreeWindow::sceneCreationMenu() + { + 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")) { + if (handleSceneCreation(sceneNameBuffer)) { + memset(sceneNameBuffer, 0, sizeof(sceneNameBuffer)); + m_popupManager.closePopupInContext(); + } + } + + ImGui::SameLine(); + if (ImNexo::Button("Cancel")) + m_popupManager.closePopupInContext(); + + m_popupManager.closePopup(); + } + + void SceneTreeWindow::showNode(SceneObject &object) + { + 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; + + // Check if this object is selected + auto const &selector = Selector::get(); + bool isSelected = selector.isEntitySelected(object.data.entity); + + if (isSelected) + baseFlags |= ImGuiTreeNodeFlags_Selected; + + bool nodeOpen = false; + const std::string uniqueLabel = object.uiName; + + // If the user wishes to rename handle the rename, else handle the selection + if (m_renameTarget && m_renameTarget->first == object.type && m_renameTarget->second == object.uuid) + handleRename(object); + else + nodeOpen = handleSelection(object, uniqueLabel, baseFlags); + + handleHovering(object); + + // Handles the right click on each different type of object + if (object.type != SelectionType::NONE && ImGui::BeginPopupContextItem(uniqueLabel.c_str())) + { + // Only show rename option for the primary selected entity or for non-selected entities + if ((!isSelected || (isSelected && selector.getPrimaryEntity() == object.data.entity)) && + ImGui::MenuItem("Rename")) + { + m_renameTarget = {object.type, object.uuid}; + m_renameBuffer = object.uiName; + } + + if (object.type == SelectionType::SCENE) + sceneSelected(object); + else if (object.type == SelectionType::DIR_LIGHT || object.type == SelectionType::POINT_LIGHT || object.type == SelectionType::SPOT_LIGHT) + lightSelected(object); + else if (object.type == SelectionType::CAMERA) + cameraSelected(object); + else if (object.type == SelectionType::ENTITY) + entitySelected(object); + ImGui::EndPopup(); + } + + // Go further into the tree + if (nodeOpen && !leaf) + { + for (auto &child: object.children) + { + showNode(child); + } + ImGui::TreePop(); + } + } + + void SceneTreeWindow::show() + { + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - 300, 20), ImGuiCond_FirstUseEver); + 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)) + { + firstDockSetup(NEXO_WND_USTRID_SCENE_TREE); + + auto &selector = Selector::get(); + bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + + // Ctrl+A to select all entities in current scene + if (isCtrlPressed && ImGui::IsKeyPressed(ImGuiKey_A) && ImGui::IsWindowFocused()) { + // Get current scene ID + int currentSceneId = selector.getSelectedScene(); + if (currentSceneId != -1) { + auto &app = nexo::getApp(); + auto &scene = app.getSceneManager().getScene(currentSceneId); + + selector.clearSelection(); + + // Add all entities in the scene to selection + for (const auto entity : scene.getEntities()) { + const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); + if (uuidComponent) { + selector.addToSelection(uuidComponent->get().uuid, entity); + } + } + } + } + + // Opens the right click popup when no items are hovered + 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 + const auto& selectedEntities = selector.getSelectedEntities(); + if (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); + } + sceneContextMenu(); + sceneCreationMenu(); + } + ImGui::End(); + } +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp new file mode 100644 index 000000000..0387ef769 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp @@ -0,0 +1,24 @@ +//// Shutdown.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: 28/04/2025 +// Description: Source file for the scene tree window shutdown +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" + +namespace nexo::editor { + + void SceneTreeWindow::shutdown() + { + // Nothing to shutdown + } + +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp new file mode 100644 index 000000000..a0a301cf9 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp @@ -0,0 +1,80 @@ +//// Update.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: 28/04/2025 +// Description: Source file for the update of the scene tree window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" + +namespace nexo::editor { + void SceneTreeWindow::update() + { + root_.uiName = "Scene Tree"; + root_.data.entity = -1; + root_.type = SelectionType::NONE; + root_.children.clear(); + m_nbPointLights = 0; + m_nbDirLights = 0; + m_nbSpotLights = 0; + + // Retrieves the scenes that are displayed on the GUI + const auto &scenes = m_windowRegistry.getWindows(); + std::map sceneNodes; + for (const auto &scene : scenes) + { + sceneNodes[scene->getSceneId()] = newSceneNode(scene->getWindowName(), scene->getSceneId(), windowId); + } + + generateNodes( + sceneNodes, + [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + return this->newAmbientLightNode(sceneId, uiId, entity); + }); + generateNodes( + sceneNodes, + [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + return this->newDirectionalLightNode(sceneId, uiId, entity); + }); + generateNodes( + sceneNodes, + [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + return this->newPointLightNode(sceneId, uiId, entity); + }); + generateNodes( + sceneNodes, + [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + return this->newSpotLightNode(sceneId, uiId, entity); + }); + + generateNodes>( + sceneNodes, + [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + return this->newCameraNode(sceneId, uiId, entity); + }); + + generateNodes< + components::RenderComponent, + components::TransformComponent, + components::SceneTag, + ecs::Exclude, + ecs::Exclude, + ecs::Exclude>( + sceneNodes, + [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + return this->newEntityNode(sceneId, uiId, entity); + }); + + for (const auto &[_, sceneNode] : sceneNodes) + { + root_.children.push_back(sceneNode); + } + } +} From 9addf14cff5502c386b4e3828e38fbbd61331da1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 00:08:38 +0900 Subject: [PATCH 221/450] refactor(document-windows): now using builder pattern for command sbortcuts creation --- .../EditorScene/EditorScene.hpp | 5 + .../DocumentWindows/EditorScene/Shortcuts.cpp | 1113 +++++++++-------- editor/src/inputs/Command.hpp | 26 + 3 files changed, 589 insertions(+), 555 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp index ed985e6dd..9c97b193a 100644 --- a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -145,6 +145,11 @@ namespace nexo::editor { */ void setupScene(); + void hideAllButSelectionCallback(); + void selectAllCallback(); + void unhideAllCallback(); + void deleteCallback(); + void setupGlobalState(); void setupGizmoState(); void setupGizmoTranslateState(); diff --git a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp index cb0f5cfa0..edf04b131 100644 --- a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp +++ b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp @@ -19,85 +19,152 @@ namespace nexo::editor { + static void hideCallback() + { + auto &selector = Selector::get(); + const auto &selectedEntities = selector.getSelectedEntities(); + auto &app = nexo::getApp(); + auto& actionManager = ActionManager::get(); + auto actionGroup = actionManager.createActionGroup(); + for (const auto entity : selectedEntities) { + auto &renderComponent = app.m_coordinator->getComponent(entity); + auto beforeState = renderComponent.save(); + renderComponent.isRendered = !renderComponent.isRendered; + auto afterState = renderComponent.save(); + actionGroup->addAction(std::make_unique>( + entity, beforeState, afterState)); + } + actionManager.recordAction(std::move(actionGroup)); + selector.clearSelection(); + } + + void EditorScene::selectAllCallback() + { + auto &selector = Selector::get(); + auto &app = nexo::getApp(); + auto &scene = app.getSceneManager().getScene(m_sceneId); + + selector.clearSelection(); + + for (const auto entity : scene.getEntities()) { + if (entity == m_editorCamera) continue; // Skip editor camera + + const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); + if (uuidComponent) + selector.addToSelection(uuidComponent->get().uuid, entity); + } + m_windowState = m_gizmoState; + } + + void EditorScene::hideAllButSelectionCallback() + { + auto &app = getApp(); + const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); + auto &selector = Selector::get(); + auto &actionManager = ActionManager::get(); + auto actionGroup = actionManager.createActionGroup(); + for (const auto entity : entities) { + if (app.m_coordinator->entityHasComponent(entity) && !selector.isEntitySelected(entity)) { + auto &renderComponent = app.m_coordinator->getComponent(entity); + if (renderComponent.isRendered) { + auto beforeState = renderComponent.save(); + renderComponent.isRendered = false; + auto afterState = renderComponent.save(); + actionGroup->addAction(std::make_unique>( + entity, beforeState, afterState)); + } + } + } + actionManager.recordAction(std::move(actionGroup)); + } + + void EditorScene::deleteCallback() + { + auto &selector = Selector::get(); + const auto &selectedEntities = selector.getSelectedEntities(); + auto &app = nexo::getApp(); + auto& actionManager = ActionManager::get(); + if (selectedEntities.size() > 1) { + auto actionGroup = actionManager.createActionGroup(); + for (const auto entity : selectedEntities) { + actionGroup->addAction(actionManager.prepareEntityDeletion(entity)); + app.deleteEntity(entity); + } + actionManager.recordAction(std::move(actionGroup)); + } else { + auto deleteAction = actionManager.prepareEntityDeletion(selectedEntities[0]); + app.deleteEntity(selectedEntities[0]); + actionManager.recordAction(std::move(deleteAction)); + } + selector.clearSelection(); + this->m_windowState = m_globalState; + } + + void EditorScene::unhideAllCallback() + { + auto &app = getApp(); + const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); + auto &actionManager = ActionManager::get(); + auto actionGroup = actionManager.createActionGroup(); + for (const auto entity : entities) { + if (app.m_coordinator->entityHasComponent(entity)) { + auto &renderComponent = app.m_coordinator->getComponent(entity); + if (!renderComponent.isRendered) { + auto beforeState = renderComponent.save(); + renderComponent.isRendered = true; + auto afterState = renderComponent.save(); + actionGroup->addAction(std::make_unique>( + entity, beforeState, afterState)); + } + } + } + actionManager.recordAction(std::move(actionGroup)); + } + void EditorScene::setupGlobalState() { // ================= GLOBAL STATE ============================= m_globalState = {static_cast(EditorState::GLOBAL)}; + + // Shift context m_globalState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Add entity", - "A", - [this]{ - this->m_popupManager.openPopup("Add new entity popup"); - }, - nullptr, - nullptr, - false, - } - } - } + Command::create() + .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() ); + + // Control context m_globalState.registerCommand( - { - "Control context", - "Ctrl", - nullptr, - nullptr, - nullptr, - true, - { - { - "Unhide all", - "H", - [this]{ - auto &app = getApp(); - const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); - for (const auto entity : entities) { - if (app.m_coordinator->entityHasComponent(entity)) { - auto &renderComponent = app.m_coordinator->getComponent(entity); - renderComponent.isRendered = true; - } - } - }, - nullptr, - nullptr, - false, - } - } - } + 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( - { - "Select all", - "A", - [this]{ - auto &selector = Selector::get(); - auto &app = nexo::getApp(); - auto &scene = app.getSceneManager().getScene(m_sceneId); - - selector.clearSelection(); - - for (const auto entity : scene.getEntities()) { - if (entity == m_editorCamera) continue; // Skip editor camera - - const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); - if (uuidComponent) - selector.addToSelection(uuidComponent->get().uuid, entity); - } - m_windowState = m_gizmoState; - }, - nullptr, - nullptr, - false - } + Command::create() + .description("Select all") + .key("A") + .onPressed([this]{ this->selectAllCallback(); }) + .build() ); } @@ -105,318 +172,254 @@ namespace nexo::editor { { // ================= GIZMO STATE ============================= m_gizmoState = {static_cast(EditorState::GIZMO)}; + + // Delete m_gizmoState.registerCommand( - { - "Delete", - "Delete", - [this]{ - auto &selector = Selector::get(); - const auto &selectedEntities = selector.getSelectedEntities(); - auto &app = nexo::getApp(); - auto& actionManager = ActionManager::get(); - if (selectedEntities.size() > 1) { - auto actionGroup = actionManager.createActionGroup(); - for (const auto entity : selectedEntities) { - actionGroup->addAction(actionManager.prepareEntityDeletion(entity)); - app.deleteEntity(entity); - } - actionManager.recordAction(std::move(actionGroup)); - } else { - auto deleteAction = actionManager.prepareEntityDeletion(selectedEntities[0]); - app.deleteEntity(selectedEntities[0]); - actionManager.recordAction(std::move(deleteAction)); - } - selector.clearSelection(); - this->m_windowState = m_globalState; - }, - nullptr, - nullptr, - false, - } + Command::create() + .description("Delete") + .key("Delete") + .onPressed([this]() { this->deleteCallback(); }) + .build() ); + + // Hide m_gizmoState.registerCommand( - { - "Hide", - "H", - [this]{ - auto &selector = Selector::get(); - const auto &selectedEntities = selector.getSelectedEntities(); - auto &app = nexo::getApp(); - auto& actionManager = ActionManager::get(); - if (selectedEntities.size() > 1) { - auto actionGroup = actionManager.createActionGroup(); - for (const auto entity : selectedEntities) { - auto &renderComponent = app.m_coordinator->getComponent(entity); - auto beforeState = renderComponent.save(); - renderComponent.isRendered = !renderComponent.isRendered; - auto afterState = renderComponent.save(); - actionGroup->addAction(std::make_unique>( - entity, beforeState, afterState)); - } - actionManager.recordAction(std::move(actionGroup)); - } else { - auto &renderComponent = app.m_coordinator->getComponent(selectedEntities[0]); - auto beforeState = renderComponent.save(); - renderComponent.isRendered = !renderComponent.isRendered; - auto afterState = renderComponent.save(); - actionManager.recordAction(std::move(std::make_unique>( - selectedEntities[0], beforeState, afterState))); - } - selector.clearSelection(); - }, - nullptr, - nullptr, - false, - } + Command::create() + .description("Hide") + .key("H") + .onPressed(&hideCallback) + .build() ); + + // Translate m_gizmoState.registerCommand( - { - "Translate", - "G", - [this]{ + Command::create() + .description("Translate") + .key("G") + .onPressed([this]{ this->m_windowState = m_gizmoTranslateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Rotate m_gizmoState.registerCommand( - { - "Rotate", - "R", - [this]{ + Command::create() + .description("Rotate") + .key("R") + .onPressed([this]{ this->m_windowState = m_gizmoRotateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Scale m_gizmoState.registerCommand( - { - "Scale", - "S", - [this]{ + Command::create() + .description("Scale") + .key("S") + .onPressed([this]{ this->m_windowState = m_gizmoScaleState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Shift context m_gizmoState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Toggle snapping", - "S", - [this]{ + 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; - }, - [this]{ + }) + .onReleased([this]{ m_snapTranslateOn = false; m_snapRotateOn = false; - }, - nullptr, - false, - }, - { - "Hide all but selection", - "H", - [this]{ - auto &app = getApp(); - const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); - auto &selector = Selector::get(); - for (const auto entity : entities) { - if (app.m_coordinator->entityHasComponent(entity) && !selector.isEntitySelected(entity)) { - auto &renderComponent = app.m_coordinator->getComponent(entity); - renderComponent.isRendered = false; - } - } - }, - nullptr, - nullptr, - false, - } - } - } + }) + .build() + ) + .addChild( + Command::create() + .description("Hide all but selection") + .key("H") + .onPressed([this]{ this->hideAllButSelectionCallback(); }) + .build() + ) + .build() ); } void EditorScene::setupGizmoTranslateState() { - // ================= TRANSLATE STATE ============================= m_gizmoTranslateState = {static_cast(EditorState::GIZMO_TRANSLATE)}; + + // Universal m_gizmoTranslateState.registerCommand( - { - "Universal", - "U", - [this]{ + Command::create() + .description("Universal") + .key("U") + .onPressed([this]{ this->m_windowState = m_gizmoState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Translate m_gizmoTranslateState.registerCommand( - { - "Translate", - "G", - [this]{ + Command::create() + .description("Translate") + .key("G") + .onPressed([this]{ this->m_windowState = m_gizmoTranslateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - [this]{ + }) + .onRepeat([this]{ if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; else this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; - }, - false, - } + }) + .build() ); + + // Rotate m_gizmoTranslateState.registerCommand( - { - "Rotate", - "R", - [this]{ + Command::create() + .description("Rotate") + .key("R") + .onPressed([this]{ this->m_windowState = m_gizmoRotateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Scale m_gizmoTranslateState.registerCommand( - { - "Scale", - "S", - [this]{ + Command::create() + .description("Scale") + .key("S") + .onPressed([this]{ this->m_windowState = m_gizmoScaleState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Shift context m_gizmoTranslateState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Exclude X", - "X", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_X); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_X); - }, - nullptr, - false, - }, - { - "Exclude Y", - "Y", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Y); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Y); - }, - nullptr, - false, - }, - { - "Exclude Z", - "Z", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::TRANSLATE_Z); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE_Z); - }, - nullptr, - false, - }, - { - "Toggle snapping", - "S", - [this]{ + Command::create() + .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; - }, - [this]{ + }) + .onReleased([this]{ m_snapTranslateOn = false; - }, - nullptr, - false, - } - } - } + }) + .build() + ) + .build() ); + + // Lock X m_gizmoTranslateState.registerCommand( - { - "Lock X", - "X", - [this]{ + Command::create() + .description("Lock X") + .key("X") + .onPressed([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_X; - }, - [this]{ + }) + .onReleased([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - false, - } + }) + .build() ); + + // Lock Y m_gizmoTranslateState.registerCommand( - { - "Lock Y", - "Y", - [this]{ + Command::create() + .description("Lock Y") + .key("Y") + .onPressed([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Y; - }, - [this]{ + }) + .onReleased([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - false, - } + }) + .build() ); + + // Lock Z m_gizmoTranslateState.registerCommand( - { - "Lock Z", - "Z", - [this]{ + Command::create() + .description("Lock Z") + .key("Z") + .onPressed([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE_Z; - }, - [this]{ + }) + .onReleased([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - false, - } + }) + .build() ); } @@ -424,164 +427,164 @@ namespace nexo::editor { { // ================= ROTATE STATE ============================= m_gizmoRotateState = {static_cast(EditorState::GIZMO_ROTATE)}; + + // Universal m_gizmoRotateState.registerCommand( - { - "Universal", - "U", - [this]{ + Command::create() + .description("Universal") + .key("U") + .onPressed([this]{ this->m_windowState = m_gizmoState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Rotate m_gizmoRotateState.registerCommand( - { - "Rotate", - "R", - [this]{ + Command::create() + .description("Rotate") + .key("R") + .onPressed([this]{ this->m_windowState = m_gizmoRotateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, // << Key pressed - nullptr, // << Key released - [this]{ + }) + .onRepeat([this]{ if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; else this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; - }, // << Key repeat - false, - } + }) + .build() ); + + // Translate m_gizmoRotateState.registerCommand( - { - "Translate", - "G", - [this]{ + Command::create() + .description("Translate") + .key("G") + .onPressed([this]{ this->m_windowState = m_gizmoTranslateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Scale m_gizmoRotateState.registerCommand( - { - "Scale", - "S", - [this]{ + Command::create() + .description("Scale") + .key("S") + .onPressed([this]{ this->m_windowState = m_gizmoScaleState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Shift context m_gizmoRotateState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Exclude X", - "X", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_X); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_X); - }, - nullptr, - false, - }, - { - "Exclude Y", - "Y", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Y); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Y); - }, - nullptr, - false, - }, - { - "Exclude Z", - "Z", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::ROTATE_Z); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE_Z); - }, - nullptr, - false, - }, - { - "Toggle snapping", - "S", - [this]{ + Command::create() + .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; - }, - [this]{ + }) + .onReleased([this]{ m_snapRotateOn = false; - }, - nullptr, - false, - }, - } - } + }) + .build() + ) + .build() ); + + // Lock X m_gizmoRotateState.registerCommand( - { - "Lock X", - "X", - [this]{ + Command::create() + .description("Lock X") + .key("X") + .onPressed([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_X; - }, - [this]{ + }) + .onReleased([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - false, - } + }) + .build() ); + + // Lock Y m_gizmoRotateState.registerCommand( - { - "Lock Y", - "Y", - [this]{ + Command::create() + .description("Lock Y") + .key("Y") + .onPressed([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Y; - }, - [this]{ + }) + .onReleased([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - false, - } + }) + .build() ); + + // Lock Z m_gizmoRotateState.registerCommand( - { - "Lock Z", - "Z", - [this]{ + Command::create() + .description("Lock Z") + .key("Z") + .onPressed([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE_Z; - }, - [this]{ + }) + .onReleased([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - false, - } + }) + .build() ); } @@ -589,152 +592,152 @@ namespace nexo::editor { { // ================= SCALE STATE ============================= m_gizmoScaleState = {static_cast(EditorState::GIZMO_SCALE)}; + + // Universal m_gizmoScaleState.registerCommand( - { - "Universal", - "U", - [this]{ + Command::create() + .description("Universal") + .key("U") + .onPressed([this]{ this->m_windowState = m_gizmoState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Scale m_gizmoScaleState.registerCommand( - { - "Scale", - "S", - [this]{ + Command::create() + .description("Scale") + .key("S") + .onPressed([this]{ this->m_windowState = m_gizmoScaleState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - [this]{ + }) + .onRepeat([this]{ if (this->m_currentGizmoMode == ImGuizmo::MODE::LOCAL) this->m_currentGizmoMode = ImGuizmo::MODE::WORLD; else this->m_currentGizmoMode = ImGuizmo::MODE::LOCAL; - }, - false, - } + }) + .build() ); + + // Translate m_gizmoScaleState.registerCommand( - { - "Translate", - "G", - [this]{ + Command::create() + .description("Translate") + .key("G") + .onPressed([this]{ this->m_windowState = m_gizmoTranslateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Rotate m_gizmoScaleState.registerCommand( - { - "Rotate", - "R", - [this]{ + Command::create() + .description("Rotate") + .key("R") + .onPressed([this]{ this->m_windowState = m_gizmoRotateState; this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE; - }, - nullptr, - nullptr, - false, - } + }) + .build() ); + + // Shift context m_gizmoScaleState.registerCommand( - { - "Shift context", - "Shift", - nullptr, - nullptr, - nullptr, - true, - { - { - "Exclude X", - "X", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_X); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_X); - }, - nullptr, - false, - }, - { - "Exclude Y", - "Y", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Y); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Y); - }, - nullptr, - false, - }, - { - "Exclude Z", - "Z", - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ~ImGuizmo::OPERATION::SCALE_Z); - }, - [this]{ - m_currentGizmoOperation = static_cast(m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE_Z); - }, - nullptr, - false, - } - } - } + Command::create() + .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() ); + + // Lock X m_gizmoScaleState.registerCommand( - { - "Lock X", - "X", - [this]{ + Command::create() + .description("Lock X") + .key("X") + .onPressed([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_X; - }, - [this]{ + }) + .onReleased([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - false, - } + }) + .build() ); + + // Lock Y m_gizmoScaleState.registerCommand( - { - "Lock Y", - "Y", - [this]{ + Command::create() + .description("Lock Y") + .key("Y") + .onPressed([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Y; - }, - [this]{ + }) + .onReleased([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - false, - } + }) + .build() ); + + // Lock Z m_gizmoScaleState.registerCommand( - { - "Lock Z", - "Z", - [this]{ + Command::create() + .description("Lock Z") + .key("Z") + .onPressed([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE_Z; - }, - [this]{ + }) + .onReleased([this]{ this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE; - }, - nullptr, - false, - } + }) + .build() ); } diff --git a/editor/src/inputs/Command.hpp b/editor/src/inputs/Command.hpp index 331a9a366..db6fa0ead 100644 --- a/editor/src/inputs/Command.hpp +++ b/editor/src/inputs/Command.hpp @@ -44,6 +44,32 @@ namespace nexo::editor { const std::string &getDescription() const; const 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(std::function cb) { pressed = cb; return *this; } + Builder& onReleased(std::function cb) { released = cb; return *this; } + Builder& onRepeat(std::function cb) { repeat = cb; return *this; } + Builder& modifier(bool val) { mod = val; return *this; } + Builder& addChild(Command child) { children.push_back(std::move(child)); return *this; } + + Command build() const { + return Command(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 Builder(); } + private: std::bitset m_signature; std::string m_description; From 017c48bd07dc62f11b48c64607053e8872b1fc45 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 02:43:22 +0900 Subject: [PATCH 222/450] feat(document-windows): add str uid for bottom bar --- editor/src/ADocumentWindow.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index 02248dd9a..a5f19f089 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -29,6 +29,7 @@ namespace nexo::editor { #define NEXO_WND_USTRID_CONSOLE "###Console" #define NEXO_WND_USTRID_MATERIAL_INSPECTOR "###Material Inspector" #define NEXO_WND_USTRID_DEFAULT_SCENE "###Default Scene" + #define NEXO_WND_USTRID_BOTTOM_BAR "###CommandsBar" class ADocumentWindow : public IDocumentWindow { public: From 8001e2052b6e5e1335a8dd9d876f01fb59b609c1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 02:43:50 +0900 Subject: [PATCH 223/450] refactor(document-windows): split into multiple method + fix: bottom does not process inputs anymore --- editor/src/Editor.cpp | 122 ++++++++++++++++++++++++------------------ editor/src/Editor.hpp | 5 ++ 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 78cd4d1e4..d88d602db 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -12,6 +12,7 @@ // ////////////////////////////////////////////////////////////////////////////// +#include "ADocumentWindow.hpp" #define IMGUI_DEFINE_MATH_OPERATORS #include "utils/Config.hpp" @@ -317,23 +318,8 @@ namespace nexo::editor { ImGui::DockSpaceOverViewport(viewport->ID); } - void Editor::render() + void Editor::handleGlobalCommands() { - getApp().beginFrame(); - - ImGuiBackend::begin(); - - ImGuizmo::SetImGuiContext(ImGui::GetCurrentContext()); - ImGuizmo::BeginFrame(); - buildDockspace(); - - drawMenuBar(); - // ImGui::ShowDemoWindow(); - - - - m_windowRegistry.render(); - if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyPressed(ImGuiKey_Z)) { if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) @@ -345,48 +331,52 @@ namespace nexo::editor { ActionManager::get().undo(); } } + } - // Get the commands to display in the bottom bar + std::vector Editor::handleFocusedWindowCommands() + { 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 const float commandPersistTime = 2.0f; // Show last commands for 2 seconds + auto focusedWindow = m_windowRegistry.getFocusedWindow(); + if (focusedWindow) { - auto focusedWindow = m_windowRegistry.getFocusedWindow(); - if (focusedWindow) - { - WindowState currentState = m_windowRegistry.getFocusedWindow()->getWindowState(); - m_inputManager.processInputs(currentState); - possibleCommands = m_inputManager.getPossibleCommands(currentState); + WindowState currentState = m_windowRegistry.getFocusedWindow()->getWindowState(); + m_inputManager.processInputs(currentState); + possibleCommands = m_inputManager.getPossibleCommands(currentState); - // Update the last valid commands if we have any - if (!possibleCommands.empty()) - { - 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()) - { - // 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); - lastValidCommands = possibleCommands; - } - else - { - // Use the last valid set of commands - possibleCommands = lastValidCommands; - } + // Update the last valid commands if we have any + if (!possibleCommands.empty()) + { + 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()) + { + // 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); + lastValidCommands = possibleCommands; + } + else + { + // Use the last valid set of commands + possibleCommands = lastValidCommands; } } + return possibleCommands; + } + void Editor::drawShortcutBar(const std::vector &possibleCommands) + { const float bottomBarHeight = 38.0f; ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.05f, 0.06f, 0.12f, 0.85f)); // Matches your dark blue theme ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.0f)); @@ -402,9 +392,12 @@ namespace nexo::editor { ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBackground; + ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoBackground; - if (ImGui::Begin("CommandsBar", nullptr, bottomBarFlags)) + if (ImGui::Begin(NEXO_WND_USTRID_BOTTOM_BAR, nullptr, bottomBarFlags)) { const float textScaleFactor = 0.90f; // 15% larger text ImGui::SetWindowFontScale(textScaleFactor); @@ -481,10 +474,11 @@ namespace nexo::editor { } ImGui::End(); ImGui::PopStyleColor(2); // Pop both text and bg colors + } - - - + void Editor::drawBackground() + { + auto viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->Pos); ImGui::SetNextWindowSize(viewport->Size); ImGui::SetNextWindowViewport(viewport->ID); @@ -506,12 +500,34 @@ namespace nexo::editor { {0.73f, IM_COL32(58, 124, 161, 255) }, }; - float angle = 148; + constexpr float angle = 148; ImNexo::RectFilledLinearGradient(viewport->Pos, ImVec2(viewport->Pos.x + viewport->Size.x, viewport->Pos.y + viewport->Size.y), angle, stops); ImGui::End(); + } + + void Editor::render() + { + getApp().beginFrame(); + + ImGuiBackend::begin(); + + ImGuizmo::SetImGuiContext(ImGui::GetCurrentContext()); + ImGuizmo::BeginFrame(); + buildDockspace(); + + drawMenuBar(); + // ImGui::ShowDemoWindow(); + m_windowRegistry.render(); + + handleGlobalCommands(); + + // Get the commands to display in the bottom bar + std::vector possibleCommands = handleFocusedWindowCommands(); + drawShortcutBar(possibleCommands); + drawBackground(); ImGui::Render(); diff --git a/editor/src/Editor.hpp b/editor/src/Editor.hpp index 977f3ebd5..c7c46134a 100644 --- a/editor/src/Editor.hpp +++ b/editor/src/Editor.hpp @@ -164,6 +164,11 @@ namespace nexo::editor { * sets a flag to signal that the editor should quit. */ void drawMenuBar(); + void drawShortcutBar(const std::vector &possibleCommands); + void drawBackground(); + + void handleGlobalCommands(); + std::vector handleFocusedWindowCommands(); bool m_quit = false; bool m_showDemoWindow = false; From b03c95d24e59337fa92d421c9f64ec96d65a5d53 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 09:27:44 +0900 Subject: [PATCH 224/450] feat(document-windows): add shortcuts for scene tree window --- editor/CMakeLists.txt | 1 + .../src/DocumentWindows/EditorScene/Show.cpp | 4 +- .../DocumentWindows/SceneTreeWindow/Init.cpp | 1 + .../SceneTreeWindow/SceneTreeWindow.hpp | 29 ++ .../SceneTreeWindow/Shortcuts.cpp | 359 ++++++++++++++++++ .../DocumentWindows/SceneTreeWindow/Show.cpp | 31 +- .../SceneTreeWindow/Update.cpp | 6 + 7 files changed, 408 insertions(+), 23 deletions(-) create mode 100644 editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 70bfe7c59..b2b533ec2 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -70,6 +70,7 @@ set(SRCS editor/src/DocumentWindows/SceneTreeWindow/Show.cpp editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp editor/src/DocumentWindows/SceneTreeWindow/Update.cpp + editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp editor/src/DocumentWindows/PopupManager.cpp editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index d946e0d45..1af77edb5 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -129,8 +129,8 @@ namespace nexo::editor { ImGui::Dummy(ImVec2(0, 5)); m_viewPosition = ImGui::GetCursorScreenPos(); - m_focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); - m_hovered = ImGui::IsWindowHovered(ImGuiFocusedFlags_RootAndChildWindows); + m_focused = ImGui::IsWindowFocused(); + m_hovered = ImGui::IsWindowHovered(); app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused); if (m_focused && selector.getSelectedScene() != m_sceneId) { selector.setSelectedScene(m_sceneId); diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp index fdd217f0f..6094a7a9d 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp @@ -18,6 +18,7 @@ namespace nexo::editor { void SceneTreeWindow::setup() { + setupShortcuts(); // Nothing to setup } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp index b3aa1e093..80e64e117 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp @@ -374,5 +374,34 @@ namespace nexo::editor { * @return true if the docking setup was successful, false otherwise. */ bool setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName); + + enum class SceneTreeState { + GLOBAL, + NB_STATE + }; + WindowState m_defaultState; + bool m_forceExpandAll = false; + bool m_forceCollapseAll = false; + bool m_resetExpandState = false; + + // Add these method declarations + void setupShortcuts(); + void setupDefaultState(); + + // Add these callback methods + void deleteSelectedCallback(); + void expandAllCallback(); + void collapseAllCallback(); + void renameSelectedCallback(); + void duplicateSelectedCallback(); + void focusOnSelectedCallback(); + void selectAllCallback(); + void deselectAllCallback(); + void groupEntitiesCallback(); + void ungroupEntitiesCallback(); + void invertSelectionCallback(); + void hideSelectedCallback(); + void showSelectedCallback(); + void showAllCallback(); }; } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp new file mode 100644 index 000000000..43eb8c4f3 --- /dev/null +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp @@ -0,0 +1,359 @@ +//// Shortcuts.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: 29/04/2025 +// Description: Source file for the scene tree window shortcuts +// +/////////////////////////////////////////////////////////////////////////////// + +#include "SceneTreeWindow.hpp" +#include "context/ActionManager.hpp" +#include "components/Uuid.hpp" + +namespace nexo::editor { + void SceneTreeWindow::setupShortcuts() { + setupDefaultState(); + + // Start with the default state + m_windowState = m_defaultState; + } + + void SceneTreeWindow::setupDefaultState() { + m_defaultState = {static_cast(SceneTreeState::GLOBAL)}; // Use a unique ID for this state + + // CTRL context + m_defaultState.registerCommand( + Command::create() + .description("Control context") + .key("Ctrl") + .modifier(true) + .addChild( + Command::create() + .description("Select all") + .key("A") + .onPressed([this](){ + this->selectAllCallback(); }) + .build() + ) + .addChild( + Command::create() + .description("Duplicate") + .key("D") + .onPressed([this](){ this->duplicateSelectedCallback(); }) + .build() + ) + .addChild( + Command::create() + .description("Unhide all") + .key("H") + .onPressed([this](){ this->showAllCallback(); }) + .build() + ) + .build() + ); + + // Delete selected + m_defaultState.registerCommand( + Command::create() + .description("Delete selected") + .key("Delete") + .onPressed([this](){ this->deleteSelectedCallback(); }) + .build() + ); + + // Rename selected + m_defaultState.registerCommand( + Command::create() + .description("Rename selected") + .key("F2") + .onPressed([this](){ this->renameSelectedCallback(); }) + .build() + ); + + // Expand all nodes + m_defaultState.registerCommand( + Command::create() + .description("Expand all") + .key("K") + .onPressed([this](){ this->expandAllCallback(); }) + .build() + ); + + // Collapse all nodes + m_defaultState.registerCommand( + Command::create() + .description("Collapse all") + .key("B") + .onPressed([this](){ this->collapseAllCallback(); }) + .build() + ); + + // Hide selected + m_defaultState.registerCommand( + Command::create() + .description("Hide") + .key("H") + .onPressed([this](){ this->hideSelectedCallback(); }) + .build() + ); + } + + void SceneTreeWindow::selectAllCallback() { + // Get current scene ID + auto& selector = Selector::get(); + int currentSceneId = selector.getSelectedScene(); + + if (currentSceneId != -1) { + auto& app = nexo::getApp(); + auto& scene = app.getSceneManager().getScene(currentSceneId); + + selector.clearSelection(); + + // Add all entities in the scene to selection + for (const auto entity : scene.getEntities()) { + const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); + if (uuidComponent) { + selector.addToSelection(uuidComponent->get().uuid, entity); + } + } + } + } + + void SceneTreeWindow::deleteSelectedCallback() { + auto& selector = Selector::get(); + const auto& selectedEntities = selector.getSelectedEntities(); + + if (selectedEntities.empty()) return; + + auto& app = nexo::getApp(); + auto& actionManager = ActionManager::get(); + + if (selectedEntities.size() > 1) { + auto actionGroup = actionManager.createActionGroup(); + for (const auto entity : selectedEntities) { + actionGroup->addAction(actionManager.prepareEntityDeletion(entity)); + app.deleteEntity(entity); + } + actionManager.recordAction(std::move(actionGroup)); + } else { + auto deleteAction = actionManager.prepareEntityDeletion(selectedEntities[0]); + app.deleteEntity(selectedEntities[0]); + actionManager.recordAction(std::move(deleteAction)); + } + + selector.clearSelection(); + m_windowState = m_defaultState; + } + + void SceneTreeWindow::expandAllCallback() { + // Implementation would depend on your tree structure + // This could be implemented by setting a flag that forces + // all TreeNodeEx calls to return true (opened) + // For now, we'll just log it + m_forceCollapseAll = false; + m_forceExpandAll = true; + LOG(NEXO_INFO, "Expand all nodes in scene tree"); + } + + void SceneTreeWindow::collapseAllCallback() { + // Implementation would depend on your tree structure + // This could reset the expanded state of all nodes + // For now, we'll just log it + m_forceCollapseAll = true; + m_forceExpandAll = false; + LOG(NEXO_INFO, "Collapse all nodes in scene tree"); + } + + void SceneTreeWindow::renameSelectedCallback() { + } + + void SceneTreeWindow::duplicateSelectedCallback() { + auto& selector = Selector::get(); + const auto& selectedEntities = selector.getSelectedEntities(); + + if (selectedEntities.empty()) return; + + auto& app = nexo::getApp(); + auto& actionManager = ActionManager::get(); + int currentSceneId = selector.getSelectedScene(); + + if (currentSceneId == -1) return; + + // Clear current selection to prepare for selecting the new duplicates + selector.clearSelection(); + + std::vector newEntities; + auto actionGroup = actionManager.createActionGroup(); + + for (const auto entity : selectedEntities) { + LOG(NEXO_INFO, "Duplicating entity {}", entity); + } + + //actionManager.recordAction(std::move(actionGroup)); + + // Select all the newly created entities + for (const auto newEntity : newEntities) { + const auto uuidComponent = app.m_coordinator->tryGetComponent(newEntity); + if (uuidComponent) { + selector.addToSelection(uuidComponent->get().uuid, newEntity); + } + } + } + + void SceneTreeWindow::focusOnSelectedCallback() { + auto& selector = Selector::get(); + const auto& selectedEntities = selector.getSelectedEntities(); + + if (selectedEntities.empty()) return; + + // Implementation would focus the editor camera on the selected entity/entities + // This depends on your camera system + // For now, just log it + LOG(NEXO_INFO, "Focusing on {} selected entities", selectedEntities.size()); + } + + void SceneTreeWindow::deselectAllCallback() { + auto& selector = Selector::get(); + selector.clearSelection(); + m_windowState = m_defaultState; + } + + void SceneTreeWindow::groupEntitiesCallback() { + auto& selector = Selector::get(); + const auto& selectedEntities = selector.getSelectedEntities(); + + if (selectedEntities.size() <= 1) return; // Need at least 2 entities to group + + // Implementation would create a parent entity and reparent all selected entities to it + // This depends on your scene hierarchy system + // For now, just log it + LOG(NEXO_INFO, "Grouping {} entities", selectedEntities.size()); + } + + void SceneTreeWindow::ungroupEntitiesCallback() { + auto& selector = Selector::get(); + const auto& selectedEntities = selector.getSelectedEntities(); + + if (selectedEntities.empty()) return; + + // Implementation would unparent all children of the selected groups + // This depends on your scene hierarchy system + // For now, just log it + LOG(NEXO_INFO, "Ungrouping {} entities", selectedEntities.size()); + } + + void SceneTreeWindow::invertSelectionCallback() { + auto& selector = Selector::get(); + int currentSceneId = selector.getSelectedScene(); + + if (currentSceneId == -1) return; + + auto& app = nexo::getApp(); + auto& scene = app.getSceneManager().getScene(currentSceneId); + + // Get all entities in the scene + const std::set &allEntities = scene.getEntities(); + + // Get currently selected entities + const auto& selectedEntities = selector.getSelectedEntities(); + std::unordered_set selectedSet(selectedEntities.begin(), selectedEntities.end()); + + // Clear current selection + selector.clearSelection(); + + // Select entities that weren't selected before + for (const auto entity : allEntities) { + if (selectedSet.find(entity) == selectedSet.end()) { + const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); + if (uuidComponent) { + selector.addToSelection(uuidComponent->get().uuid, entity); + } + } + } + } + + void SceneTreeWindow::hideSelectedCallback() { + auto& selector = Selector::get(); + const auto& selectedEntities = selector.getSelectedEntities(); + + if (selectedEntities.empty()) return; + + auto& app = nexo::getApp(); + auto& actionManager = ActionManager::get(); + auto actionGroup = actionManager.createActionGroup(); + + for (const auto entity : selectedEntities) { + if (app.m_coordinator->entityHasComponent(entity)) { + auto& renderComponent = app.m_coordinator->getComponent(entity); + if (renderComponent.isRendered) { + auto beforeState = renderComponent.save(); + renderComponent.isRendered = !renderComponent.isRendered; + auto afterState = renderComponent.save(); + actionGroup->addAction(std::make_unique>( + entity, beforeState, afterState)); + } + } + } + + actionManager.recordAction(std::move(actionGroup)); + } + + void SceneTreeWindow::showSelectedCallback() { + auto& selector = Selector::get(); + const auto& selectedEntities = selector.getSelectedEntities(); + + if (selectedEntities.empty()) return; + + auto& app = nexo::getApp(); + auto& actionManager = ActionManager::get(); + auto actionGroup = actionManager.createActionGroup(); + + for (const auto entity : selectedEntities) { + if (app.m_coordinator->entityHasComponent(entity)) { + auto& renderComponent = app.m_coordinator->getComponent(entity); + if (!renderComponent.isRendered) { + auto beforeState = renderComponent.save(); + renderComponent.isRendered = true; + auto afterState = renderComponent.save(); + actionGroup->addAction(std::make_unique>( + entity, beforeState, afterState)); + } + } + } + + actionManager.recordAction(std::move(actionGroup)); + } + + void SceneTreeWindow::showAllCallback() { + auto& selector = Selector::get(); + int currentSceneId = selector.getSelectedScene(); + + if (currentSceneId == -1) return; + + auto& app = nexo::getApp(); + auto& scene = app.getSceneManager().getScene(currentSceneId); + auto& actionManager = ActionManager::get(); + auto actionGroup = actionManager.createActionGroup(); + + for (const auto entity : scene.getEntities()) { + if (app.m_coordinator->entityHasComponent(entity)) { + auto& renderComponent = app.m_coordinator->getComponent(entity); + if (!renderComponent.isRendered) { + auto beforeState = renderComponent.save(); + renderComponent.isRendered = true; + auto afterState = renderComponent.save(); + actionGroup->addAction(std::make_unique>( + entity, beforeState, afterState)); + } + } + } + + actionManager.recordAction(std::move(actionGroup)); + } +} diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp index 0c0ac27c2..02b07df82 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -75,6 +75,14 @@ namespace nexo::editor { bool nodeOpen = false; const std::string uniqueLabel = object.uiName; + if (m_forceExpandAll && !leaf) { + ImGui::SetNextItemOpen(true); + m_resetExpandState = true; + } else if (m_forceCollapseAll) { + ImGui::SetNextItemOpen(false); + m_resetExpandState = true; + } + // If the user wishes to rename handle the rename, else handle the selection if (m_renameTarget && m_renameTarget->first == object.type && m_renameTarget->second == object.uuid) handleRename(object); @@ -124,29 +132,10 @@ namespace nexo::editor { if (ImGui::Begin(ICON_FA_SITEMAP " Scene Tree" NEXO_WND_USTRID_SCENE_TREE, &m_opened, ImGuiWindowFlags_NoCollapse)) { firstDockSetup(NEXO_WND_USTRID_SCENE_TREE); + m_focused = ImGui::IsWindowFocused(); + m_hovered = ImGui::IsWindowHovered(); auto &selector = Selector::get(); - bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); - - // Ctrl+A to select all entities in current scene - if (isCtrlPressed && ImGui::IsKeyPressed(ImGuiKey_A) && ImGui::IsWindowFocused()) { - // Get current scene ID - int currentSceneId = selector.getSelectedScene(); - if (currentSceneId != -1) { - auto &app = nexo::getApp(); - auto &scene = app.getSceneManager().getScene(currentSceneId); - - selector.clearSelection(); - - // Add all entities in the scene to selection - for (const auto entity : scene.getEntities()) { - const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); - if (uuidComponent) { - selector.addToSelection(uuidComponent->get().uuid, entity); - } - } - } - } // Opens the right click popup when no items are hovered if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered( diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp index a0a301cf9..6700d5e39 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp @@ -25,6 +25,12 @@ namespace nexo::editor { m_nbDirLights = 0; m_nbSpotLights = 0; + if (m_resetExpandState) { + m_forceExpandAll = false; + m_forceCollapseAll = false; + m_resetExpandState = false; + } + // Retrieves the scenes that are displayed on the GUI const auto &scenes = m_windowRegistry.getWindows(); std::map sceneNodes; From c2e188008301754e7509898ad92b64cb2948fceb Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 09:35:43 +0900 Subject: [PATCH 225/450] fix(document-windows): viewport now correspond to scene viewport when creating --- editor/src/DocumentWindows/EditorScene/Init.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/src/DocumentWindows/EditorScene/Init.cpp b/editor/src/DocumentWindows/EditorScene/Init.cpp index b8c5ccbd3..4c2895a2c 100644 --- a/editor/src/DocumentWindows/EditorScene/Init.cpp +++ b/editor/src/DocumentWindows/EditorScene/Init.cpp @@ -86,5 +86,7 @@ namespace nexo::editor { oldCameraComponent.active = false; oldCameraComponent.render = false; m_activeCamera = cameraId; + auto &newCameraComponent = app.m_coordinator->getComponent(cameraId); + newCameraComponent.resize(m_viewSize.x, m_viewSize.y); } } From 225a0ae97e2a72d82c514f3345b17276d48b2b61 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 09:52:12 +0900 Subject: [PATCH 226/450] fix(document-windows): fix strange blip when cancelling the camera creation --- editor/src/ImNexo/Panels.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index 7e40caa23..82af1efd2 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -128,6 +128,23 @@ namespace ImNexo { static char cameraName[128] = ""; static bool nameIsEmpty = false; + static bool closingPopup = false; + static bool result = 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 + if (closingPopup) { + // Now it's safe to delete the entity + auto &app = nexo::getApp(); + app.deleteEntity(camera); + + camera = nexo::ecs::MAX_ENTITIES; + cameraName[0] = '\0'; + nameIsEmpty = false; + closingPopup = false; + + return true; + } ImGui::Columns(2, "CameraCreatorColumns", false); ImGui::SetColumnWidth(0, inspectorWidth); @@ -325,12 +342,9 @@ namespace ImNexo { ImGui::SameLine(); if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) { - nameIsEmpty = false; - app.deleteEntity(camera); - camera = nexo::ecs::MAX_ENTITIES; - cameraName[0] = '\0'; ImGui::CloseCurrentPopup(); - return true; + closingPopup = true; + return false; } return false; } From 230a8a2dd56d7db09393d99048126256782f40c5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 11:32:05 +0900 Subject: [PATCH 227/450] refactor(document-windows): add entity is now handled via a popup --- .../SceneTreeWindow/SceneTreeWindow.hpp | 3 + .../SceneTreeWindow/Selection.cpp | 68 +--------------- .../DocumentWindows/SceneTreeWindow/Show.cpp | 77 +++++++++++++++++++ 3 files changed, 84 insertions(+), 64 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp index 80e64e117..149f906e4 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp @@ -342,6 +342,8 @@ namespace nexo::editor { */ void sceneContextMenu(); + void showSceneSelectionContextMenu(scene::SceneId sceneId, const std::string &uuid = "", const std::string &uiName = ""); + /** * @brief Displays a modal popup for creating a new scene. * @@ -390,6 +392,7 @@ namespace nexo::editor { // Add these callback methods void deleteSelectedCallback(); + void addEntityCallback(); void expandAllCallback(); void collapseAllCallback(); void renameSelectedCallback(); diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp index e06957974..b723de359 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp @@ -125,70 +125,10 @@ namespace nexo::editor { void SceneTreeWindow::sceneSelected([[maybe_unused]] const SceneObject &obj) { - if (ImGui::MenuItem("Delete Scene")) { - auto &app = Application::getInstance(); - auto &selector = Selector::get(); - selector.clearSelection(); - const std::string &sceneName = selector.getUiHandle(obj.uuid, obj.uiName); - m_windowRegistry.unregisterWindow(sceneName); - app.getSceneManager().deleteScene(obj.data.sceneProperties.sceneId); - } - - // ---- Add Entity submenu ---- - if (ImGui::BeginMenu("Add Entity")) { - auto &app = Application::getInstance(); - auto &sceneManager = app.getSceneManager(); - const auto sceneId = obj.data.sceneProperties.sceneId; - - // --- Primitives submenu --- - if (ImGui::BeginMenu("Primitives")) { - if (ImGui::MenuItem("Cube")) { - const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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); - } - ImGui::EndMenu(); - } - - // --- Model item (with file‑dialog) --- - if (ImGui::MenuItem("Model")) { - //TODO: import model - } - - // --- Lights submenu --- - 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); - } - if (ImGui::MenuItem("Point")) { - const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); - utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); - sceneManager.getScene(sceneId).addEntity(pointLight); - } - if (ImGui::MenuItem("Spot")) { - const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); - utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); - sceneManager.getScene(sceneId).addEntity(spotLight); - } - ImGui::EndMenu(); - } - - // --- Camera item --- - if (ImGui::MenuItem("Camera")) { - m_popupManager.openPopupWithCallback("Popup camera inspector", [this, obj]() { - const auto &editorScenes = m_windowRegistry.getWindows(); - for (const auto &scene : editorScenes) { - if (scene->getSceneId() == obj.data.sceneProperties.sceneId) { - ImNexo::CameraInspector(obj.data.sceneProperties.sceneId, scene->getViewportSize()); - break; - } - } - }, ImVec2(1440,900)); - } - - ImGui::EndMenu(); - } + 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, diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp index 02b07df82..a979d566f 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -14,9 +14,80 @@ #include "SceneTreeWindow.hpp" #include "components/Uuid.hpp" +#include "EntityFactory3D.hpp" +#include "LightFactory.hpp" +#include "utils/EditorProps.hpp" +#include "ImNexo/Panels.hpp" namespace nexo::editor { + void SceneTreeWindow::showSceneSelectionContextMenu(scene::SceneId sceneId, const std::string &uuid, const std::string &uiName) + { + if (uuid != "" && uiName != "" &&ImGui::MenuItem("Delete Scene")) { + auto &app = Application::getInstance(); + auto &selector = Selector::get(); + selector.clearSelection(); + const std::string &sceneName = selector.getUiHandle(uuid, uiName); + m_windowRegistry.unregisterWindow(sceneName); + app.getSceneManager().deleteScene(sceneId); + } + + // ---- Add Entity submenu ---- + if (ImGui::BeginMenu("Add Entity")) { + 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, -5.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); + } + ImGui::EndMenu(); + } + + // --- Model item (with file‑dialog) --- + if (ImGui::MenuItem("Model")) { + //TODO: import model + } + + // --- Lights submenu --- + 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); + } + if (ImGui::MenuItem("Point")) { + const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); + utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); + sceneManager.getScene(sceneId).addEntity(pointLight); + } + if (ImGui::MenuItem("Spot")) { + const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); + utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); + sceneManager.getScene(sceneId).addEntity(spotLight); + } + ImGui::EndMenu(); + } + + // --- 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, scene->getViewportSize()); + break; + } + } + }, ImVec2(1440,900)); + } + + ImGui::EndMenu(); + } + } + void SceneTreeWindow::sceneContextMenu() { if (m_popupManager.showPopup("Scene Tree Context Menu")) @@ -26,6 +97,12 @@ namespace nexo::editor { m_popupManager.closePopup(); } + if (m_popupManager.showPopup("Scene selection context menu")) + { + m_popupManager.runPopupCallback("Scene selection context menu"); + m_popupManager.closePopup(); + } + if (m_popupManager.showPopupModal("Popup camera inspector")) { m_popupManager.runPopupCallback("Popup camera inspector"); m_popupManager.closePopup(); From 19f698adf734490c33cc59b64cb9b557a1998672 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 11:32:29 +0900 Subject: [PATCH 228/450] feat(document-windows): add expand/collapse shortcut + add entity shortcut --- .../SceneTreeWindow/Shortcuts.cpp | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp index 43eb8c4f3..1f99dae3c 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp @@ -17,15 +17,15 @@ #include "components/Uuid.hpp" namespace nexo::editor { - void SceneTreeWindow::setupShortcuts() { + void SceneTreeWindow::setupShortcuts() + { setupDefaultState(); - - // Start with the default state m_windowState = m_defaultState; } - void SceneTreeWindow::setupDefaultState() { - m_defaultState = {static_cast(SceneTreeState::GLOBAL)}; // Use a unique ID for this state + void SceneTreeWindow::setupDefaultState() + { + m_defaultState = {static_cast(SceneTreeState::GLOBAL)}; // CTRL context m_defaultState.registerCommand( @@ -55,13 +55,29 @@ namespace nexo::editor { .onPressed([this](){ this->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](){ this->addEntityCallback(); }) .build() ); // Delete selected m_defaultState.registerCommand( Command::create() - .description("Delete selected") + .description("Delete") .key("Delete") .onPressed([this](){ this->deleteSelectedCallback(); }) .build() @@ -70,7 +86,7 @@ namespace nexo::editor { // Rename selected m_defaultState.registerCommand( Command::create() - .description("Rename selected") + .description("Rename") .key("F2") .onPressed([this](){ this->renameSelectedCallback(); }) .build() @@ -80,7 +96,7 @@ namespace nexo::editor { m_defaultState.registerCommand( Command::create() .description("Expand all") - .key("K") + .key("Down") .onPressed([this](){ this->expandAllCallback(); }) .build() ); @@ -89,7 +105,7 @@ namespace nexo::editor { m_defaultState.registerCommand( Command::create() .description("Collapse all") - .key("B") + .key("Up") .onPressed([this](){ this->collapseAllCallback(); }) .build() ); @@ -104,8 +120,21 @@ namespace nexo::editor { ); } - void SceneTreeWindow::selectAllCallback() { - // Get current scene ID + 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]() {this->showSceneSelectionContextMenu(currentSceneId);} + ); + } + + void SceneTreeWindow::selectAllCallback() + { auto& selector = Selector::get(); int currentSceneId = selector.getSelectedScene(); @@ -115,7 +144,6 @@ namespace nexo::editor { selector.clearSelection(); - // Add all entities in the scene to selection for (const auto entity : scene.getEntities()) { const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); if (uuidComponent) { @@ -151,14 +179,10 @@ namespace nexo::editor { m_windowState = m_defaultState; } - void SceneTreeWindow::expandAllCallback() { - // Implementation would depend on your tree structure - // This could be implemented by setting a flag that forces - // all TreeNodeEx calls to return true (opened) - // For now, we'll just log it + void SceneTreeWindow::expandAllCallback() + { m_forceCollapseAll = false; m_forceExpandAll = true; - LOG(NEXO_INFO, "Expand all nodes in scene tree"); } void SceneTreeWindow::collapseAllCallback() { From 66bf66003332e6ed0b6701bbd5eece304733e361 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 15:15:05 +0900 Subject: [PATCH 229/450] fix(document-windows): fix camera not working when creating scene --- editor/src/ADocumentWindow.hpp | 1 + editor/src/DocumentWindows/EditorScene/Show.cpp | 2 ++ editor/src/DocumentWindows/EditorScene/Update.cpp | 2 +- editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp | 1 - 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index a5f19f089..179a0ec20 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -106,6 +106,7 @@ namespace nexo::editor { bool m_opened = true; bool m_focused = false; bool m_hovered = false; // TODO: make these update without user intervention + bool m_isVisibleInDock = true; bool m_firstOpened = true; diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index 1af77edb5..b1a11e17d 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -129,7 +129,9 @@ namespace nexo::editor { ImGui::Dummy(ImVec2(0, 5)); m_viewPosition = ImGui::GetCursorScreenPos(); + m_focused = ImGui::IsWindowFocused(); + m_isVisibleInDock = ImGui::IsWindowFocused(ImGuiFocusedFlags_DockHierarchy); m_hovered = ImGui::IsWindowHovered(); app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused); if (m_focused && selector.getSelectedScene() != m_sceneId) { diff --git a/editor/src/DocumentWindows/EditorScene/Update.cpp b/editor/src/DocumentWindows/EditorScene/Update.cpp index b58e13268..e982a7391 100644 --- a/editor/src/DocumentWindows/EditorScene/Update.cpp +++ b/editor/src/DocumentWindows/EditorScene/Update.cpp @@ -118,7 +118,7 @@ namespace nexo::editor { void EditorScene::update() { auto &selector = Selector::get(); - if (!m_opened || m_activeCamera == -1) + if (!m_opened || m_activeCamera == -1 || !m_isVisibleInDock) return; SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; runEngine(m_sceneId, RenderingType::FRAMEBUFFER, sceneType); diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp index b723de359..a70f826ca 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp @@ -49,7 +49,6 @@ namespace nexo::editor { } } - void SceneTreeWindow::cameraSelected(const SceneObject &obj) const { auto &app = Application::getInstance(); From 58c4554da817889842fedcfbd770fd8fbeaa6cd9 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 29 Apr 2025 15:44:27 +0900 Subject: [PATCH 230/450] fix(document-windows): add export compile commands for zed editor --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e95a8f99..e67aa1c7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ project(client CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(NEXO_COVERAGE OFF CACHE BOOL "Enable coverage for binaries") set(NEXO_GIT_SUBMODULE OFF CACHE BOOL "Enable git submodules init and update") From bb1068345f83c7833cb539b0465a3324e4e74949 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 12:25:45 +0900 Subject: [PATCH 231/450] refactor(document-windows): add a check for window visiblity when it is not docked --- editor/src/ADocumentWindow.cpp | 66 +++++++++++++++++++ editor/src/ADocumentWindow.hpp | 28 +------- .../src/DocumentWindows/EditorScene/Show.cpp | 17 +++-- .../DocumentWindows/EditorScene/Update.cpp | 4 +- 4 files changed, 80 insertions(+), 35 deletions(-) create mode 100644 editor/src/ADocumentWindow.cpp diff --git a/editor/src/ADocumentWindow.cpp b/editor/src/ADocumentWindow.cpp new file mode 100644 index 000000000..62fa75b66 --- /dev/null +++ b/editor/src/ADocumentWindow.cpp @@ -0,0 +1,66 @@ +//// ADocumentWindow.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: 29/04/2025 +// Description: Source file for the abstract document window class +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ADocumentWindow.hpp" + +namespace nexo::editor { + + void ADocumentWindow::firstDockSetup(const std::string &windowName) + { + if (ImGuiWindow* currentWindow = ImGui::GetCurrentWindow(); currentWindow) + { + const bool isDocked = currentWindow->DockIsActive; + const ImGuiID currentDockID = currentWindow->DockId; + 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 (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 + // 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); + m_firstOpened = false; + } + } + + void ADocumentWindow::visibilityCheck() + { + m_focused = ImGui::IsWindowFocused(); + bool isDocked = ImGui::IsWindowDocked(); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + + if (isDocked) { + ImGuiID dock_id = ImGui::GetWindowDockID(); + ImGuiDockNode* node = ImGui::DockBuilderGetNode(dock_id); + + // 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 { + // Not docked windows are visible if we've reached this point + m_isVisibleInDock = true; + } + + m_hovered = ImGui::IsWindowHovered(); + } +} diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index 179a0ec20..83ea527bf 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -75,37 +75,15 @@ namespace nexo::editor { * * @param windowName The name used to look up the expected dock identifier in the WindowRegistry. */ - void firstDockSetup(const std::string &windowName) - { - if (ImGuiWindow* currentWindow = ImGui::GetCurrentWindow(); currentWindow) - { - const bool isDocked = currentWindow->DockIsActive; - const ImGuiID currentDockID = currentWindow->DockId; - 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 (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 - // 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); - m_firstOpened = false; - } - } + void firstDockSetup(const std::string &windowName); + void visibilityCheck(); 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; bool m_isVisibleInDock = true; bool m_firstOpened = true; diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index b1a11e17d..0e31fc0b7 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -119,21 +119,20 @@ namespace nexo::editor { auto &selector = Selector::get(); m_windowName = selector.getUiHandle(m_sceneUuid, std::string(ICON_FA_GLOBE) + " " + m_windowName); const std::string &sceneWindowName = m_windowName + std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId); - + m_wasVisibleLastFrame = m_isVisibleInDock; + m_isVisibleInDock = false; if (ImGui::Begin(sceneWindowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse)) { firstDockSetup(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId)); - auto &app = getApp(); + auto &app = getApp(); // Add some spacing after the toolbar ImGui::Dummy(ImVec2(0, 5)); m_viewPosition = ImGui::GetCursorScreenPos(); - - m_focused = ImGui::IsWindowFocused(); - m_isVisibleInDock = ImGui::IsWindowFocused(ImGuiFocusedFlags_DockHierarchy); - m_hovered = ImGui::IsWindowHovered(); + visibilityCheck(); app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused); + if (m_focused && selector.getSelectedScene() != m_sceneId) { selector.setSelectedScene(m_sceneId); selector.clearSelection(); @@ -142,9 +141,9 @@ namespace nexo::editor { if (m_activeCamera == -1) renderNoActiveCamera(); else { - renderView(); - renderGizmo(); - renderToolbar(); + renderView(); + renderGizmo(); + renderToolbar(); } if (m_popupManager.showPopup("Add new entity popup")) diff --git a/editor/src/DocumentWindows/EditorScene/Update.cpp b/editor/src/DocumentWindows/EditorScene/Update.cpp index e982a7391..a4d76ba78 100644 --- a/editor/src/DocumentWindows/EditorScene/Update.cpp +++ b/editor/src/DocumentWindows/EditorScene/Update.cpp @@ -118,7 +118,9 @@ namespace nexo::editor { void EditorScene::update() { auto &selector = Selector::get(); - if (!m_opened || m_activeCamera == -1 || !m_isVisibleInDock) + bool is_currently_visible = m_isVisibleInDock || m_wasVisibleLastFrame; + + if (!m_opened || m_activeCamera == -1 || !is_currently_visible) return; SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; runEngine(m_sceneId, RenderingType::FRAMEBUFFER, sceneType); From 5b6eec251978e98076bb035e7eeb9d2ae8fc4ae8 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 12:26:03 +0900 Subject: [PATCH 232/450] fix(document-windows): camera not moving when switching scene --- engine/src/components/Camera.hpp | 2 + engine/src/systems/CameraSystem.cpp | 98 ++++++++++++++++++----------- 2 files changed, 64 insertions(+), 36 deletions(-) diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index a0e62a79f..3fb564d1b 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -163,6 +163,8 @@ namespace nexo::components { float yaw = 0.0f; ///< Yaw angle in degrees. float pitch = 0.0f; ///< Pitch angle in degrees. float translationSpeed = 5.0f; ///< Camera speed + bool wasMouseReleased = true; + bool wasActiveLastFrame = true; struct Memento { float mouseSensitivity; diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index bab131eef..32dbdaea9 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -1,4 +1,3 @@ -//// CameraSystem.cpp /////////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -142,43 +141,70 @@ namespace nexo::system { void PerspectiveCameraControllerSystem::handleEvent(event::EventMouseMove &event) { - auto const &renderContext = getSingleton(); - 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 glm::vec2 mouseDelta = (currentMousePosition - controller.lastMousePosition) * controller.mouseSensitivity; - controller.lastMousePosition = currentMousePosition; - const auto &sceneTag = getComponent(entity); - if (!sceneTag.isActive || sceneTag.id != sceneRendered) - continue; - const auto &cameraComponent = getComponent(entity); - if (cameraComponent.resizing || !event::isMouseDown(NEXO_MOUSE_LEFT) || !cameraComponent.active) - continue; - - controller.yaw += -mouseDelta.x; - controller.pitch += -mouseDelta.y; - - // Clamp pitch to avoid flipping - if (controller.pitch > 89.0f) - controller.pitch = 89.0f; - if (controller.pitch < -89.0f) - controller.pitch = -89.0f; - - // Rebuild the quaternion from yaw and pitch. - glm::quat qPitch = glm::angleAxis(glm::radians(controller.pitch), glm::vec3(1.0f, 0.0f, 0.0f)); - glm::quat qYaw = glm::angleAxis(glm::radians(controller.yaw), glm::vec3(0.0f, 1.0f, 0.0f)); + auto const &renderContext = getSingleton(); + if (renderContext.sceneRendered == -1) + return; - auto &transform = getComponent(entity); - transform.quat = glm::normalize(qYaw * qPitch); - event.consumed = true; + 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); + const auto &cameraComponent = getComponent(entity); + bool isActiveScene = sceneTag.isActive && sceneTag.id == sceneRendered; + bool isActiveCamera = isActiveScene && cameraComponent.active; + bool mouseDown = event::isMouseDown(NEXO_MOUSE_LEFT); + + // Check for scene transition - if the camera wasn't active before but is now + 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; + 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 mouse isn't down or we're just starting to track, just update position without movement + if (!mouseDown || controller.wasMouseReleased) { + controller.lastMousePosition = currentMousePosition; + controller.wasMouseReleased = false; + continue; + } + + if (cameraComponent.resizing) { + controller.lastMousePosition = currentMousePosition; + continue; + } + + const glm::vec2 mouseDelta = (currentMousePosition - controller.lastMousePosition) * controller.mouseSensitivity; + controller.yaw += -mouseDelta.x; + controller.pitch += -mouseDelta.y; + + // Clamp pitch to avoid flipping + if (controller.pitch > 89.0f) controller.pitch = 89.0f; + if (controller.pitch < -89.0f) controller.pitch = -89.0f; + + // Rebuild quaternion and update transform + glm::quat qPitch = glm::angleAxis(glm::radians(controller.pitch), glm::vec3(1.0f, 0.0f, 0.0f)); + glm::quat qYaw = glm::angleAxis(glm::radians(controller.yaw), glm::vec3(0.0f, 1.0f, 0.0f)); + + auto &transform = getComponent(entity); + transform.quat = glm::normalize(qYaw * qPitch); + + // Update last position after processing + controller.lastMousePosition = currentMousePosition; + event.consumed = true; } - } + } PerspectiveCameraTargetSystem::PerspectiveCameraTargetSystem() { From fbd45593f7e996a6b8256eb970af5a7ae1b589e6 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 12:26:22 +0900 Subject: [PATCH 233/450] chore(document-windows): add source files --- editor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index b2b533ec2..5d1097b3f 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -36,6 +36,7 @@ set(SRCS editor/src/Editor.cpp editor/src/WindowRegistry.cpp editor/src/DockingRegistry.cpp + editor/src/ADocumentWindow.cpp editor/src/DocumentWindows/EditorScene/Gizmo.cpp editor/src/DocumentWindows/EditorScene/Init.cpp editor/src/DocumentWindows/EditorScene/Shortcuts.cpp From 27bacead6aa19b808cdcded2e9a0e19446e53400 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 13:56:00 +0900 Subject: [PATCH 234/450] fix(document-windows): now only using quat from the transfrom for camera controller --- engine/src/components/Camera.hpp | 6 ------ engine/src/systems/CameraSystem.cpp | 31 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 3fb564d1b..38ab28709 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -160,8 +160,6 @@ namespace nexo::components { glm::vec2 lastMousePosition{}; ///< Last recorded mouse position. float mouseSensitivity = 0.1f;///< Sensitivity factor for mouse movement. - float yaw = 0.0f; ///< Yaw angle in degrees. - float pitch = 0.0f; ///< Pitch angle in degrees. float translationSpeed = 5.0f; ///< Camera speed bool wasMouseReleased = true; bool wasActiveLastFrame = true; @@ -176,8 +174,6 @@ namespace nexo::components { { PerspectiveCameraController controller; controller.mouseSensitivity = mouseSensitivity; - controller.yaw = yaw; - controller.pitch = pitch; controller.translationSpeed = translationSpeed; return controller; } @@ -187,8 +183,6 @@ namespace nexo::components { { return { mouseSensitivity, - yaw, - pitch, translationSpeed }; } diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index 32dbdaea9..a7bd9086c 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -173,7 +173,6 @@ namespace nexo::system { // 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 mouse isn't down or we're just starting to track, just update position without movement if (!mouseDown || controller.wasMouseReleased) { controller.lastMousePosition = currentMousePosition; controller.wasMouseReleased = false; @@ -185,20 +184,26 @@ namespace nexo::system { continue; } + auto &transform = getComponent(entity); const glm::vec2 mouseDelta = (currentMousePosition - controller.lastMousePosition) * controller.mouseSensitivity; - controller.yaw += -mouseDelta.x; - controller.pitch += -mouseDelta.y; - - // Clamp pitch to avoid flipping - if (controller.pitch > 89.0f) controller.pitch = 89.0f; - if (controller.pitch < -89.0f) controller.pitch = -89.0f; - // Rebuild quaternion and update transform - glm::quat qPitch = glm::angleAxis(glm::radians(controller.pitch), glm::vec3(1.0f, 0.0f, 0.0f)); - glm::quat qYaw = glm::angleAxis(glm::radians(controller.yaw), glm::vec3(0.0f, 1.0f, 0.0f)); - - auto &transform = getComponent(entity); - transform.quat = glm::normalize(qYaw * qPitch); + // Extract camera orientation vectors from current quaternion + glm::vec3 right = transform.quat * glm::vec3(1.0f, 0.0f, 0.0f); + glm::vec3 up = transform.quat * glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 front = transform.quat * glm::vec3(0.0f, 0.0f, -1.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::vec3 newFront = newQuat * glm::vec3(0.0f, 0.0f, -1.0f); + + // Check if the resulting orientation would flip the camera (pitch constraint) + float pitchAngle = glm::degrees(std::asin(newFront.y)); + if (pitchAngle < -85.0f || pitchAngle > 85.0f) + transform.quat = glm::normalize(yawRotation * transform.quat); + else + transform.quat = newQuat; // Update last position after processing controller.lastMousePosition = currentMousePosition; From cc43cd63616a051929c6f40fd670ffb8c3c8ab8d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 15:17:54 +0900 Subject: [PATCH 235/450] refactor(document-windows): add SceneInfo struct to pass to the run function of the engine for different scene props --- engine/src/Application.cpp | 12 ++++++------ engine/src/Application.hpp | 11 ++++++++++- engine/src/Nexo.cpp | 4 ++-- engine/src/Nexo.hpp | 2 +- engine/src/systems/RenderSystem.cpp | 2 ++ 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index 429e68611..e40556ec9 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -243,28 +243,28 @@ namespace nexo { m_lastFrameTime = time; } - void Application::run(const scene::SceneId sceneId, const RenderingType renderingType, const SceneType sceneType) + void Application::run(const SceneInfo &sceneInfo) { auto &renderContext = m_coordinator->getSingletonComponent(); if (!m_isMinimized) { - renderContext.sceneRendered = sceneId; - renderContext.sceneType = sceneType; - if (m_SceneManager.getScene(sceneId).isRendered()) + renderContext.sceneRendered = sceneInfo.id; + renderContext.sceneType = sceneInfo.sceneType; + if (m_SceneManager.getScene(sceneInfo.id).isRendered()) { m_cameraContextSystem->update(); m_lightSystem->update(); m_renderSystem->update(); } - if (m_SceneManager.getScene(sceneId).isActive()) + if (m_SceneManager.getScene(sceneInfo.id).isActive()) { m_perspectiveCameraControllerSystem->update(m_currentTimestep); } } // Update (swap buffers and poll events) - if (renderingType == RenderingType::WINDOW) + if (sceneInfo.renderingType == RenderingType::WINDOW) m_window->onUpdate(); m_eventManager->dispatchEvents(); renderContext.reset(); diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index 33658d985..e27119ef3 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -76,6 +76,15 @@ namespace nexo { */ void beginFrame(); + struct SceneInfo { + scene::SceneId id; + RenderingType renderingType = RenderingType::WINDOW; + SceneType sceneType = SceneType::GAME; + bool isChildWindow = false; + glm::vec2 childWindowPos{}; + glm::vec2 childWindowSize{}; + }; + /** * @brief Runs the application for the specified scene and rendering type. * @@ -93,7 +102,7 @@ namespace nexo { * @param renderingType The rendering mode (e.g., WINDOW or other types). * @param sceneType The type of scene to render. */ - void run(scene::SceneId sceneId, RenderingType renderingType, SceneType sceneType = SceneType::GAME); + void run(const SceneInfo &sceneInfo); /** * @brief Ends the current frame by clearing processed events. diff --git a/engine/src/Nexo.cpp b/engine/src/Nexo.cpp index 7447a1af2..ef18a5b6c 100644 --- a/engine/src/Nexo.cpp +++ b/engine/src/Nexo.cpp @@ -28,10 +28,10 @@ namespace nexo { return Application::getInstance(); } - void runEngine(const scene::SceneId id, const RenderingType renderingType, SceneType sceneType) + void runEngine(const Application::SceneInfo &sceneInfo) { Application &app = Application::getInstance(); - app.run(id, renderingType, sceneType); + app.run(sceneInfo); } } diff --git a/engine/src/Nexo.hpp b/engine/src/Nexo.hpp index 395f5a3c6..292c95317 100644 --- a/engine/src/Nexo.hpp +++ b/engine/src/Nexo.hpp @@ -45,5 +45,5 @@ namespace nexo { Application &getApp(); - void runEngine(scene::SceneId id, RenderingType renderingType = RenderingType::WINDOW, SceneType sceneType = SceneType::GAME); + void runEngine(const Application::SceneInfo &sceneInfo); } diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index fcca796d5..159ad58c0 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -149,7 +149,9 @@ namespace nexo::system { gridShader->setUniformFloat4("uGridColorThin", {0.5f, 0.55f, 0.7f, 0.6f}); // Soft blue-purple gridShader->setUniformFloat4("uGridColorThick", {0.7f, 0.75f, 0.9f, 0.8f}); // Lighter blue-purple const glm::vec2 &mousePos = event::getMousePosition(); + std::cout << "mouse pos " << mousePos.x << " " << mousePos.y << std::endl; const glm::vec2 &screenSize = camera.renderTarget->getSize(); + std::cout << "screen size " << screenSize.x << " " << screenSize.y << std::endl; const glm::vec3 &rayDir = math::projectRayToWorld(mousePos.x, mousePos.y, camera.viewProjectionMatrix, camera.cameraPosition, screenSize.x, screenSize.y); glm::vec3 mouseWorldPos = camera.cameraPosition; From b3988702b1dbbcc265a08c6c9c1f39ddf1bd40f8 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 15:18:30 +0900 Subject: [PATCH 236/450] refactor(document-windows): centralized the window pos/size and content size via a beginRender function that also handles the docking update and visibility update --- editor/src/ADocumentWindow.cpp | 21 +++++++++- editor/src/ADocumentWindow.hpp | 39 ++++++++++++------- .../src/DocumentWindows/AssetManager/Show.cpp | 2 +- .../DocumentWindows/ConsoleWindow/Show.cpp | 2 +- .../EditorScene/EditorScene.hpp | 9 ----- .../src/DocumentWindows/EditorScene/Gizmo.cpp | 2 +- .../src/DocumentWindows/EditorScene/Init.cpp | 11 +++--- .../src/DocumentWindows/EditorScene/Show.cpp | 27 ++++++------- .../DocumentWindows/EditorScene/Toolbar.cpp | 5 ++- .../DocumentWindows/EditorScene/Update.cpp | 8 ++-- .../EntityProperties/RenderProperty.cpp | 6 ++- .../DocumentWindows/InspectorWindow/Show.cpp | 2 +- .../MaterialInspector/Show.cpp | 7 ++-- .../DocumentWindows/SceneTreeWindow/Init.cpp | 1 - .../DocumentWindows/SceneTreeWindow/Show.cpp | 4 +- editor/src/IDocumentWindow.hpp | 1 + editor/src/ImNexo/Panels.cpp | 3 +- 17 files changed, 86 insertions(+), 64 deletions(-) diff --git a/editor/src/ADocumentWindow.cpp b/editor/src/ADocumentWindow.cpp index 62fa75b66..be2ce2263 100644 --- a/editor/src/ADocumentWindow.cpp +++ b/editor/src/ADocumentWindow.cpp @@ -16,7 +16,14 @@ namespace nexo::editor { - void ADocumentWindow::firstDockSetup(const std::string &windowName) + void ADocumentWindow::beginRender(const std::string &windowName) + { + dockingUpdate(windowName); + visibilityUpdate(); + sizeUpdate(); + } + + void ADocumentWindow::dockingUpdate(const std::string &windowName) { if (ImGuiWindow* currentWindow = ImGui::GetCurrentWindow(); currentWindow) { @@ -42,7 +49,7 @@ namespace nexo::editor { } } - void ADocumentWindow::visibilityCheck() + void ADocumentWindow::visibilityUpdate() { m_focused = ImGui::IsWindowFocused(); bool isDocked = ImGui::IsWindowDocked(); @@ -63,4 +70,14 @@ namespace nexo::editor { m_hovered = ImGui::IsWindowHovered(); } + + void ADocumentWindow::sizeUpdate() + { + ImGuiWindow* window = ImGui::GetCurrentWindow(); + m_windowPos = window->Pos; + m_windowSize = window->Size; + m_contentSizeMin = ImGui::GetWindowContentRegionMin(); + m_contentSizeMax = ImGui::GetWindowContentRegionMax(); + m_contentSize = ImGui::GetContentRegionAvail(); + } } diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index 83ea527bf..a925972ae 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -49,6 +49,8 @@ namespace nexo::editor { [[nodiscard]] bool isOpened() const override { return m_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. * @@ -64,19 +66,7 @@ namespace nexo::editor { [[nodiscard]] const WindowState &getWindowState() const override { return m_windowState; }; - /** - * @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 firstDockSetup(const std::string &windowName); - void visibilityCheck(); + WindowId windowId; protected: @@ -86,11 +76,34 @@ namespace nexo::editor { bool m_wasVisibleLastFrame; 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(); }; } diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 9c9d9a462..50e4c813e 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -178,7 +178,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); - firstDockSetup(NEXO_WND_USTRID_ASSET_MANAGER); + beginRender(NEXO_WND_USTRID_ASSET_MANAGER); drawMenuBar(); diff --git a/editor/src/DocumentWindows/ConsoleWindow/Show.cpp b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp index fd007bfb6..7aec9c0ea 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Show.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp @@ -103,7 +103,7 @@ namespace nexo::editor { { ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); ImGui::Begin(ICON_FA_FILE_TEXT " Console" NEXO_WND_USTRID_CONSOLE, &m_opened, ImGuiWindowFlags_NoCollapse); - firstDockSetup(NEXO_WND_USTRID_CONSOLE); + beginRender(NEXO_WND_USTRID_CONSOLE); const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerHeight), false, ImGuiWindowFlags_HorizontalScrollbar); diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp index 9c97b193a..3e0b4ab98 100644 --- a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -73,13 +73,6 @@ namespace nexo::editor { */ [[nodiscard]] scene::SceneId getSceneId() const {return m_sceneId;}; - /** - * @brief Gets the current viewport size. - * - * @return ImVec2 The width and height of the viewport in pixels. - */ - [[nodiscard]] ImVec2 getViewportSize() const {return m_viewSize;}; - /** * @brief Sets the active camera for this scene. * @@ -100,8 +93,6 @@ namespace nexo::editor { private: bool m_defaultScene = false; - ImVec2 m_viewSize = {0, 0}; - ImVec2 m_viewPosition = {0, 0}; ImVec2 m_viewportBounds[2]; ImGuizmo::OPERATION m_currentGizmoOperation = ImGuizmo::UNIVERSAL; ImGuizmo::MODE m_currentGizmoMode = ImGuizmo::WORLD; diff --git a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp index 90161ce1b..2b127837b 100644 --- a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp +++ b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp @@ -54,7 +54,7 @@ namespace nexo::editor { { ImGuizmo::SetOrthographic(camera.type == components::CameraType::ORTHOGRAPHIC); ImGuizmo::SetDrawlist(); - ImGuizmo::SetRect(m_viewPosition.x, m_viewPosition.y, m_viewSize.x, m_viewSize.y); + ImGuizmo::SetRect(m_windowPos.x, m_windowPos.y, m_contentSize.x, m_contentSize.y); ImGuizmo::Enable(true); } diff --git a/editor/src/DocumentWindows/EditorScene/Init.cpp b/editor/src/DocumentWindows/EditorScene/Init.cpp index 4c2895a2c..245c0e99d 100644 --- a/editor/src/DocumentWindows/EditorScene/Init.cpp +++ b/editor/src/DocumentWindows/EditorScene/Init.cpp @@ -36,10 +36,10 @@ namespace nexo::editor { framebufferSpecs.attachments = { renderer::FrameBufferTextureFormats::RGBA8, renderer::FrameBufferTextureFormats::RED_INTEGER, renderer::FrameBufferTextureFormats::Depth }; - framebufferSpecs.width = static_cast(m_viewSize.x); - framebufferSpecs.height = static_cast(m_viewSize.y); + framebufferSpecs.width = static_cast(m_contentSize.x); + framebufferSpecs.height = static_cast(m_contentSize.y); const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs); - m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget); + m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast(m_contentSize.x), static_cast(m_contentSize.y), renderTarget); auto &cameraComponent = app.m_coordinator->getComponent(m_editorCamera); cameraComponent.render = true; app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_editorCamera)); @@ -75,8 +75,7 @@ namespace nexo::editor { void EditorScene::setupWindow() { - constexpr auto size = ImVec2(1280, 720); - m_viewSize = size; + m_contentSize = ImVec2(1280, 720); } void EditorScene::setCamera(ecs::Entity cameraId) @@ -87,6 +86,6 @@ namespace nexo::editor { oldCameraComponent.render = false; m_activeCamera = cameraId; auto &newCameraComponent = app.m_coordinator->getComponent(cameraId); - newCameraComponent.resize(m_viewSize.x, m_viewSize.y); + newCameraComponent.resize(m_contentSize.x, m_contentSize.y); } } diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index 0e31fc0b7..ceca4fc1a 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -26,7 +26,7 @@ namespace nexo::editor { { // No active camera, render the text at the center of the screen ImVec2 textSize = ImGui::CalcTextSize("No active camera"); - auto textPos = ImVec2((m_viewSize.x - textSize.x) / 2, (m_viewSize.y - textSize.y) / 2); + auto textPos = ImVec2((m_contentSize.x - textSize.x) / 2, (m_contentSize.y - textSize.y) / 2); ImGui::SetCursorScreenPos(textPos); ImGui::Text("No active camera"); @@ -75,7 +75,7 @@ namespace nexo::editor { // --- Camera item --- if (ImGui::MenuItem("Camera")) { m_popupManager.openPopupWithCallback("Popup camera inspector", [this]() { - ImNexo::CameraInspector(this->m_sceneId, this->m_viewSize); + ImNexo::CameraInspector(this->m_sceneId, this->m_contentSize); }, ImVec2(1440,900)); } m_popupManager.closePopup(); @@ -85,21 +85,21 @@ namespace nexo::editor { { const auto viewPortOffset = ImGui::GetCursorPos(); auto &cameraComponent = Application::m_coordinator->getComponent(m_activeCamera); - const ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail(); + if (!cameraComponent.m_renderTarget) + return; + const glm::vec2 renderTargetSize = cameraComponent.m_renderTarget->getSize(); // Resize handling - if ((viewportPanelSize.x > 0 && viewportPanelSize.y > 0) && (m_viewSize.x != viewportPanelSize.x || m_viewSize.y != viewportPanelSize.y)) + if ((m_contentSize.x > 0 && m_contentSize.y > 0) && (m_contentSize.x != renderTargetSize.x || m_contentSize.y != renderTargetSize.y)) { - cameraComponent.resize(static_cast(viewportPanelSize.x), - static_cast(viewportPanelSize.y)); + cameraComponent.resize(static_cast(m_contentSize.x), + static_cast(m_contentSize.y)); - m_viewSize.x = viewportPanelSize.x; - m_viewSize.y = viewportPanelSize.y; } // Render framebuffer const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); - ImGui::Image(static_cast(static_cast(textureId)), m_viewSize, ImVec2(0, 1), ImVec2(1, 0)); + ImGui::Image(static_cast(static_cast(textureId)), m_contentSize, ImVec2(0, 1), ImVec2(1, 0)); const auto windowSize = ImGui::GetWindowSize(); auto minBounds = ImGui::GetWindowPos(); @@ -121,16 +121,11 @@ namespace nexo::editor { const std::string &sceneWindowName = m_windowName + std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId); m_wasVisibleLastFrame = m_isVisibleInDock; m_isVisibleInDock = false; - if (ImGui::Begin(sceneWindowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse)) + if (ImGui::Begin(sceneWindowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollWithMouse)) { - firstDockSetup(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId)); + beginRender(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId)); auto &app = getApp(); - // Add some spacing after the toolbar - ImGui::Dummy(ImVec2(0, 5)); - m_viewPosition = ImGui::GetCursorScreenPos(); - - visibilityCheck(); app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused); if (m_focused && selector.getSelectedScene() != m_sceneId) { diff --git a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp index 93caa7090..20c0608b2 100644 --- a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp +++ b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp @@ -22,12 +22,13 @@ namespace nexo::editor { void EditorScene::initialToolbarSetup(const float buttonWidth, const float buttonHeight) { - ImVec2 toolbarPos = m_viewPosition; + ImVec2 toolbarPos = m_windowPos; toolbarPos.x += 10.0f; + toolbarPos.y += 20.0f; ImGui::SetCursorScreenPos(toolbarPos); - ImVec2 toolbarSize = ImVec2(m_viewSize.x - buttonWidth, 50.0f); + ImVec2 toolbarSize = ImVec2(m_contentSize.x - buttonWidth, 50.0f); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.1f, 0.1f, 0.1f, 0.0f)); ImGui::BeginChild("##ToolbarOverlay", toolbarSize, 0, diff --git a/editor/src/DocumentWindows/EditorScene/Update.cpp b/editor/src/DocumentWindows/EditorScene/Update.cpp index a4d76ba78..8a4195bf1 100644 --- a/editor/src/DocumentWindows/EditorScene/Update.cpp +++ b/editor/src/DocumentWindows/EditorScene/Update.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "EditorScene.hpp" +#include "Types.hpp" #include "context/Selector.hpp" #include "components/Uuid.hpp" @@ -91,10 +92,10 @@ namespace nexo::editor { my -= m_viewportBounds[0].y; // Flip the y-coordinate to match opengl texture format - my = m_viewSize.y - my; + my = m_contentSize.y - my; // Check if mouse is inside viewport - if (!(mx >= 0 && my >= 0 && mx < m_viewSize.x && my < m_viewSize.y)) + if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) return; int entityId = sampleEntityTexture(mx, my); @@ -123,7 +124,8 @@ namespace nexo::editor { if (!m_opened || m_activeCamera == -1 || !is_currently_visible) return; SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; - runEngine(m_sceneId, RenderingType::FRAMEBUFFER, sceneType); + Application::SceneInfo sceneInfo{static_cast(m_sceneId), RenderingType::FRAMEBUFFER, sceneType}; + runEngine(sceneInfo); // Handle mouse clicks for selection diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index b9b9a4ee6..7f71224f5 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -72,7 +72,8 @@ namespace nexo::editor { ImGui::BeginChild("MaterialPreview", ImVec2(previewWidth - 4, totalHeight), true); auto &app = getApp(); - app.run(scenePreviewInfo.sceneId, RenderingType::FRAMEBUFFER); + Application::SceneInfo sceneInfo{static_cast(scenePreviewInfo.sceneId), nexo::RenderingType::FRAMEBUFFER}; + app.run(sceneInfo); auto const &cameraComponent = Application::m_coordinator->getComponent(scenePreviewInfo.cameraId); const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); @@ -167,7 +168,8 @@ namespace nexo::editor { utils::genScenePreview("Modify material inspector", {64, 64}, entity, previewParams); auto &app = nexo::getApp(); app.getSceneManager().getScene(previewParams.sceneId).setActiveStatus(false); - app.run(previewParams.sceneId, RenderingType::FRAMEBUFFER); + Application::SceneInfo sceneInfo{static_cast(previewParams.sceneId), nexo::RenderingType::FRAMEBUFFER}; + app.run(sceneInfo); const auto &cameraComponent = Application::m_coordinator->getComponent(previewParams.cameraId); framebuffer = cameraComponent.m_renderTarget; app.getSceneManager().deleteScene(previewParams.sceneId); diff --git a/editor/src/DocumentWindows/InspectorWindow/Show.cpp b/editor/src/DocumentWindows/InspectorWindow/Show.cpp index e0dbc0100..a617d643e 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Show.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Show.cpp @@ -71,7 +71,7 @@ namespace nexo::editor { void InspectorWindow::show() { ImGui::Begin(ICON_FA_SLIDERS " Inspector" NEXO_WND_USTRID_INSPECTOR, &m_opened, ImGuiWindowFlags_NoCollapse); - firstDockSetup(NEXO_WND_USTRID_INSPECTOR); + beginRender(NEXO_WND_USTRID_INSPECTOR); auto const &selector = Selector::get(); if (selector.getPrimarySelectionType() == SelectionType::SCENE) { diff --git a/editor/src/DocumentWindows/MaterialInspector/Show.cpp b/editor/src/DocumentWindows/MaterialInspector/Show.cpp index 78925b262..a4f74d312 100644 --- a/editor/src/DocumentWindows/MaterialInspector/Show.cpp +++ b/editor/src/DocumentWindows/MaterialInspector/Show.cpp @@ -46,7 +46,8 @@ namespace nexo::editor { { utils::genScenePreview("Modify material inspector", {64, 64}, m_ecsEntity, previewParams); auto &app = nexo::getApp(); - app.run(previewParams.sceneId, RenderingType::FRAMEBUFFER); + Application::SceneInfo sceneInfo{static_cast(previewParams.sceneId), nexo::RenderingType::FRAMEBUFFER}; + app.run(sceneInfo); const auto &cameraComponent = nexo::Application::m_coordinator->getComponent(previewParams.cameraId); m_framebuffer = cameraComponent.m_renderTarget; materialModified = false; @@ -83,8 +84,8 @@ namespace nexo::editor { if (ImGui::Begin("Material Inspector" NEXO_WND_USTRID_MATERIAL_INSPECTOR, &inspectorWindow->getSubInspectorVisibility(), window_flags)) { - firstDockSetup(NEXO_WND_USTRID_MATERIAL_INSPECTOR); - renderMaterialInspector(selectedEntity); + beginRender(NEXO_WND_USTRID_MATERIAL_INSPECTOR); + renderMaterialInspector(selectedEntity); } ImGui::End(); } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp index 6094a7a9d..c61efed26 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Init.cpp @@ -19,7 +19,6 @@ namespace nexo::editor { void SceneTreeWindow::setup() { setupShortcuts(); - // Nothing to setup } } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp index a979d566f..acf5aa22f 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -77,7 +77,7 @@ namespace nexo::editor { const auto &editorScenes = m_windowRegistry.getWindows(); for (const auto &scene : editorScenes) { if (scene->getSceneId() == sceneId) { - ImNexo::CameraInspector(sceneId, scene->getViewportSize()); + ImNexo::CameraInspector(sceneId, scene->getContentSize()); break; } } @@ -208,7 +208,7 @@ namespace nexo::editor { if (ImGui::Begin(ICON_FA_SITEMAP " Scene Tree" NEXO_WND_USTRID_SCENE_TREE, &m_opened, ImGuiWindowFlags_NoCollapse)) { - firstDockSetup(NEXO_WND_USTRID_SCENE_TREE); + beginRender(NEXO_WND_USTRID_SCENE_TREE); m_focused = ImGui::IsWindowFocused(); m_hovered = ImGui::IsWindowHovered(); diff --git a/editor/src/IDocumentWindow.hpp b/editor/src/IDocumentWindow.hpp index 1e8759a80..1e2f56616 100644 --- a/editor/src/IDocumentWindow.hpp +++ b/editor/src/IDocumentWindow.hpp @@ -34,6 +34,7 @@ namespace nexo::editor { [[nodiscard]] virtual bool isFocused() const = 0; [[nodiscard]] virtual bool isOpened() const = 0; [[nodiscard]] virtual bool isHovered() const = 0; + [[nodiscard]] virtual const ImVec2 &getContentSize() const = 0; [[nodiscard]] virtual bool &getOpened() = 0; [[nodiscard]] virtual const std::string &getWindowName() const = 0; [[nodiscard]] virtual const WindowState &getWindowState() const = 0; diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index 82af1efd2..338ccbe6d 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -302,7 +302,8 @@ namespace ImNexo { ImGui::BeginChild("CameraPreview", ImVec2(previewWidth - 4, totalHeight), true); auto &app = nexo::getApp(); - app.run(sceneId, nexo::RenderingType::FRAMEBUFFER); + nexo::Application::SceneInfo sceneInfo{static_cast(sceneId), nexo::RenderingType::FRAMEBUFFER}; + app.run(sceneInfo); auto const &cameraComponent = nexo::Application::m_coordinator->getComponent(camera); const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); From 9ccce7456eb52d3d320f11fd69329223aecb8891 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 18:57:51 +0900 Subject: [PATCH 237/450] fix(document-windows): now use the viewport bounds for the gizmo rect setup --- editor/src/DocumentWindows/EditorScene/Gizmo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp index 2b127837b..7ef1339f6 100644 --- a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp +++ b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp @@ -54,7 +54,7 @@ namespace nexo::editor { { ImGuizmo::SetOrthographic(camera.type == components::CameraType::ORTHOGRAPHIC); ImGuizmo::SetDrawlist(); - ImGuizmo::SetRect(m_windowPos.x, m_windowPos.y, m_contentSize.x, m_contentSize.y); + ImGuizmo::SetRect(m_viewportBounds[0].x, m_viewportBounds[0].y, m_contentSize.x, m_contentSize.y); ImGuizmo::Enable(true); } From 5ce7f827163e016f16ee79bae3cb84e05e5daabd Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 18:58:25 +0900 Subject: [PATCH 238/450] fix(document-windows): now properly retrieve the viewport bounds based on the size of the image drawn --- editor/src/DocumentWindows/EditorScene/Show.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index ceca4fc1a..4e40cdbc4 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -89,6 +89,7 @@ namespace nexo::editor { return; const glm::vec2 renderTargetSize = cameraComponent.m_renderTarget->getSize(); + // Resize handling if ((m_contentSize.x > 0 && m_contentSize.y > 0) && (m_contentSize.x != renderTargetSize.x || m_contentSize.y != renderTargetSize.y)) { @@ -101,15 +102,10 @@ namespace nexo::editor { const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); ImGui::Image(static_cast(static_cast(textureId)), m_contentSize, ImVec2(0, 1), ImVec2(1, 0)); - const auto windowSize = ImGui::GetWindowSize(); - auto minBounds = ImGui::GetWindowPos(); - - minBounds.x += viewPortOffset.x; - minBounds.y += viewPortOffset.y; - - const ImVec2 maxBounds = {minBounds.x + windowSize.x, minBounds.y + windowSize.y}; - m_viewportBounds[0] = minBounds; - m_viewportBounds[1] = maxBounds; + ImVec2 viewportMin = ImGui::GetItemRectMin(); + ImVec2 viewportMax = ImGui::GetItemRectMax(); + m_viewportBounds[0] = viewportMin; + m_viewportBounds[1] = viewportMax; } void EditorScene::show() From 56916f44f8407ccc532ae9c87fdc3b8dafcca413 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 18:59:17 +0900 Subject: [PATCH 239/450] fix(document-windows): now pass viewport bounds to the engine to properly handle the mouse projection --- editor/src/DocumentWindows/EditorScene/Update.cpp | 3 +++ engine/src/Application.cpp | 5 +++++ engine/src/Application.hpp | 5 ++--- engine/src/components/RenderContext.hpp | 5 +++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/Update.cpp b/editor/src/DocumentWindows/EditorScene/Update.cpp index 8a4195bf1..f913ea6db 100644 --- a/editor/src/DocumentWindows/EditorScene/Update.cpp +++ b/editor/src/DocumentWindows/EditorScene/Update.cpp @@ -125,6 +125,9 @@ namespace nexo::editor { return; SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; Application::SceneInfo sceneInfo{static_cast(m_sceneId), RenderingType::FRAMEBUFFER, sceneType}; + 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); diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index e40556ec9..f696c726a 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -251,6 +251,11 @@ namespace nexo { { renderContext.sceneRendered = sceneInfo.id; renderContext.sceneType = sceneInfo.sceneType; + if (sceneInfo.isChildWindow) { + renderContext.isChildWindow = true; + renderContext.viewportBounds[0] = sceneInfo.viewportBounds[0]; + renderContext.viewportBounds[1] = sceneInfo.viewportBounds[1]; + } if (m_SceneManager.getScene(sceneInfo.id).isRendered()) { m_cameraContextSystem->update(); diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index e27119ef3..920be35d0 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -80,9 +80,8 @@ namespace nexo { scene::SceneId id; RenderingType renderingType = RenderingType::WINDOW; SceneType sceneType = SceneType::GAME; - bool isChildWindow = false; - glm::vec2 childWindowPos{}; - glm::vec2 childWindowSize{}; + 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 }; /** diff --git a/engine/src/components/RenderContext.hpp b/engine/src/components/RenderContext.hpp index 2dc18e754..ccb11f2f3 100644 --- a/engine/src/components/RenderContext.hpp +++ b/engine/src/components/RenderContext.hpp @@ -23,6 +23,8 @@ namespace nexo::components { struct RenderContext { int sceneRendered = -1; SceneType sceneType; + 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; @@ -63,6 +65,9 @@ namespace nexo::components { void reset() { sceneRendered = -1; + isChildWindow = false; + viewportBounds[0] = glm::vec2{}; + viewportBounds[1] = glm::vec2{}; std::queue empty; std::swap(cameras, empty); sceneLights.ambientLight = glm::vec3(0.0f); From f3fcdb3323c3852fb6e98a38089c8c52fdc50ae7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 18:59:46 +0900 Subject: [PATCH 240/450] fix(document-windows): properly project the mouse position into the world for grid hightlight --- engine/src/systems/RenderSystem.cpp | 89 +++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 159ad58c0..06e50b5a7 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -23,6 +23,7 @@ #include "components/Render.hpp" #include "core/event/Input.hpp" #include "math/Projection.hpp" +#include "math/Vector.hpp" #include "renderer/RenderCommand.hpp" #include "ecs/Coordinator.hpp" #include "core/exceptions/Exceptions.hpp" @@ -133,38 +134,88 @@ namespace nexo::system { void RenderSystem::renderGrid(const components::CameraContext &camera, components::RenderContext &renderContext) { - //TODO: Implement a way to do this without relying on camera render target when none is bound if (!camera.renderTarget) return; + renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, "Grid shader"); auto gridShader = renderContext.renderer3D.getShader(); gridShader->bind(); // Grid appearance const components::RenderContext::GridParams &gridParams = renderContext.gridParams; - gridShader->setUniformFloat("uGridSize", gridParams.gridSize); // Size of grid from center to edge - gridShader->setUniformFloat("uGridCellSize", gridParams.cellSize); // Base size of each cell - gridShader->setUniformFloat("uGridMinPixelsBetweenCells", gridParams.minPixelsBetweenCells); // For LOD calculation - - gridShader->setUniformFloat4("uGridColorThin", {0.5f, 0.55f, 0.7f, 0.6f}); // Soft blue-purple - gridShader->setUniformFloat4("uGridColorThick", {0.7f, 0.75f, 0.9f, 0.8f}); // Lighter blue-purple - const glm::vec2 &mousePos = event::getMousePosition(); - std::cout << "mouse pos " << mousePos.x << " " << mousePos.y << std::endl; - const glm::vec2 &screenSize = camera.renderTarget->getSize(); - std::cout << "screen size " << screenSize.x << " " << screenSize.y << std::endl; - const glm::vec3 &rayDir = math::projectRayToWorld(mousePos.x, mousePos.y, camera.viewProjectionMatrix, camera.cameraPosition, screenSize.x, screenSize.y); - - glm::vec3 mouseWorldPos = camera.cameraPosition; - if (rayDir.y != 0.0f) { - float t = -camera.cameraPosition.y / rayDir.y; - if (t > 0.0f) { - mouseWorldPos = camera.cameraPosition + rayDir * t; + gridShader->setUniformFloat("uGridSize", gridParams.gridSize); + gridShader->setUniformFloat("uGridCellSize", gridParams.cellSize); + gridShader->setUniformFloat("uGridMinPixelsBetweenCells", gridParams.minPixelsBetweenCells); + + gridShader->setUniformFloat4("uGridColorThin", {0.5f, 0.55f, 0.7f, 0.6f}); + gridShader->setUniformFloat4("uGridColorThick", {0.7f, 0.75f, 0.9f, 0.8f}); + + 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 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 + ); + + // Convert to normalized coordinates [0,1] + 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 + ); + + // Project ray + const glm::vec3 rayDir = math::projectRayToWorld( + framebufferPos.x, framebufferPos.y, + camera.viewProjectionMatrix, camera.cameraPosition, + renderTargetSize.x, renderTargetSize.y + ); + + // Calculate intersection with y=0 plane (grid plane) + if (rayDir.y != 0.0f) { + float t = -camera.cameraPosition.y / rayDir.y; + if (t > 0.0f) { + mouseWorldPos = camera.cameraPosition + rayDir * t; + } + } + } + } else { + const glm::vec3 rayDir = math::projectRayToWorld( + globalMousePos.x, globalMousePos.y, + camera.viewProjectionMatrix, camera.cameraPosition, + renderTargetSize.x, renderTargetSize.y + ); + + if (rayDir.y != 0.0f) { + float t = -camera.cameraPosition.y / rayDir.y; + if (t > 0.0f) { + mouseWorldPos = camera.cameraPosition + rayDir * t; + } } } - // For glowing effect + + // Set uniforms for grid highlighting gridShader->setUniformFloat3("uMouseWorldPos", mouseWorldPos); gridShader->setUniformFloat("uTime", static_cast(glfwGetTime())); + // Render the grid renderer::RenderCommand::setDepthMask(false); glDisable(GL_CULL_FACE); renderer::RenderCommand::drawUnIndexed(6); From 7064c1930d61253a3056b48a8058810a349d499a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 19:00:16 +0900 Subject: [PATCH 241/450] feat(document-windows): add utils function to check if 2D pos is within bounding box --- common/math/Vector.cpp | 5 +++++ common/math/Vector.hpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/common/math/Vector.cpp b/common/math/Vector.cpp index c191a0d63..999a2e374 100644 --- a/common/math/Vector.cpp +++ b/common/math/Vector.cpp @@ -46,4 +46,9 @@ namespace nexo::math { return glm::degrees(euler); } + + bool isPosInBounds(const glm::vec2 pos, const glm::vec2 &min, const glm::vec2 &max) + { + return pos.x >= min.x && pos.x <= max.x && pos.y >= min.y && pos.y <= max.y; + } } diff --git a/common/math/Vector.hpp b/common/math/Vector.hpp index d856d377b..64bd05f02 100644 --- a/common/math/Vector.hpp +++ b/common/math/Vector.hpp @@ -44,4 +44,6 @@ namespace nexo::math { * when sinp approaches ±1. */ glm::vec3 customQuatToEuler(const glm::quat &q); + + bool isPosInBounds(const glm::vec2 pos, const glm::vec2 &min, const glm::vec2 &max); } From f184c4dadde499e8c0097010dd394cbb451b6c09 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 4 May 2025 22:12:27 +0900 Subject: [PATCH 242/450] fix(document-windows): add header include for msvc --- editor/src/ImNexo/Elements.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/ImNexo/Elements.cpp b/editor/src/ImNexo/Elements.cpp index c5cfff903..967aae448 100644 --- a/editor/src/ImNexo/Elements.cpp +++ b/editor/src/ImNexo/Elements.cpp @@ -17,6 +17,7 @@ #include "Utils.hpp" #include +#include namespace ImNexo { From e4984cc9739410f14897375a0688a3943d6ac53e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 26 Mar 2025 06:11:55 +0900 Subject: [PATCH 243/450] init(document-windows): init branch From ffb9a905ac2433556a8fa5c1276cb527f4ac329f Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 13 Apr 2025 21:58:02 +0900 Subject: [PATCH 244/450] feat(asset-ecs): implement ModelImporter class for 3D model asset handling --- .../src/DocumentWindows/AssetManager/Init.cpp | 2 +- engine/CMakeLists.txt | 1 + engine/src/assets/AssetImporterBase.hpp | 15 +- engine/src/assets/Assets/Model/Model.hpp | 11 +- .../src/assets/Assets/Model/ModelImporter.cpp | 200 ++++++++++++++++++ .../src/assets/Assets/Model/ModelImporter.hpp | 46 +--- 6 files changed, 228 insertions(+), 47 deletions(-) create mode 100644 engine/src/assets/Assets/Model/ModelImporter.cpp diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp index b4421035b..38e0611ed 100644 --- a/editor/src/DocumentWindows/AssetManager/Init.cpp +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -29,7 +29,7 @@ namespace nexo::editor { assets::AssetImporter importer; std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf"); assets::ImporterFileInput fileInput{path}; - auto assetRef9mn = importer.importAssetAuto(assets::AssetLocation("my_package::9mn@foo/bar/"), fileInput); + auto assetRef9mn = importer.importAsset(assets::AssetLocation("my_package::9mn@foo/bar/"), fileInput); } { assets::AssetImporter importer; diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 0b510fc41..08952e056 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -59,6 +59,7 @@ set(COMMON_SOURCES engine/src/assets/AssetCatalog.cpp engine/src/assets/AssetImporter.cpp engine/src/assets/AssetImporterContext.cpp + engine/src/assets/Assets/Model/ModelImporter.cpp ) # Add API-specific sources diff --git a/engine/src/assets/AssetImporterBase.hpp b/engine/src/assets/AssetImporterBase.hpp index 4177de4b7..b91084a25 100644 --- a/engine/src/assets/AssetImporterBase.hpp +++ b/engine/src/assets/AssetImporterBase.hpp @@ -19,6 +19,7 @@ #include "AssetImporterInput.hpp" #include +#include namespace nexo::assets { @@ -69,7 +70,19 @@ namespace nexo::assets { } } catch (const std::exception& e) { // Log the error - LOG(NEXO_ERROR, "Failed to import asset from file '{}': {}", ctx.location.getPath(), e.what()); + 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()); } } }; diff --git a/engine/src/assets/Assets/Model/Model.hpp b/engine/src/assets/Assets/Model/Model.hpp index 4a8db1b69..535f9728f 100644 --- a/engine/src/assets/Assets/Model/Model.hpp +++ b/engine/src/assets/Assets/Model/Model.hpp @@ -16,23 +16,16 @@ #include "assets/Asset.hpp" -#include -#include +#include namespace nexo::assets { - struct ModelData { - // Model data - // TODO: Implement model data - const aiScene *scene; - }; - /** * @class Model * * @brief Represents a 3D model asset. */ - class Model final : public Asset { + class Model final : public Asset { public: Model() = default; diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp new file mode 100644 index 000000000..46d7a2221 --- /dev/null +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -0,0 +1,200 @@ +//// ModelImporter.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: Guillaume HEIN +// Date: 10/04/2025 +// Description: Implementation file for the ModelImporter class +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ModelImporter.hpp" + +#include + +namespace nexo::assets { + + bool ModelImporter::canRead(const ImporterInputVariant& inputVariant) + { + std::string extension; + if (std::holds_alternative(inputVariant)) + extension = std::get(inputVariant).filePath.extension().string(); + if (std::holds_alternative(inputVariant)) { + const auto& mem = std::get(inputVariant); + if (mem.fileExtension) + extension = mem.fileExtension.value(); + } + const Assimp::Importer importer; + return importer.IsExtensionSupported(extension); + } + + void ModelImporter::importImpl(AssetImporterContext& ctx) + { + m_model = loadModel(ctx); + ctx.setMainAsset(m_model); + } + + Model* ModelImporter::loadModel(AssetImporterContext& ctx) + { + Model* model = new Model(); + + //m_model->setData(new components::Model()); + const auto param = ctx.getParameters(); + int flags = aiProcess_Triangulate + | aiProcess_FlipUVs + | aiProcess_GenNormals; + const aiScene* scene = nullptr; + if (std::holds_alternative(ctx.input)) + scene = m_importer.ReadFile(std::get(ctx.input).filePath.string(), flags); + if (std::holds_alternative(ctx.input)) { + auto [memoryData, fileExtension] = std::get(ctx.input); + scene = m_importer.ReadFileFromMemory(memoryData.data(), memoryData.size(), flags, fileExtension ? fileExtension->c_str() : nullptr); + } + if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { + //log error TODO: improve error handling in importers + delete model; + if (scene) + m_importer.FreeScene(); + throw core::LoadModelException(ctx.location.getFullLocation(), m_importer.GetErrorString()); + } + auto meshNode = processNode(ctx, scene->mRootNode, scene); + if (!meshNode) { + delete model; + throw core::LoadModelException(ctx.location.getFullLocation(), "Failed to process model node"); + } + model->setData(new components::Model(meshNode)); + return model; + } + + std::shared_ptr ModelImporter::processNode(AssetImporterContext& ctx, aiNode const* node, + const aiScene* scene) + { + static int nbNode = 0; + nbNode++; + auto meshNode = std::make_shared(); + + glm::mat4 nodeTransform = convertAssimpMatrixToGLM(node->mTransformation); + + meshNode->transform = nodeTransform; + + 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->mNumChildren; i++) + { + auto newNode = processNode(ctx, node->mChildren[i], scene); + if (newNode) + meshNode->children.push_back(newNode); + } + + return meshNode; + } + + components::Mesh ModelImporter::processMesh(AssetImporterContext& ctx, aiMesh* mesh, const aiScene* scene) + { + std::vector vertices; + std::vector indices; + vertices.reserve(mesh->mNumVertices); + + for (unsigned int i = 0; i < mesh->mNumVertices; i++) + { + renderer::Vertex vertex{}; + vertex.position = {mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z}; + + if (mesh->HasNormals()) { + vertex.normal = { mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z }; + } + + if (mesh->mTextureCoords[0]) + vertex.texCoord = {mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y}; + else + vertex.texCoord = {0.0f, 0.0f}; + + vertices.push_back(vertex); + } + + for (unsigned int i = 0; i < mesh->mNumFaces; i++) + { + aiFace face = mesh->mFaces[i]; + indices.insert(indices.end(), face.mIndices, face.mIndices + face.mNumIndices); + } + + aiMaterial const *material = scene->mMaterials[mesh->mMaterialIndex]; + + components::Material materialComponent; + + aiColor4D color; + if (material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { + 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 }; + } + + if (material->Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) { + materialComponent.emissiveColor = { color.r, color.g, color.b }; + } + + if (float roughness = 0.0f; material->Get(AI_MATKEY_SHININESS, roughness) == AI_SUCCESS) { + materialComponent.roughness = 1.0f - (roughness / 100.0f); // Convert glossiness to roughness + } + + // Load Metallic + if (float metallic = 0.0f; material->Get(AI_MATKEY_METALLIC_FACTOR, metallic) == AI_SUCCESS) { + materialComponent.metallic = metallic; + } + + if (float opacity = 1.0f; material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { + materialComponent.opacity = opacity; + } + + // Load Textures + std::filesystem::path modelPath(std::holds_alternative(ctx.input) ? + std::get(ctx.input).filePath : ""); + std::filesystem::path modelDirectory = modelPath.parent_path(); + + auto loadTexture = [&](aiTextureType type) -> std::shared_ptr { + aiString str; + if (material->GetTexture(type, 0, &str) == AI_SUCCESS) { + std::filesystem::path texturePath = modelDirectory / std::string(str.C_Str()); + return renderer::Texture2D::create(texturePath.string()); + } + return nullptr; + }; + + materialComponent.albedoTexture = loadTexture(aiTextureType_DIFFUSE); + 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); + + 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"); + + LOG(NEXO_INFO, "Loaded mesh {}", mesh->mName.data); + + return {mesh->mName.data, vertices, indices, materialComponent}; + } + + glm::mat4 ModelImporter::convertAssimpMatrixToGLM(const aiMatrix4x4& matrix) + { + return glm::mat4( + 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 b4b26f4ed..0bda27836 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.hpp +++ b/engine/src/assets/Assets/Model/ModelImporter.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "assets/AssetImporterBase.hpp" #include "assets/Assets/Model/Model.hpp" @@ -30,47 +31,20 @@ namespace nexo::assets { ModelImporter() = default; ~ModelImporter() override = default; - bool canRead(const ImporterInputVariant& inputVariant) override - { - std::string extension; - if (std::holds_alternative(inputVariant)) - extension = std::get(inputVariant).filePath.extension().string(); - if (std::holds_alternative(inputVariant)) { - const auto& mem = std::get(inputVariant); - if (mem.fileExtension) - extension = mem.fileExtension.value(); - } - const Assimp::Importer importer; - return importer.IsExtensionSupported(extension); - } + bool canRead(const ImporterInputVariant& inputVariant) override; - void importImpl(AssetImporterContext& ctx) override - { - m_model = new Model(); - m_model->setData(new ModelData()); - const auto param = ctx.getParameters(); - int flags = aiProcess_Triangulate - | aiProcess_FlipUVs - | aiProcess_GenNormals; - const aiScene* scene = nullptr; - if (std::holds_alternative(ctx.input)) - scene = m_importer.ReadFile(std::get(ctx.input).filePath.string(), flags); - if (std::holds_alternative(ctx.input)) { - auto memInput = std::get(ctx.input); - scene = m_importer.ReadFileFromMemory(memInput.memoryData.data(), memInput.memoryData.size(), flags, memInput.fileExtension ? memInput.fileExtension->c_str() : nullptr); - } - if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { - //log error TODO: improve error handling in importers - auto error = m_importer.GetErrorString(); - LOG(NEXO_ERROR, "Error while importing model: {}: {}", ctx.location.getPath(), error); - } - m_model->data->scene = scene; - ctx.setMainAsset(m_model); - } + void importImpl(AssetImporterContext& ctx) override; protected: Model *m_model = nullptr; //< Model being imported Assimp::Importer m_importer; //< Assimp importer instance + + private: + Model* loadModel(AssetImporterContext& ctx); + std::shared_ptr processNode(AssetImporterContext& ctx, aiNode const *node, const aiScene* scene); + components::Mesh processMesh(AssetImporterContext& ctx, aiMesh* mesh, const aiScene* scene); + static glm::mat4 convertAssimpMatrixToGLM(const aiMatrix4x4& matrix); + }; } // namespace nexo::assets From 4f4bec4e983f7bd981488bf262a3aa8b12d8330a Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 13 Apr 2025 21:58:50 +0900 Subject: [PATCH 245/450] feat(asset-ecs): add handling of textures with less than 3 channels in OpenGL --- .../src/renderer/opengl/OpenGlTexture2D.cpp | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index e0b59c892..bc76d19fd 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -97,15 +97,26 @@ namespace nexo::renderer { GLenum internalFormat = 0; GLenum dataFormat = 0; - if (channels == 4) { - internalFormat = GL_RGBA8; - dataFormat = GL_RGBA; - } else if (channels == 3) { - internalFormat = GL_RGB8; - dataFormat = GL_RGB; - } else { - stbi_image_free(data); - THROW_EXCEPTION(TextureUnsupportedFormat, "OPENGL", channels, debugPath); + switch (channels) { + [[likely]] case 4: + m_internalFormat = GL_RGBA8; + m_dataFormat = GL_RGBA; + break; + [[likely]] case 3: + m_internalFormat = GL_RGB8; + m_dataFormat = GL_RGB; + break; + case 2: + m_internalFormat = GL_RG8; + m_dataFormat = GL_RG; + break; + case 1: + m_internalFormat = GL_R8; + m_dataFormat = GL_RED; + break; + default: + stbi_image_free(data); + THROW_EXCEPTION(TextureUnsupportedFormat, "OPENGL", channels, debugPath); } m_internalFormat = internalFormat; From 818975fcdfc17f490a9113da41518cee0f3a573d Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 13 Apr 2025 22:29:09 +0900 Subject: [PATCH 246/450] refactor(asset-ecs): change Texture2D creation methods to return unique_ptr instead of shared_ptr --- engine/src/renderer/Texture.cpp | 16 +++++++++------- engine/src/renderer/Texture.hpp | 12 ++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/engine/src/renderer/Texture.cpp b/engine/src/renderer/Texture.cpp index 2c69e4137..6883b767a 100644 --- a/engine/src/renderer/Texture.cpp +++ b/engine/src/renderer/Texture.cpp @@ -15,32 +15,34 @@ #include "Texture.hpp" #include "Renderer.hpp" #include "renderer/RendererExceptions.hpp" + +#include + #ifdef GRAPHICS_API_OPENGL #include "opengl/OpenGlTexture2D.hpp" #endif namespace nexo::renderer { - - std::shared_ptr Texture2D::create(unsigned int width, unsigned int height) + std::unique_ptr Texture2D::create(unsigned int width, unsigned int height) { #ifdef GRAPHICS_API_OPENGL - return std::make_shared(width, height); + return std::make_unique(width, height); #endif THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); } - std::shared_ptr Texture2D::create(uint8_t* buffer, unsigned int len) + std::unique_ptr Texture2D::create(uint8_t* buffer, unsigned int len) { #ifdef GRAPHICS_API_OPENGL - return std::make_shared(buffer, len); + return std::make_unique(buffer, len); #endif THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); } - std::shared_ptr Texture2D::create(const std::string &path) + std::unique_ptr Texture2D::create(const std::string& path) { #ifdef GRAPHICS_API_OPENGL - return std::make_shared(path); + return std::make_unique(path); #endif THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); } diff --git a/engine/src/renderer/Texture.hpp b/engine/src/renderer/Texture.hpp index 9dc878c71..6b293a20d 100644 --- a/engine/src/renderer/Texture.hpp +++ b/engine/src/renderer/Texture.hpp @@ -62,14 +62,14 @@ namespace nexo::renderer { * * @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 `Texture2D` instance. + * @return A unique pointer to the created `Texture2D` instance. * * Example: * ```cpp * auto blankTexture = Texture2D::create(512, 512); * ``` */ - static std::shared_ptr create(unsigned int width, unsigned int height); + static std::unique_ptr create(unsigned int width, unsigned int height); /** * @brief Creates a 2D texture from file in memory. @@ -80,7 +80,7 @@ namespace nexo::renderer { * * @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 `Texture2D` instance. + * @return A unique pointer to the created `Texture2D` instance. * * Example: * ```cpp @@ -88,7 +88,7 @@ namespace nexo::renderer { * auto texture = Texture2D::create(imageData.data(), imageData.size()); * ``` */ - static std::shared_ptr create(uint8_t *buffer, unsigned int len); + static std::unique_ptr create(uint8_t* buffer, unsigned int len); /** * @brief Creates a 2D texture from an image file. @@ -98,14 +98,14 @@ namespace nexo::renderer { * for rendering after creation. * * @param path The file path to the texture image. - * @return A shared pointer to the created `Texture2D` instance. + * @return A unique pointer to the created `Texture2D` instance. * * Example: * ```cpp * auto texture = Texture2D::create("assets/textures/brick_wall.png"); * ``` */ - static std::shared_ptr create(const std::string &path); + static std::unique_ptr create(const std::string& path); }; } From 53a79048ceb98f3d73f184bc60a1344bc7082c3a Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 13 Apr 2025 22:43:08 +0900 Subject: [PATCH 247/450] refactor(asset-ecs): move implementation of TextureImporter to .cpp file --- engine/CMakeLists.txt | 1 + engine/src/assets/AssetImporter.cpp | 2 + .../src/assets/Assets/Model/ModelImporter.cpp | 12 +++- .../src/assets/Assets/Model/ModelImporter.hpp | 3 - .../assets/Assets/Texture/TextureImporter.cpp | 67 +++++++++++++++++++ .../assets/Assets/Texture/TextureImporter.hpp | 49 ++------------ 6 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 engine/src/assets/Assets/Texture/TextureImporter.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 08952e056..874db5738 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -60,6 +60,7 @@ set(COMMON_SOURCES engine/src/assets/AssetImporter.cpp engine/src/assets/AssetImporterContext.cpp engine/src/assets/Assets/Model/ModelImporter.cpp + engine/src/assets/Assets/Texture/TextureImporter.cpp ) # Add API-specific sources diff --git a/engine/src/assets/AssetImporter.cpp b/engine/src/assets/AssetImporter.cpp index 9c067d3bb..c05fb4840 100644 --- a/engine/src/assets/AssetImporter.cpp +++ b/engine/src/assets/AssetImporter.cpp @@ -16,7 +16,9 @@ #include "AssetImporterBase.hpp" #include "AssetCatalog.hpp" +#include "Assets/Model/Model.hpp" #include "Assets/Model/ModelImporter.hpp" +#include "Assets/Texture/Texture.hpp" #include "Assets/Texture/TextureImporter.hpp" namespace nexo::assets { diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index 46d7a2221..ce9a56f91 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -14,7 +14,17 @@ #include "ModelImporter.hpp" -#include +#include +#include +#include +#include +#include + +#include "assets/AssetImporterBase.hpp" +#include "assets/Assets/Model/Model.hpp" +#include "ModelParameters.hpp" + +#include "core/exceptions/Exceptions.hpp" namespace nexo::assets { diff --git a/engine/src/assets/Assets/Model/ModelImporter.hpp b/engine/src/assets/Assets/Model/ModelImporter.hpp index 0bda27836..b71094626 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.hpp +++ b/engine/src/assets/Assets/Model/ModelImporter.hpp @@ -14,15 +14,12 @@ #pragma once -#include #include #include -#include #include #include "assets/AssetImporterBase.hpp" #include "assets/Assets/Model/Model.hpp" -#include "ModelParameters.hpp" namespace nexo::assets { diff --git a/engine/src/assets/Assets/Texture/TextureImporter.cpp b/engine/src/assets/Assets/Texture/TextureImporter.cpp new file mode 100644 index 000000000..fcd6338f4 --- /dev/null +++ b/engine/src/assets/Assets/Texture/TextureImporter.cpp @@ -0,0 +1,67 @@ +//// TextureImporter.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: Guillaume HEIN +// Date: 13/04/2025 +// Description: Implementation file for the TextureImporter class +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "TextureImporter.hpp" + +#include +#include +#include "assets/AssetImporterBase.hpp" +#include "assets/Assets/Texture/Texture.hpp" +#include "TextureParameters.hpp" + +namespace nexo::assets { + + bool TextureImporter::canRead(const ImporterInputVariant& inputVariant) + { + if (std::holds_alternative(inputVariant)) + return canReadFile(std::get(inputVariant)); + if (std::holds_alternative(inputVariant)) { + return canReadMemory(std::get(inputVariant)); + } + return false; + } + + void TextureImporter::importImpl(AssetImporterContext& ctx) + { + auto asset = new Texture(); + std::shared_ptr rendererTexture; + if (std::holds_alternative(ctx.input)) + rendererTexture = renderer::Texture2D::create(std::get(ctx.input).filePath.string()); + else { + auto data = std::get(ctx.input).memoryData; + rendererTexture = renderer::Texture2D::create(data.data(), data.size()); + } + auto assetData = new TextureData(); + assetData->texture = rendererTexture; + asset->setData(assetData); + asset->m_metadata.id = boost::uuids::random_generator()(); + + ctx.setMainAsset(asset); + } + + bool TextureImporter::canReadMemory(const ImporterMemoryInput& input) + { + const int ok = stbi_info_from_memory(input.memoryData.data(), input.memoryData.size(), nullptr, nullptr, nullptr); + return ok; + } + + bool TextureImporter::canReadFile(const ImporterFileInput& input) + { + const int ok = stbi_info(input.filePath.string().c_str(), nullptr, nullptr, nullptr); + return ok; + } + +} // namespace nexo::assets diff --git a/engine/src/assets/Assets/Texture/TextureImporter.hpp b/engine/src/assets/Assets/Texture/TextureImporter.hpp index 4c94b5d4b..9b1b4aadb 100644 --- a/engine/src/assets/Assets/Texture/TextureImporter.hpp +++ b/engine/src/assets/Assets/Texture/TextureImporter.hpp @@ -14,11 +14,7 @@ #pragma once -#include -#include #include "assets/AssetImporterBase.hpp" -#include "assets/Assets/Texture/Texture.hpp" -#include "TextureParameters.hpp" namespace nexo::assets { @@ -27,48 +23,11 @@ namespace nexo::assets { TextureImporter() = default; ~TextureImporter() override = default; - bool canRead(const ImporterInputVariant& inputVariant) override - { - if (std::holds_alternative(inputVariant)) - return canReadFile(std::get(inputVariant)); - if (std::holds_alternative(inputVariant)) { - return canReadMemory(std::get(inputVariant)); - } - return false; - } - - void importImpl(AssetImporterContext& ctx) override - { - auto asset = new Texture(); - std::shared_ptr rendererTexture; - if (std::holds_alternative(ctx.input)) - rendererTexture = renderer::Texture2D::create(std::get(ctx.input).filePath.string()); - else { - auto data = std::get(ctx.input).memoryData; - rendererTexture = renderer::Texture2D::create(data.data(), data.size()); - } - auto assetData = new TextureData(); - assetData->texture = rendererTexture; - asset->setData(assetData); - asset->m_metadata.id = boost::uuids::random_generator()(); - - ctx.setMainAsset(asset); - } - + bool canRead(const ImporterInputVariant& inputVariant) override; + void importImpl(AssetImporterContext& ctx) override; protected: - - bool canReadMemory(const ImporterMemoryInput& input) - { - const int ok = stbi_info_from_memory(input.memoryData.data(), input.memoryData.size(), nullptr, nullptr, nullptr); - return ok; - } - - bool canReadFile(const ImporterFileInput& input) - { - const int ok = stbi_info(input.filePath.string().c_str(), nullptr, nullptr, nullptr); - return ok; - } - + static bool canReadMemory(const ImporterMemoryInput& input); + static bool canReadFile(const ImporterFileInput& input); }; } // namespace nexo::assets From e36795db6bcacb4312874377f39bd364a3862572 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 14 Apr 2025 01:01:08 +0900 Subject: [PATCH 248/450] fix(asset-ecs): compilation on tests, change texture variable declarations to use std::shared_ptr --- tests/renderer/Renderer3D.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/renderer/Renderer3D.test.cpp b/tests/renderer/Renderer3D.test.cpp index f62dc618f..35b026a3e 100644 --- a/tests/renderer/Renderer3D.test.cpp +++ b/tests/renderer/Renderer3D.test.cpp @@ -381,7 +381,7 @@ namespace nexo::renderer { {{ 0.0f, 0.5f, 0.0f}, {0.5f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, -1} }; std::vector indices = {0, 1, 2}; - auto texture = Texture2D::create(4, 4); + std::shared_ptr texture = Texture2D::create(4, 4); // Use an OpenGL query to count the number of triangles drawn GLuint query; From 2489a56cc4269868931b9d932c9ac97782235986 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 14 Apr 2025 03:14:37 +0900 Subject: [PATCH 249/450] Revert "refactor(asset-ecs): change Texture2D creation methods to return unique_ptr instead of shared_ptr" This reverts commit dffd8742eb65cf148cc2501de3dbb1426b05b25d. --- engine/src/renderer/Texture.cpp | 16 +++++++--------- engine/src/renderer/Texture.hpp | 12 ++++++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/engine/src/renderer/Texture.cpp b/engine/src/renderer/Texture.cpp index 6883b767a..2c69e4137 100644 --- a/engine/src/renderer/Texture.cpp +++ b/engine/src/renderer/Texture.cpp @@ -15,34 +15,32 @@ #include "Texture.hpp" #include "Renderer.hpp" #include "renderer/RendererExceptions.hpp" - -#include - #ifdef GRAPHICS_API_OPENGL #include "opengl/OpenGlTexture2D.hpp" #endif namespace nexo::renderer { - std::unique_ptr Texture2D::create(unsigned int width, unsigned int height) + + std::shared_ptr Texture2D::create(unsigned int width, unsigned int height) { #ifdef GRAPHICS_API_OPENGL - return std::make_unique(width, height); + return std::make_shared(width, height); #endif THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); } - std::unique_ptr Texture2D::create(uint8_t* buffer, unsigned int len) + std::shared_ptr Texture2D::create(uint8_t* buffer, unsigned int len) { #ifdef GRAPHICS_API_OPENGL - return std::make_unique(buffer, len); + return std::make_shared(buffer, len); #endif THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); } - std::unique_ptr Texture2D::create(const std::string& path) + std::shared_ptr Texture2D::create(const std::string &path) { #ifdef GRAPHICS_API_OPENGL - return std::make_unique(path); + return std::make_shared(path); #endif THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); } diff --git a/engine/src/renderer/Texture.hpp b/engine/src/renderer/Texture.hpp index 6b293a20d..9dc878c71 100644 --- a/engine/src/renderer/Texture.hpp +++ b/engine/src/renderer/Texture.hpp @@ -62,14 +62,14 @@ namespace nexo::renderer { * * @param width The width of the texture in pixels. * @param height The height of the texture in pixels. - * @return A unique pointer to the created `Texture2D` instance. + * @return A shared pointer to the created `Texture2D` instance. * * Example: * ```cpp * auto blankTexture = Texture2D::create(512, 512); * ``` */ - static std::unique_ptr create(unsigned int width, unsigned int height); + static std::shared_ptr create(unsigned int width, unsigned int height); /** * @brief Creates a 2D texture from file in memory. @@ -80,7 +80,7 @@ namespace nexo::renderer { * * @param buffer The memory buffer containing the texture image data. * @param len The length of the memory buffer in bytes. - * @return A unique pointer to the created `Texture2D` instance. + * @return A shared pointer to the created `Texture2D` instance. * * Example: * ```cpp @@ -88,7 +88,7 @@ namespace nexo::renderer { * auto texture = Texture2D::create(imageData.data(), imageData.size()); * ``` */ - static std::unique_ptr create(uint8_t* buffer, unsigned int len); + static std::shared_ptr create(uint8_t *buffer, unsigned int len); /** * @brief Creates a 2D texture from an image file. @@ -98,14 +98,14 @@ namespace nexo::renderer { * for rendering after creation. * * @param path The file path to the texture image. - * @return A unique pointer to the created `Texture2D` instance. + * @return A shared pointer to the created `Texture2D` instance. * * Example: * ```cpp * auto texture = Texture2D::create("assets/textures/brick_wall.png"); * ``` */ - static std::unique_ptr create(const std::string& path); + static std::shared_ptr create(const std::string &path); }; } From a3b421f67fed709d22c9f361acb11a7ca4162431 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 14 Apr 2025 03:14:56 +0900 Subject: [PATCH 250/450] Revert "fix(asset-ecs): compilation on tests, change texture variable declarations to use std::shared_ptr" This reverts commit d6241baa95b5c295d432a4bff6af9e8837525b1c. --- tests/renderer/Renderer3D.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/renderer/Renderer3D.test.cpp b/tests/renderer/Renderer3D.test.cpp index 35b026a3e..f62dc618f 100644 --- a/tests/renderer/Renderer3D.test.cpp +++ b/tests/renderer/Renderer3D.test.cpp @@ -381,7 +381,7 @@ namespace nexo::renderer { {{ 0.0f, 0.5f, 0.0f}, {0.5f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, -1} }; std::vector indices = {0, 1, 2}; - std::shared_ptr texture = Texture2D::create(4, 4); + auto texture = Texture2D::create(4, 4); // Use an OpenGL query to count the number of triangles drawn GLuint query; From 6d68fe2696e32112293e7b323f1edb41c8e5574f Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 14 Apr 2025 23:25:39 +0900 Subject: [PATCH 251/450] refactor(asset-ecs): remove unused functions and commented code from EntityFactory3D --- engine/src/EntityFactory3D.cpp | 147 +-------------------------------- engine/src/EntityFactory3D.hpp | 8 -- 2 files changed, 2 insertions(+), 153 deletions(-) diff --git a/engine/src/EntityFactory3D.cpp b/engine/src/EntityFactory3D.cpp index c721a3372..6cdfea6e8 100644 --- a/engine/src/EntityFactory3D.cpp +++ b/engine/src/EntityFactory3D.cpp @@ -67,7 +67,7 @@ namespace nexo { return newCube; } - ecs::Entity EntityFactory3D::createModel(const std::string &path, glm::vec3 pos, glm::vec3 size, glm::vec3 rotation) + /*ecs::Entity EntityFactory3D::createModel(const std::string &path, glm::vec3 pos, glm::vec3 size, glm::vec3 rotation) { components::TransformComponent transform{}; transform.pos = pos; @@ -85,7 +85,7 @@ namespace nexo { components::UuidComponent uuid; Application::m_coordinator->addComponent(newModel, uuid); return newModel; - } + }*/ ecs::Entity EntityFactory3D::createBillboard(const glm::vec3 &pos, const glm::vec3 &size, const glm::vec4 &color) { @@ -127,146 +127,3 @@ namespace nexo { return newBillboard; } } - -namespace nexo::utils { - glm::mat4 convertAssimpMatrixToGLM(const aiMatrix4x4 &matrix) - { - return glm::mat4( - 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 - ); - } - - components::Mesh processMesh(const std::string &path, aiMesh *mesh, const aiScene *scene) - { - std::vector vertices; - std::vector indices; - vertices.reserve(mesh->mNumVertices); - - for (unsigned int i = 0; i < mesh->mNumVertices; i++) - { - renderer::Vertex vertex{}; - vertex.position = {mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z}; - - if (mesh->HasNormals()) { - vertex.normal = { mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z }; - } - - if (mesh->mTextureCoords[0]) - vertex.texCoord = {mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y}; - else - vertex.texCoord = {0.0f, 0.0f}; - - vertices.push_back(vertex); - } - - for (unsigned int i = 0; i < mesh->mNumFaces; i++) - { - aiFace face = mesh->mFaces[i]; - indices.insert(indices.end(), face.mIndices, face.mIndices + face.mNumIndices); - } - - aiMaterial const *material = scene->mMaterials[mesh->mMaterialIndex]; - - components::Material materialComponent; - - aiColor4D color; - if (material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { - 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 }; - } - - if (material->Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) { - materialComponent.emissiveColor = { color.r, color.g, color.b }; - } - - float roughness = 0.0f; - if (material->Get(AI_MATKEY_SHININESS, roughness) == AI_SUCCESS) { - materialComponent.roughness = 1.0f - (roughness / 100.0f); // Convert glossiness to roughness - } - - // Load Metallic - float metallic = 0.0f; - if (material->Get(AI_MATKEY_METALLIC_FACTOR, metallic) == AI_SUCCESS) { - materialComponent.metallic = metallic; - } - - float opacity = 1.0f; - if (material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { - materialComponent.opacity = opacity; - } - - // Load Textures - std::filesystem::path modelPath(path); - std::filesystem::path modelDirectory = modelPath.parent_path(); - - auto loadTexture = [&](aiTextureType type) -> std::shared_ptr { - aiString str; - if (material->GetTexture(type, 0, &str) == AI_SUCCESS) { - std::filesystem::path texturePath = modelDirectory / std::string(str.C_Str()); - return renderer::Texture2D::create(texturePath.string()); - } - return nullptr; - }; - - materialComponent.albedoTexture = loadTexture(aiTextureType_DIFFUSE); - 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); - - 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"); - - LOG(NEXO_INFO, "Loaded mesh {}", mesh->mName.data); - - return {mesh->mName.data, vertices, indices, materialComponent}; - } - - - std::shared_ptr processNode(const std::string &path, aiNode const *node, const aiScene *scene) - { - static int nbNode = 0; - nbNode++; - auto meshNode = std::make_shared(); - - glm::mat4 nodeTransform = convertAssimpMatrixToGLM(node->mTransformation); - - meshNode->transform = nodeTransform; - - for (unsigned int i = 0; i < node->mNumMeshes; i++) - { - aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; - meshNode->meshes.push_back(processMesh(path, mesh, scene)); - } - - for (unsigned int i = 0; i < node->mNumChildren; i++) - { - auto newNode = processNode(path, node->mChildren[i], scene); - if (newNode) - meshNode->children.push_back(newNode); - } - - return meshNode; - } - - std::shared_ptr loadModel(const std::string &path) - { - Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( - path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace); - - if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) - THROW_EXCEPTION(core::LoadModelException, path, importer.GetErrorString()); - - return processNode(path, scene->mRootNode, scene); - } -} diff --git a/engine/src/EntityFactory3D.hpp b/engine/src/EntityFactory3D.hpp index 3378250e2..eb7ac3cbf 100644 --- a/engine/src/EntityFactory3D.hpp +++ b/engine/src/EntityFactory3D.hpp @@ -66,11 +66,3 @@ namespace nexo static ecs::Entity createBillboard(const glm::vec3 &pos, const glm::vec3 &size, const components::Material &material); }; } - -namespace nexo::utils -{ - std::shared_ptr loadModel(const std::string& path); - std::shared_ptr processNode(const std::string &path, aiNode const *node, const aiScene* scene); - components::Mesh processMesh(const std::string &path, aiMesh* mesh, const aiScene* scene); - glm::mat4 convertAssimpMatrixToGLM(const aiMatrix4x4& matrix); -} From 690ee4ea9bed32b675af198b407f4c421544b68a Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 14 Apr 2025 23:26:47 +0900 Subject: [PATCH 252/450] feat(asset-ecs): implement material and textures assets in ECS --- engine/src/assets/Asset.hpp | 2 + engine/src/assets/AssetCatalog.hpp | 21 ++ engine/src/assets/AssetImporterInput.hpp | 2 +- engine/src/assets/AssetRef.hpp | 2 + .../src/assets/Assets/Material/Material.hpp | 35 +++ engine/src/assets/Assets/Model/Model.hpp | 3 +- .../src/assets/Assets/Model/ModelImporter.cpp | 208 +++++++++++++----- .../src/assets/Assets/Model/ModelImporter.hpp | 13 +- engine/src/assets/Assets/Texture/Texture.hpp | 4 +- .../assets/Assets/Texture/TextureImporter.cpp | 6 +- engine/src/components/Render.hpp | 2 +- engine/src/components/Render3D.hpp | 13 +- engine/src/components/Shapes3D.hpp | 46 +++- 13 files changed, 274 insertions(+), 83 deletions(-) create mode 100644 engine/src/assets/Assets/Material/Material.hpp diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index d9d4f35e0..1224a0120 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -38,6 +38,7 @@ namespace nexo::assets { enum class AssetType { UNKNOWN, TEXTURE, + MATERIAL, MODEL, SOUND, MUSIC, @@ -56,6 +57,7 @@ namespace nexo::assets { constexpr const char *AssetTypeNames[] = { "UNKNOWN", "TEXTURE", + "MATERIAL", "MODEL", "SOUND", "MUSIC", diff --git a/engine/src/assets/AssetCatalog.hpp b/engine/src/assets/AssetCatalog.hpp index 1cb7424e7..6f2f73c48 100644 --- a/engine/src/assets/AssetCatalog.hpp +++ b/engine/src/assets/AssetCatalog.hpp @@ -150,6 +150,27 @@ namespace nexo::assets { * @return GenericAssetRef A reference to the registered asset, or a null reference if the asset pointer was null. */ GenericAssetRef registerAsset(const AssetLocation& location, IAsset *asset); + + /** + * @brief Creates and registers a new asset in the catalog. + * + * This method creates a new asset of the specified type, registers it in the catalog, and returns a reference to it. + * + * @tparam AssetType The type of asset to create. Must be derived from IAsset. + * @param location The asset's location metadata. + * @param data Optional data to load the asset. Default is nullptr. + * @return AssetRef A reference to the created and registered asset. + */ + template + requires std::derived_from + AssetRef createAsset(const AssetLocation& location, void *data = nullptr) + { + auto asset = new AssetType(); + asset->setRawData(data); + auto assetRef = registerAsset(location, asset); + return assetRef.template as(); + } + private: std::unordered_map> m_assets; }; diff --git a/engine/src/assets/AssetImporterInput.hpp b/engine/src/assets/AssetImporterInput.hpp index 36b21bf83..a3e641cce 100644 --- a/engine/src/assets/AssetImporterInput.hpp +++ b/engine/src/assets/AssetImporterInput.hpp @@ -28,7 +28,7 @@ namespace nexo::assets { // Import from memory buffer, importer should read from the buffer struct ImporterMemoryInput { std::vector memoryData; //< Memory buffer - std::optional fileExtension; //< For format detection with memory sources (MUST start with a dot, e.g.: .png) + std::string formatHint; //< For format detection with memory sources (can be ARGB8888, .obj, etc.) }; using ImporterInputVariant = std::variant; diff --git a/engine/src/assets/AssetRef.hpp b/engine/src/assets/AssetRef.hpp index 5ed27c32e..a02985d08 100644 --- a/engine/src/assets/AssetRef.hpp +++ b/engine/src/assets/AssetRef.hpp @@ -146,6 +146,8 @@ namespace nexo::assets { explicit AssetRef(const std::shared_ptr& assetPtr) : GenericAssetRef(assetPtr) {} + explicit(false) AssetRef(std::nullptr_t) : GenericAssetRef(nullptr) {} + /** * @brief Locks the asset reference, providing safe access * @return A shared_ptr to the asset, or empty shared_ptr if expired diff --git a/engine/src/assets/Assets/Material/Material.hpp b/engine/src/assets/Assets/Material/Material.hpp new file mode 100644 index 000000000..3e9d3e13f --- /dev/null +++ b/engine/src/assets/Assets/Material/Material.hpp @@ -0,0 +1,35 @@ +//// Material.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: Guillaume HEIN +// Date: 24/02/2025 +// Description: Header file for the Material class +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "assets/Asset.hpp" + +#include + +namespace nexo::assets { + + /** + * @class Material + * + * @brief Represents a material asset. + */ + class Material final : public Asset { + public: + Material() = default; + + ~Material() override = default; + }; + +} diff --git a/engine/src/assets/Assets/Model/Model.hpp b/engine/src/assets/Assets/Model/Model.hpp index 535f9728f..15625ba7d 100644 --- a/engine/src/assets/Assets/Model/Model.hpp +++ b/engine/src/assets/Assets/Model/Model.hpp @@ -16,7 +16,8 @@ #include "assets/Asset.hpp" -#include +#include "components/Shapes3D.hpp" +#include "assets/Assets/Material/Material.hpp" namespace nexo::assets { diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index ce9a56f91..0254ff475 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -15,10 +15,14 @@ #include "ModelImporter.hpp" #include +#include + #include #include #include -#include + +#include "components/Shapes3D.hpp" +#include "Path.hpp" #include "assets/AssetImporterBase.hpp" #include "assets/Assets/Model/Model.hpp" @@ -35,8 +39,7 @@ namespace nexo::assets { extension = std::get(inputVariant).filePath.extension().string(); if (std::holds_alternative(inputVariant)) { const auto& mem = std::get(inputVariant); - if (mem.fileExtension) - extension = mem.fileExtension.value(); + extension = mem.formatHint; } const Assimp::Importer importer; return importer.IsExtensionSupported(extension); @@ -61,8 +64,8 @@ namespace nexo::assets { if (std::holds_alternative(ctx.input)) scene = m_importer.ReadFile(std::get(ctx.input).filePath.string(), flags); if (std::holds_alternative(ctx.input)) { - auto [memoryData, fileExtension] = std::get(ctx.input); - scene = m_importer.ReadFileFromMemory(memoryData.data(), memoryData.size(), flags, fileExtension ? fileExtension->c_str() : nullptr); + auto& [memoryData, formatHint] = std::get(ctx.input); + 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 @@ -71,6 +74,10 @@ namespace nexo::assets { m_importer.FreeScene(); throw core::LoadModelException(ctx.location.getFullLocation(), m_importer.GetErrorString()); } + + loadEmbeddedTextures(ctx, scene); + loadMaterials(ctx, scene); + auto meshNode = processNode(ctx, scene->mRootNode, scene); if (!meshNode) { delete model; @@ -80,11 +87,138 @@ namespace nexo::assets { return model; } + void ModelImporter::loadEmbeddedTextures(AssetImporterContext& ctx, const aiScene* scene) + { + m_textures.reserve(scene->mNumTextures); + // Load embedded textures + for (int i = 0; scene->mNumTextures; ++i) { + aiTexture *texture = scene->mTextures[i]; + AssetImporter assetImporter; + ImporterInputVariant inputVariant; + if (texture->mHeight == 0) { // Compressed texture + 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) + }; + } else { // Uncompressed texture + // TODO: implement RGBA8888 format + /*inputVariant = ImporterMemoryInput{ + .memoryData = std::vector(texture->pcData, texture->pcData + (texture->mWidth * texture->mHeight * 4)), + .formatHint = std::string(texture->achFormatHint) + };*/ + return; + } + + auto assetTexture = assetImporter.importAsset( + ctx.genUniqueDependencyLocation(), + inputVariant); + m_textures.emplace(texture->mFilename.C_Str(), assetTexture); + } + + } + + void ModelImporter::loadMaterials(AssetImporterContext& ctx, const aiScene* scene) + { + m_materials.assign(scene->mNumMaterials, nullptr); + + std::filesystem::path modelPath; + if (std::holds_alternative(ctx.input)) + 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())); + } + std::filesystem::path modelDirectory = modelPath.parent_path(); + + for (int matIdx = 0; matIdx < scene->mNumMaterials; ++matIdx) { + aiMaterial const *material = scene->mMaterials[matIdx]; + + const auto materialComponent = new components::Material(); + + aiColor4D color; + if (material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { + 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 }; + } + + if (material->Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) { + materialComponent->emissiveColor = { color.r, color.g, color.b }; + } + + if (float roughness = 0.0f; material->Get(AI_MATKEY_SHININESS, roughness) == AI_SUCCESS) { + materialComponent->roughness = 1.0f - (roughness / 100.0f); // Convert glossiness to roughness + } + + // Load Metallic + if (float metallic = 0.0f; material->Get(AI_MATKEY_METALLIC_FACTOR, metallic) == AI_SUCCESS) { + materialComponent->metallic = metallic; + } + + if (float opacity = 1.0f; material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { + materialComponent->opacity = opacity; + } + + // 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); + } + + aiString aiStr; + if (material->GetTexture(type, 0, &aiStr) == AI_SUCCESS) { + const char* cStr = aiStr.C_Str(); + if (cStr[0] == '*') { + // Embedded texture + if (const auto it = m_textures.find(cStr) ; it != m_textures.end()) { + return it->second; + } + } + std::filesystem::path texturePath = (modelDirectory / cStr).lexically_normal(); + auto texturePathStr = texturePath.string(); + if (const auto it = m_textures.find(texturePathStr.c_str()) ; it != m_textures.end()) { + return it->second; + } + AssetImporter assetImporter; + ImporterInputVariant inputVariant = ImporterFileInput{ + .filePath = texturePath + }; + auto assetTexture = assetImporter.importAsset( + ctx.genUniqueDependencyLocation(), + inputVariant); + m_textures.emplace(texturePathStr.c_str(), assetTexture); + return assetTexture; + } + return nullptr; + }; + + materialComponent->albedoTexture = loadTexture(aiTextureType_DIFFUSE); + 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); + + 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"); + + AssetCatalog::getInstance().createAsset( + ctx.genUniqueDependencyLocation(), + materialComponent + ); + } // end for (int matIdx = 0; matIdx < scene->mNumMaterials; ++matIdx) + } + std::shared_ptr ModelImporter::processNode(AssetImporterContext& ctx, aiNode const* node, const aiScene* scene) { - static int nbNode = 0; - nbNode++; auto meshNode = std::make_shared(); glm::mat4 nodeTransform = convertAssimpMatrixToGLM(node->mTransformation); @@ -136,64 +270,18 @@ namespace nexo::assets { indices.insert(indices.end(), face.mIndices, face.mIndices + face.mNumIndices); } - aiMaterial const *material = scene->mMaterials[mesh->mMaterialIndex]; - - components::Material materialComponent; - - aiColor4D color; - if (material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { - 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 }; + AssetRef materialComponent = nullptr; + 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); } - - if (material->Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) { - materialComponent.emissiveColor = { color.r, color.g, color.b }; + if (!materialComponent) { + LOG(NEXO_WARN, "ModelImporter: Model {}: Mesh {} has no material.", std::quoted(ctx.location.getFullLocation()), std::quoted(mesh->mName.C_Str())); } - if (float roughness = 0.0f; material->Get(AI_MATKEY_SHININESS, roughness) == AI_SUCCESS) { - materialComponent.roughness = 1.0f - (roughness / 100.0f); // Convert glossiness to roughness - } - - // Load Metallic - if (float metallic = 0.0f; material->Get(AI_MATKEY_METALLIC_FACTOR, metallic) == AI_SUCCESS) { - materialComponent.metallic = metallic; - } - - if (float opacity = 1.0f; material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { - materialComponent.opacity = opacity; - } - - // Load Textures - std::filesystem::path modelPath(std::holds_alternative(ctx.input) ? - std::get(ctx.input).filePath : ""); - std::filesystem::path modelDirectory = modelPath.parent_path(); - - auto loadTexture = [&](aiTextureType type) -> std::shared_ptr { - aiString str; - if (material->GetTexture(type, 0, &str) == AI_SUCCESS) { - std::filesystem::path texturePath = modelDirectory / std::string(str.C_Str()); - return renderer::Texture2D::create(texturePath.string()); - } - return nullptr; - }; - - materialComponent.albedoTexture = loadTexture(aiTextureType_DIFFUSE); - 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); - - 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"); - LOG(NEXO_INFO, "Loaded mesh {}", mesh->mName.data); - return {mesh->mName.data, vertices, indices, materialComponent}; } diff --git a/engine/src/assets/Assets/Model/ModelImporter.hpp b/engine/src/assets/Assets/Model/ModelImporter.hpp index b71094626..31ebcd21d 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.hpp +++ b/engine/src/assets/Assets/Model/ModelImporter.hpp @@ -20,6 +20,7 @@ #include "assets/AssetImporterBase.hpp" #include "assets/Assets/Model/Model.hpp" +#include "assets/AssetRef.hpp" namespace nexo::assets { @@ -33,15 +34,19 @@ namespace nexo::assets { void importImpl(AssetImporterContext& ctx) override; protected: - Model *m_model = nullptr; //< Model being imported - Assimp::Importer m_importer; //< Assimp importer instance - - private: Model* loadModel(AssetImporterContext& ctx); + void loadEmbeddedTextures(AssetImporterContext& ctx, const aiScene* scene); + void loadMaterials(AssetImporterContext& ctx, const aiScene* scene); + std::shared_ptr processNode(AssetImporterContext& ctx, aiNode const *node, const aiScene* scene); components::Mesh processMesh(AssetImporterContext& ctx, aiMesh* mesh, const aiScene* scene); + static glm::mat4 convertAssimpMatrixToGLM(const aiMatrix4x4& matrix); + Model *m_model = nullptr; //< Model being imported + 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/Texture/Texture.hpp b/engine/src/assets/Assets/Texture/Texture.hpp index 731b0f7e3..9f1b826b0 100644 --- a/engine/src/assets/Assets/Texture/Texture.hpp +++ b/engine/src/assets/Assets/Texture/Texture.hpp @@ -19,9 +19,7 @@ namespace nexo::assets { struct TextureData { - // Texture data - // TODO: Implement texture data - std::shared_ptr texture; + std::shared_ptr texture; }; /** diff --git a/engine/src/assets/Assets/Texture/TextureImporter.cpp b/engine/src/assets/Assets/Texture/TextureImporter.cpp index fcd6338f4..29cfcd651 100644 --- a/engine/src/assets/Assets/Texture/TextureImporter.cpp +++ b/engine/src/assets/Assets/Texture/TextureImporter.cpp @@ -36,8 +36,9 @@ namespace nexo::assets { void TextureImporter::importImpl(AssetImporterContext& ctx) { + // TODO: we need to import textures independently from graphics API back end renderer::Texture2D::create implementation auto asset = new Texture(); - std::shared_ptr rendererTexture; + std::shared_ptr rendererTexture; if (std::holds_alternative(ctx.input)) rendererTexture = renderer::Texture2D::create(std::get(ctx.input).filePath.string()); else { @@ -54,6 +55,9 @@ namespace nexo::assets { bool TextureImporter::canReadMemory(const ImporterMemoryInput& input) { + 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(), input.memoryData.size(), nullptr, nullptr, nullptr); return ok; } diff --git a/engine/src/components/Render.hpp b/engine/src/components/Render.hpp index ebeb2feb3..a25436e98 100644 --- a/engine/src/components/Render.hpp +++ b/engine/src/components/Render.hpp @@ -66,7 +66,7 @@ namespace nexo::components }; struct Renderable3D final : Renderable { - Material material; + Material material; // TODO: replace with AssetRef std::shared_ptr shape; explicit Renderable3D(Material material, diff --git a/engine/src/components/Render3D.hpp b/engine/src/components/Render3D.hpp index 7a0708122..494f13283 100644 --- a/engine/src/components/Render3D.hpp +++ b/engine/src/components/Render3D.hpp @@ -18,6 +18,9 @@ #include #include +#include "assets/AssetRef.hpp" +#include "assets/Assets/Texture/Texture.hpp" + namespace nexo::components { struct Material { @@ -31,11 +34,11 @@ namespace nexo::components { 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; + assets::AssetRef albedoTexture = nullptr; + assets::AssetRef normalMap = nullptr; + assets::AssetRef metallicMap = nullptr; + assets::AssetRef roughnessMap = nullptr; + assets::AssetRef emissiveMap = nullptr; std::string shader = ""; }; diff --git a/engine/src/components/Shapes3D.hpp b/engine/src/components/Shapes3D.hpp index 93f4e3174..1fc971d79 100644 --- a/engine/src/components/Shapes3D.hpp +++ b/engine/src/components/Shapes3D.hpp @@ -13,14 +13,15 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "Render3D.hpp" -#include "Transform.hpp" -#include "renderer/RendererContext.hpp" #define GLM_ENABLE_EXPERIMENTAL #include #include #include -#include + +#include "Render3D.hpp" +#include "Transform.hpp" +#include "renderer/RendererContext.hpp" +#include "assets/Assets/Material/Material.hpp" namespace nexo::components { @@ -35,7 +36,31 @@ namespace nexo::components { void draw(std::shared_ptr &context, const TransformComponent &transf, const Material &material, const int entityID) override { const auto renderer3D = context->renderer3D; - renderer3D.drawCube(transf.pos, transf.size, transf.quat, material, entityID); + + // lock all textures + auto albedoTextureAsset = material.albedoTexture.lock(); + auto normalMapAsset = material.normalMap.lock(); + auto metallicMapAsset = material.metallicMap.lock(); + auto roughnessMapAsset = material.roughnessMap.lock(); + auto emissiveMapAsset = material.emissiveMap.lock(); + + + renderer::Material inputMaterial = { + .albedoColor = material.albedoColor, + .specularColor = material.specularColor, + .emissiveColor = material.emissiveColor, + .roughness = material.roughness, + .metallic = material.metallic, + .opacity = material.opacity, + .albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->data->texture : nullptr, + .normalMap = normalMapAsset && normalMapAsset->isLoaded() ? normalMapAsset->data->texture : nullptr, + .metallicMap = metallicMapAsset && metallicMapAsset->isLoaded() ? metallicMapAsset->data->texture : nullptr, + .roughnessMap = roughnessMapAsset && roughnessMapAsset->isLoaded() ? roughnessMapAsset->data->texture : nullptr, + .emissiveMap = emissiveMapAsset && emissiveMapAsset->isLoaded() ? emissiveMapAsset->data->texture : nullptr, + .shader = material.shader + }; + + renderer3D.drawCube(transf.pos, transf.size, transf.quat, inputMaterial, entityID); } [[nodiscard]] std::shared_ptr clone() const override { @@ -47,7 +72,7 @@ namespace nexo::components { std::string name; std::vector vertices; std::vector indices; - std::optional material; + assets::AssetRef material; }; struct MeshNode { @@ -64,7 +89,14 @@ namespace nexo::components { std::vector transformedVertices = mesh.vertices; for (auto &vertex: transformedVertices) vertex.position = glm::vec3(localTransform * glm::vec4(vertex.position, 1.0f)); - renderer3D.drawMesh(transformedVertices, mesh.indices, mesh.material->albedoTexture, entityID); + + { + const auto meshMaterialAsset = mesh.material.lock(); + const auto albedoTextureAsset = meshMaterialAsset && meshMaterialAsset->isLoaded() ? meshMaterialAsset->data->albedoTexture.lock() : nullptr; + + const auto albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->data->texture : nullptr; + renderer3D.drawMesh(transformedVertices, mesh.indices, albedoTexture, entityID); + } } for (const auto &child: children) From 8084638a6864ad3c9ac7315c0bf460883e329937 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 14 Apr 2025 23:27:23 +0900 Subject: [PATCH 253/450] refactor(asset-ecs): rename Material to InternalMaterial and update related methods in renderer for independency --- engine/src/renderer/Renderer3D.cpp | 2 +- engine/src/renderer/Renderer3D.hpp | 30 ++++++++++++++++++++----- engine/src/renderer/primitives/Cube.cpp | 22 +++++++++--------- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index ffed09b30..a7aca6967 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -186,7 +186,7 @@ namespace nexo::renderer { return textureIndex; } - void Renderer3D::setMaterialUniforms(const renderer::Material& material) const + void Renderer3D::setMaterialUniforms(const renderer::InternalMaterial& material) const { if (!m_storage) THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_3D); diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index aabc3027a..1ac8f8562 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -34,7 +34,7 @@ namespace nexo::renderer { int entityID; }; - struct Material { + struct InternalMaterial { glm::vec4 albedoColor = glm::vec4(1.0f); int albedoTexIndex = 0; // Default: 0 (white texture) glm::vec4 specularColor = glm::vec4(1.0f); @@ -49,6 +49,24 @@ namespace nexo::renderer { int opacityTexIndex = 0; // Default: 0 (white texture) }; + struct Material { + 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 + + 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::optional> shader = std::nullopt; + }; + //TODO: Add stats for the meshes struct Renderer3DStats { unsigned int drawCalls = 0; @@ -244,7 +262,7 @@ namespace nexo::renderer { * * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ - void drawCube(const glm::vec3& position, const glm::vec3& size, const components::Material &material, int entityID = -1) const; + void drawCube(const glm::vec3& position, const glm::vec3& size, const Material& material, int entityID = -1) const; /** * @brief Draws a cube using a specified transformation and material. @@ -260,7 +278,7 @@ namespace nexo::renderer { * * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ - void drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const components::Material &material, int entityID = -1) const; + void drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const Material& material, int entityID = -1) const; /** * @brief Draws a cube using a specified transformation and material. @@ -276,7 +294,7 @@ namespace nexo::renderer { * * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ - void drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const components::Material &material, int entityID = -1) const; + void drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const Material& material, int entityID = -1) const; /** * @brief Draws a cube using a specified transformation and color. @@ -290,7 +308,7 @@ namespace nexo::renderer { * * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ - void drawCube(const glm::mat4& transform, const components::Material &material, int entityID = -1) const; + void drawCube(const glm::mat4& transform, const Material& material, int entityID = -1) const; /** * @brief Draws a custom 3D mesh. @@ -371,7 +389,7 @@ namespace nexo::renderer { * * @throws RendererNotInitialized if the renderer is not initialized. */ - void setMaterialUniforms(const renderer::Material& material) const; + void setMaterialUniforms(const renderer::InternalMaterial& material) const; }; } diff --git a/engine/src/renderer/primitives/Cube.cpp b/engine/src/renderer/primitives/Cube.cpp index 1e8d8ac3b..cb2e0691e 100644 --- a/engine/src/renderer/primitives/Cube.cpp +++ b/engine/src/renderer/primitives/Cube.cpp @@ -130,7 +130,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::Material mat; + renderer::InternalMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -181,7 +181,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::Material mat; + renderer::InternalMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -226,7 +226,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::Material mat; + renderer::InternalMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -260,7 +260,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const components::Material &material, int entityID) const + void Renderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const Material& material, int entityID) const { if (!m_renderingScene) { @@ -274,7 +274,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::Material mat; + renderer::InternalMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; @@ -312,7 +312,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const components::Material &material, int entityID) const + void Renderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const Material& material, int entityID) const { if (!m_renderingScene) { @@ -329,7 +329,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::Material mat; + renderer::InternalMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; @@ -367,7 +367,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const components::Material &material, int entityID) const + void Renderer3D::drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const Material& material, int entityID) const { if (!m_renderingScene) { @@ -384,7 +384,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::Material mat; + renderer::InternalMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; @@ -422,7 +422,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::mat4& transform, const components::Material &material, int entityID) const + void Renderer3D::drawCube(const glm::mat4& transform, const Material& material, int entityID) const { if (!m_renderingScene) { @@ -432,7 +432,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::Material mat; + renderer::InternalMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; From 39e450f3fa79f8d76fab273ad139fb2bda0e09be Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 15 Apr 2025 00:48:42 +0900 Subject: [PATCH 254/450] refactor(renderer): update Material references to use renderer::Material in Renderer3D --- engine/src/renderer/Renderer3D.cpp | 12 ++++++------ engine/src/renderer/Renderer3D.hpp | 7 ++++--- engine/src/renderer/primitives/Mesh.cpp | 6 +++--- tests/renderer/Renderer3D.test.cpp | 6 +++--- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index a7aca6967..942014801 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -12,17 +12,17 @@ // /////////////////////////////////////////////////////////////////////////////// +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + #include "Renderer3D.hpp" #include "RenderCommand.hpp" #include "Logger.hpp" #include "Shader.hpp" #include "renderer/RendererExceptions.hpp" - -#include -#define GLM_ENABLE_EXPERIMENTAL -#include -#include -#include +#include "Path.hpp" namespace nexo::renderer { diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index 1ac8f8562..45f3c9b7d 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -21,6 +21,7 @@ #include #include +#include namespace nexo::renderer { @@ -324,9 +325,9 @@ namespace nexo::renderer { */ void drawMesh(const std::vector& vertices, const std::vector& indices, const std::shared_ptr& texture, int entityID = -1) const; - void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& size, const components::Material& material, int entityID = -1) const; - void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& size, const components::Material& material, int entityID = -1) const; - void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::mat4& transform, const components::Material& material, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& size, const renderer::Material& material, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& size, const renderer::Material& material, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::mat4& transform, const renderer::Material& material, int entityID = -1) const; void drawBillboard(const glm::vec3& position, const glm::vec2& size, const glm::vec4& color, int entityID) const; void drawBillboard(const glm::vec3& position, const glm::vec2& size, const components::Material& material, int entityID) const; diff --git a/engine/src/renderer/primitives/Mesh.cpp b/engine/src/renderer/primitives/Mesh.cpp index 362cb9cfe..3e601ab5b 100644 --- a/engine/src/renderer/primitives/Mesh.cpp +++ b/engine/src/renderer/primitives/Mesh.cpp @@ -18,17 +18,17 @@ namespace nexo::renderer { - void Renderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::vec3& position, [[maybe_unused]] const glm::vec3& size, [[maybe_unused]] const components::Material& material, [[maybe_unused]] int entityID) const + void Renderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::vec3& position, [[maybe_unused]] const glm::vec3& size, [[maybe_unused]] const renderer::Material& material, [[maybe_unused]] int entityID) const { } - void Renderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::vec3& position, [[maybe_unused]] const glm::vec3& rotation, [[maybe_unused]] const glm::vec3& size, [[maybe_unused]] const components::Material& material, [[maybe_unused]] int entityID) const + void Renderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::vec3& position, [[maybe_unused]] const glm::vec3& rotation, [[maybe_unused]] const glm::vec3& size, [[maybe_unused]] const renderer::Material& material, [[maybe_unused]] int entityID) const { } - void Renderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::mat4& transform, [[maybe_unused]] const components::Material& material, [[maybe_unused]] int entityID) const + void Renderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::mat4& transform, [[maybe_unused]] const renderer::Material& material, [[maybe_unused]] int entityID) const { } diff --git a/tests/renderer/Renderer3D.test.cpp b/tests/renderer/Renderer3D.test.cpp index f62dc618f..7be94e850 100644 --- a/tests/renderer/Renderer3D.test.cpp +++ b/tests/renderer/Renderer3D.test.cpp @@ -221,7 +221,7 @@ namespace nexo::renderer { glm::vec3 position = {0.0f, 0.0f, 0.0f}; glm::vec3 size = {1.0f, 1.0f, 1.0f}; - components::Material material; + renderer::Material material; material.albedoColor = {1.0f, 0.0f, 0.0f, 1.0f}; // Red color material.albedoTexture = Texture2D::create(4, 4); // Example texture @@ -312,7 +312,7 @@ namespace nexo::renderer { glm::vec3 size = {2.0f, 2.0f, 2.0f}; glm::vec3 rotation = {45.0f, 30.0f, 60.0f}; - components::Material material; + renderer::Material material; material.albedoColor = {0.0f, 1.0f, 1.0f, 1.0f}; // Cyan color material.albedoTexture = Texture2D::create(4, 4); // Example texture material.specularColor = {1.0f, 1.0f, 1.0f, 1.0f}; @@ -346,7 +346,7 @@ namespace nexo::renderer { glm::rotate(glm::mat4(1.0f), glm::radians(45.0f), {0.0f, 1.0f, 0.0f}) * glm::scale(glm::mat4(1.0f), {2.0f, 2.0f, 2.0f}); - components::Material material; + renderer::Material material; material.albedoColor = {1.0f, 1.0f, 0.0f, 1.0f}; // Yellow color material.albedoTexture = Texture2D::create(4, 4); // Example texture From c90aa8fdda1c7cee621f629ec69b120d3c6dd809 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 25 Apr 2025 07:35:53 +0900 Subject: [PATCH 255/450] refactor(asset-ecs): rename Renderer classes to add Nx* prefix --- CMakeLists.txt | 2 +- editor/CMakeLists.txt | 2 +- .../EntityProperties/RenderProperty.cpp | 2 +- .../MaterialInspector/MaterialInspector.hpp | 2 +- editor/src/backends/ImGuiBackend.cpp | 20 +-- editor/src/backends/ImGuiBackend.hpp | 6 +- editor/src/utils/ScenePreview.cpp | 10 +- engine/CMakeLists.txt | 2 +- engine/src/Application.cpp | 8 +- engine/src/Application.hpp | 4 +- engine/src/CameraFactory.cpp | 2 +- engine/src/CameraFactory.hpp | 2 +- engine/src/components/Camera.hpp | 4 +- engine/src/components/Render.hpp | 8 +- engine/src/components/Render2D.hpp | 4 +- engine/src/components/RenderContext.hpp | 2 +- engine/src/components/Shapes2D.hpp | 4 +- engine/src/components/Shapes3D.hpp | 14 +- engine/src/core/event/Input.cpp | 8 +- engine/src/core/event/Input.hpp | 6 +- engine/src/core/event/opengl/InputOpenGl.hpp | 2 +- engine/src/renderer/Buffer.cpp | 26 ++-- engine/src/renderer/Buffer.hpp | 124 ++++++++--------- engine/src/renderer/Framebuffer.cpp | 10 +- engine/src/renderer/Framebuffer.hpp | 46 +++---- engine/src/renderer/RenderCommand.cpp | 10 +- engine/src/renderer/RenderCommand.hpp | 40 +++--- engine/src/renderer/Renderer.cpp | 10 +- engine/src/renderer/Renderer.hpp | 6 +- engine/src/renderer/Renderer2D.cpp | 124 ++++++++--------- engine/src/renderer/Renderer2D.hpp | 80 +++++------ engine/src/renderer/Renderer3D.cpp | 60 ++++----- engine/src/renderer/Renderer3D.hpp | 118 ++++++++-------- engine/src/renderer/RendererAPI.hpp | 16 +-- engine/src/renderer/RendererContext.hpp | 8 +- engine/src/renderer/RendererExceptions.hpp | 98 +++++++------- engine/src/renderer/Shader.cpp | 28 ++-- engine/src/renderer/Shader.hpp | 28 ++-- engine/src/renderer/ShaderStorageBuffer.cpp | 10 +- engine/src/renderer/ShaderStorageBuffer.hpp | 6 +- engine/src/renderer/SubTexture2D.cpp | 6 +- engine/src/renderer/SubTexture2D.hpp | 34 ++--- engine/src/renderer/Texture.cpp | 26 ++-- engine/src/renderer/Texture.hpp | 30 ++--- engine/src/renderer/VertexArray.cpp | 10 +- engine/src/renderer/VertexArray.hpp | 24 ++-- engine/src/renderer/Window.cpp | 10 +- engine/src/renderer/Window.hpp | 22 +-- engine/src/renderer/opengl/OpenGlBuffer.cpp | 24 ++-- engine/src/renderer/opengl/OpenGlBuffer.hpp | 20 +-- .../src/renderer/opengl/OpenGlFramebuffer.cpp | 56 ++++---- .../src/renderer/opengl/OpenGlFramebuffer.hpp | 40 +++--- .../src/renderer/opengl/OpenGlRendererAPI.hpp | 28 ++-- .../src/renderer/opengl/OpenGlRendererApi.cpp | 52 ++++---- engine/src/renderer/opengl/OpenGlShader.cpp | 68 +++++----- engine/src/renderer/opengl/OpenGlShader.hpp | 16 +-- .../opengl/OpenGlShaderStorageBuffer.cpp | 10 +- .../opengl/OpenGlShaderStorageBuffer.hpp | 6 +- .../src/renderer/opengl/OpenGlTexture2D.cpp | 28 ++-- .../src/renderer/opengl/OpenGlTexture2D.hpp | 28 ++-- .../src/renderer/opengl/OpenGlVertexArray.cpp | 66 ++++----- .../src/renderer/opengl/OpenGlVertexArray.hpp | 30 ++--- engine/src/renderer/opengl/OpenGlWindow.cpp | 42 +++--- engine/src/renderer/opengl/OpenGlWindow.hpp | 18 +-- engine/src/renderer/primitives/Cube.cpp | 42 +++--- engine/src/renderer/primitives/Mesh.cpp | 12 +- engine/src/systems/RenderSystem.cpp | 12 +- tests/engine/components/Camera.test.cpp | 8 +- tests/renderer/Buffer.test.cpp | 104 +++++++-------- tests/renderer/CMakeLists.txt | 2 +- tests/renderer/Exceptions.test.cpp | 40 +++--- tests/renderer/Framebuffer.test.cpp | 126 +++++++++--------- tests/renderer/Renderer3D.test.cpp | 46 +++---- tests/renderer/RendererAPI.test.cpp | 26 ++-- tests/renderer/Shader.test.cpp | 14 +- tests/renderer/Texture.test.cpp | 20 +-- tests/renderer/VertexArray.test.cpp | 42 +++--- tests/renderer/contexts/opengl.hpp | 8 +- 78 files changed, 1079 insertions(+), 1079 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e67aa1c7a..1b810efa9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") set(NEXO_COMPILER_FLAGS_RELEASE -O3) set(NEXO_COVERAGE_FLAGS -O0 --coverage) elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - set(NEXO_COMPILER_FLAGS_ALL /std:c++${CMAKE_CXX_STANDARD}) + set(NEXO_COMPILER_FLAGS_ALL /std:c++${CMAKE_CXX_STANDARD} /Zc:preprocessor) set(NEXO_COMPILER_FLAGS_DEBUG /Zi /Od /Zc:preprocessor) set(NEXO_COMPILER_FLAGS_RELEASE /O2 /Zc:preprocessor) set(NEXO_COVERAGE_FLAGS "") # MSVC doesn't support coverage in the same way diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 5d1097b3f..773978c70 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -148,7 +148,7 @@ target_link_libraries(nexoEditor PRIVATE Boost::uuid) include_directories(include) if(NEXO_GRAPHICS_API STREQUAL "OpenGL") - target_compile_definitions(nexoEditor PRIVATE GRAPHICS_API_OPENGL) + target_compile_definitions(nexoEditor PRIVATE NX_GRAPHICS_API_OPENGL) endif() # Set the output directory for the executable (prevents generator from creating Debug/Release folders) diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index 7f71224f5..3aade16cd 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -157,7 +157,7 @@ namespace nexo::editor { renderComponent.isRendered = !hidden; ImNexo::ToggleButtonWithSeparator("Material", §ionOpen); - static std::shared_ptr framebuffer = nullptr; + static std::shared_ptr framebuffer = nullptr; static int entityBase = -1; if (sectionOpen) { diff --git a/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp b/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp index 7d63c900d..27b93db47 100644 --- a/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp +++ b/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp @@ -53,7 +53,7 @@ namespace nexo::editor { */ void renderMaterialInspector(int selectedEntity); - std::shared_ptr m_framebuffer = nullptr; + std::shared_ptr m_framebuffer = nullptr; int m_ecsEntity = -1; bool m_materialModified = true; }; diff --git a/editor/src/backends/ImGuiBackend.cpp b/editor/src/backends/ImGuiBackend.cpp index 31ac39280..82543d58d 100644 --- a/editor/src/backends/ImGuiBackend.cpp +++ b/editor/src/backends/ImGuiBackend.cpp @@ -14,15 +14,15 @@ #include "ImGuiBackend.hpp" #include "exceptions/Exceptions.hpp" -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/openglImGuiBackend.hpp" #endif namespace nexo::editor { - void ImGuiBackend::init([[maybe_unused]] const std::shared_ptr& window) + void ImGuiBackend::init([[maybe_unused]] const std::shared_ptr& window) { - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL OpenGLImGuiBackend::init(static_cast(window->window())); return; #endif @@ -31,7 +31,7 @@ namespace nexo::editor { void ImGuiBackend::shutdown() { - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL OpenGLImGuiBackend::shutdown(); return; #endif @@ -40,7 +40,7 @@ namespace nexo::editor { void ImGuiBackend::initFontAtlas() { - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL OpenGLImGuiBackend::initFontAtlas(); return; #endif @@ -49,25 +49,25 @@ namespace nexo::editor { void ImGuiBackend::begin() { - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL OpenGLImGuiBackend::begin(); return; #endif THROW_EXCEPTION(BackendRendererApiNotSupported, "UNKNOWN"); } - void ImGuiBackend::end([[maybe_unused]] const std::shared_ptr& window) + void ImGuiBackend::end([[maybe_unused]] const std::shared_ptr& window) { - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL OpenGLImGuiBackend::end(static_cast(window->window())); return; #endif THROW_EXCEPTION(BackendRendererApiNotSupported, "UNKNOWN"); } - void ImGuiBackend::setErrorCallback([[maybe_unused]] const std::shared_ptr &window) + void ImGuiBackend::setErrorCallback([[maybe_unused]] const std::shared_ptr &window) { - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL const auto callback = OpenGLImGuiBackend::getErrorCallback(); window->setErrorCallback(callback); return; diff --git a/editor/src/backends/ImGuiBackend.hpp b/editor/src/backends/ImGuiBackend.hpp index bb3f1d80d..b1d1cbce9 100644 --- a/editor/src/backends/ImGuiBackend.hpp +++ b/editor/src/backends/ImGuiBackend.hpp @@ -33,7 +33,7 @@ namespace nexo::editor { * @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); + static void init(const std::shared_ptr& window); /** * @brief Shuts down and cleans up the ImGui backend @@ -68,7 +68,7 @@ namespace nexo::editor { * @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); + static void end(const std::shared_ptr& window); /** * @brief Sets up the error callback for ImGui on the window @@ -76,6 +76,6 @@ namespace nexo::editor { * @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); + static void setErrorCallback(const std::shared_ptr& window); }; } diff --git a/editor/src/utils/ScenePreview.cpp b/editor/src/utils/ScenePreview.cpp index 4f0ba28d6..77a8c8124 100644 --- a/editor/src/utils/ScenePreview.cpp +++ b/editor/src/utils/ScenePreview.cpp @@ -54,18 +54,18 @@ namespace nexo::editor::utils { static ecs::Entity createPreviewCamera(scene::SceneId sceneId, ecs::Entity entity, ecs::Entity entityCopy, const glm::vec2 &previewSize, const glm::vec4 &clearColor) { auto &app = getApp(); - renderer::FramebufferSpecs framebufferSpecs; + renderer::NxFramebufferSpecs framebufferSpecs; framebufferSpecs.attachments = { - renderer::FrameBufferTextureFormats::RGBA8, - renderer::FrameBufferTextureFormats::RED_INTEGER, - renderer::FrameBufferTextureFormats::Depth + renderer::NxFrameBufferTextureFormats::RGBA8, + renderer::NxFrameBufferTextureFormats::RED_INTEGER, + renderer::NxFrameBufferTextureFormats::Depth }; framebufferSpecs.width = static_cast(previewSize.x); framebufferSpecs.height = static_cast(previewSize.y); const auto &transformComponentBase = nexo::Application::m_coordinator->getComponent(entity); const auto &transformComponent = nexo::Application::m_coordinator->getComponent(entityCopy); - auto framebuffer = renderer::Framebuffer::create(framebufferSpecs); + auto framebuffer = renderer::NxFramebuffer::create(framebufferSpecs); float distance = transformComponentBase.size.z * 2.0f; diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 874db5738..9aa39e862 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -114,7 +114,7 @@ find_package(Boost CONFIG REQUIRED COMPONENTS dll) target_link_libraries(nexoRenderer PRIVATE Boost::dll) if(NEXO_GRAPHICS_API STREQUAL "OpenGL") - target_compile_definitions(nexoRenderer PRIVATE GRAPHICS_API_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) diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index f696c726a..e48d90934 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -181,7 +181,7 @@ namespace nexo { Application::Application() { - m_window = renderer::Window::create(); + m_window = renderer::NxWindow::create(); m_eventManager = std::make_shared(); registerAllDebugListeners(); registerSignalListeners(); @@ -217,16 +217,16 @@ namespace nexo { registerWindowCallbacks(); m_window->setVsync(false); -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) { - THROW_EXCEPTION(renderer::GraphicsApiInitFailure, "Failed to initialize OpenGL context with glad"); + THROW_EXCEPTION(renderer::NxGraphicsApiInitFailure, "Failed to initialize OpenGL context with glad"); } LOG(NEXO_INFO, "OpenGL context initialized with glad"); glViewport(0, 0, static_cast(m_window->getWidth()), static_cast(m_window->getHeight())); #endif - renderer::Renderer::init(); + renderer::NxRenderer::init(); m_coordinator->init(); registerEcsComponents(); diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index 920be35d0..c607c2bb3 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -225,7 +225,7 @@ namespace nexo { scene::SceneManager &getSceneManager() { return m_SceneManager; } - [[nodiscard]] const std::shared_ptr &getWindow() const { return m_window; }; + [[nodiscard]] const std::shared_ptr &getWindow() const { return m_window; }; [[nodiscard]] bool isWindowOpen() const { return m_window->isOpen(); }; static std::shared_ptr m_coordinator; @@ -249,7 +249,7 @@ namespace nexo { bool m_isRunning = true; bool m_isMinimized = false; bool m_displayProfileResult = true; - std::shared_ptr m_window; + std::shared_ptr m_window; float m_lastFrameTime = 0.0f; Timestep m_currentTimestep; diff --git a/engine/src/CameraFactory.cpp b/engine/src/CameraFactory.cpp index d558ad7b6..e5dce48e7 100644 --- a/engine/src/CameraFactory.cpp +++ b/engine/src/CameraFactory.cpp @@ -20,7 +20,7 @@ namespace nexo { ecs::Entity CameraFactory::createPerspectiveCamera(glm::vec3 pos, unsigned int width, - unsigned int height, std::shared_ptr renderTarget, + unsigned int height, std::shared_ptr renderTarget, const glm::vec4 &clearColor, float fov, float nearPlane, float farPlane) { components::TransformComponent transform{}; diff --git a/engine/src/CameraFactory.hpp b/engine/src/CameraFactory.hpp index 2ce2df839..f591302aa 100644 --- a/engine/src/CameraFactory.hpp +++ b/engine/src/CameraFactory.hpp @@ -22,7 +22,7 @@ namespace nexo { class CameraFactory { public: static ecs::Entity createPerspectiveCamera(glm::vec3 pos, unsigned int width, - unsigned int height, std::shared_ptr renderTarget = nullptr, + 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 = 0.1f, float farPlane = 1000.0f); }; } diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 38ab28709..47b6fdcaf 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -52,7 +52,7 @@ namespace nexo::components { 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. + std::shared_ptr m_renderTarget = nullptr; ///< The render target framebuffer. /** * @brief Retrieves the projection matrix for this camera. @@ -238,6 +238,6 @@ namespace nexo::components { 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. + std::shared_ptr renderTarget; ///< The render target framebuffer. }; } diff --git a/engine/src/components/Render.hpp b/engine/src/components/Render.hpp index a25436e98..a4d69c71f 100644 --- a/engine/src/components/Render.hpp +++ b/engine/src/components/Render.hpp @@ -36,7 +36,7 @@ namespace nexo::components bool isRendered = true; - virtual void draw(std::shared_ptr &context, + virtual void draw(std::shared_ptr &context, const TransformComponent &transf, int entityID) const = 0; [[nodiscard]] virtual std::shared_ptr clone() const = 0; }; @@ -51,7 +51,7 @@ namespace nexo::components { }; - void draw(std::shared_ptr &context, + void draw(std::shared_ptr &context, const TransformComponent &transform, int entityID) const override { shape->draw(context, transform, sprite, entityID); @@ -72,7 +72,7 @@ namespace nexo::components explicit Renderable3D(Material material, const std::shared_ptr &shape) : material(std::move(material)), shape(shape) {}; - void draw(std::shared_ptr &context, const TransformComponent &transf, int entityID) const override + void draw(std::shared_ptr &context, const TransformComponent &transf, int entityID) const override { shape->draw(context, transf, material, entityID); } @@ -97,7 +97,7 @@ namespace nexo::components { } - void draw(std::shared_ptr &context, const TransformComponent &transform, const int entityID = -1) const + void draw(std::shared_ptr &context, const TransformComponent &transform, const int entityID = -1) const { if (isRendered && renderable) renderable->draw(context, transform, entityID); diff --git a/engine/src/components/Render2D.hpp b/engine/src/components/Render2D.hpp index 5efa4b5d0..8dc48138d 100644 --- a/engine/src/components/Render2D.hpp +++ b/engine/src/components/Render2D.hpp @@ -20,8 +20,8 @@ namespace nexo::components { struct SpriteComponent { glm::vec4 color; - std::shared_ptr texture = nullptr; - std::shared_ptr sprite = nullptr; + std::shared_ptr texture = nullptr; + std::shared_ptr sprite = nullptr; }; } diff --git a/engine/src/components/RenderContext.hpp b/engine/src/components/RenderContext.hpp index ccb11f2f3..3494236e6 100644 --- a/engine/src/components/RenderContext.hpp +++ b/engine/src/components/RenderContext.hpp @@ -32,7 +32,7 @@ namespace nexo::components { float cellSize = 0.025f; }; GridParams gridParams; - renderer::Renderer3D renderer3D; + renderer::NxRenderer3D renderer3D; std::queue cameras; LightContext sceneLights; diff --git a/engine/src/components/Shapes2D.hpp b/engine/src/components/Shapes2D.hpp index 3088f3687..c4fe75528 100644 --- a/engine/src/components/Shapes2D.hpp +++ b/engine/src/components/Shapes2D.hpp @@ -24,11 +24,11 @@ namespace nexo::components { struct Shape2D { virtual ~Shape2D() = default; - virtual void draw(std::shared_ptr &context, const TransformComponent &transf, const SpriteComponent &sprite, int entityID) const = 0; + virtual void draw(std::shared_ptr &context, const TransformComponent &transf, const SpriteComponent &sprite, int entityID) const = 0; }; struct Quad final : Shape2D { - void draw([[maybe_unused]] std::shared_ptr &context, [[maybe_unused]] const TransformComponent &transf, [[maybe_unused]] const SpriteComponent &sprite, [[maybe_unused]] int entityID) const override + void draw([[maybe_unused]] std::shared_ptr &context, [[maybe_unused]] const TransformComponent &transf, [[maybe_unused]] const SpriteComponent &sprite, [[maybe_unused]] int entityID) const override { //if (sprite.sprite != nullptr) // renderer2D.drawQuad(transf.pos, {transf.size.x, transf.size.y}, transf.rotation.z, diff --git a/engine/src/components/Shapes3D.hpp b/engine/src/components/Shapes3D.hpp index 1fc971d79..e0d889079 100644 --- a/engine/src/components/Shapes3D.hpp +++ b/engine/src/components/Shapes3D.hpp @@ -28,12 +28,12 @@ namespace nexo::components { struct Shape3D { virtual ~Shape3D() = default; - virtual void draw(std::shared_ptr &context, const TransformComponent &transf, const Material &material, int entityID) = 0; + virtual void draw(std::shared_ptr &context, const TransformComponent &transf, const Material &material, int entityID) = 0; [[nodiscard]] virtual std::shared_ptr clone() const = 0; }; struct Cube final : Shape3D { - void draw(std::shared_ptr &context, const TransformComponent &transf, const Material &material, const int entityID) override + void draw(std::shared_ptr &context, const TransformComponent &transf, const Material &material, const int entityID) override { const auto renderer3D = context->renderer3D; @@ -45,7 +45,7 @@ namespace nexo::components { auto emissiveMapAsset = material.emissiveMap.lock(); - renderer::Material inputMaterial = { + renderer::NxMaterial inputMaterial = { .albedoColor = material.albedoColor, .specularColor = material.specularColor, .emissiveColor = material.emissiveColor, @@ -70,7 +70,7 @@ namespace nexo::components { struct Mesh { std::string name; - std::vector vertices; + std::vector vertices; std::vector indices; assets::AssetRef material; }; @@ -80,13 +80,13 @@ namespace nexo::components { std::vector meshes; std::vector> children; - void draw(renderer::Renderer3D &renderer3D, const glm::mat4 &parentTransform, const int entityID) const + void draw(renderer::NxRenderer3D &renderer3D, const glm::mat4 &parentTransform, const int entityID) const { const glm::mat4 localTransform = parentTransform * transform; for (const auto &mesh: meshes) { //TODO: Implement a way to pass the transform directly to the shader - std::vector transformedVertices = mesh.vertices; + std::vector transformedVertices = mesh.vertices; for (auto &vertex: transformedVertices) vertex.position = glm::vec3(localTransform * glm::vec4(vertex.position, 1.0f)); @@ -120,7 +120,7 @@ namespace nexo::components { explicit Model(const std::shared_ptr &rootNode) : root(rootNode) {}; // NOT WORKING ANYMORE - void draw(std::shared_ptr &context, const TransformComponent &transf, [[maybe_unused]] const Material &material, const int entityID) override + void draw(std::shared_ptr &context, const TransformComponent &transf, [[maybe_unused]] const Material &material, const int entityID) override { auto renderer3D = context->renderer3D; //TODO: Pass the material to the draw mesh function diff --git a/engine/src/core/event/Input.cpp b/engine/src/core/event/Input.cpp index 0b6657c45..5be40d0dc 100644 --- a/engine/src/core/event/Input.cpp +++ b/engine/src/core/event/Input.cpp @@ -13,7 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "Input.hpp" #include "renderer/RendererExceptions.hpp" -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/InputOpenGl.hpp" #endif @@ -22,15 +22,15 @@ namespace nexo::event { std::shared_ptr Input::_instance = nullptr; - void Input::init(const std::shared_ptr& window) + void Input::init(const std::shared_ptr& window) { if (!_instance) { - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL _instance = std::make_shared(window); return; #endif - THROW_EXCEPTION(renderer::UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(renderer::NxUnknownGraphicsApi, "UNKNOWN"); } } diff --git a/engine/src/core/event/Input.hpp b/engine/src/core/event/Input.hpp index 635ca4c63..7bd9828f1 100644 --- a/engine/src/core/event/Input.hpp +++ b/engine/src/core/event/Input.hpp @@ -39,7 +39,7 @@ namespace nexo::event { * * @param window Shared pointer to the window for input polling. */ - explicit Input(const std::shared_ptr &window) : m_window(window) {}; + explicit Input(const std::shared_ptr &window) : m_window(window) {}; /** * @brief Checks if the specified key is currently pressed. @@ -100,10 +100,10 @@ namespace nexo::event { * * @param window Shared pointer to the window used for input. */ - static void init(const std::shared_ptr& window); + static void init(const std::shared_ptr& window); protected: - std::shared_ptr m_window; + std::shared_ptr m_window; private: static std::shared_ptr _instance; }; diff --git a/engine/src/core/event/opengl/InputOpenGl.hpp b/engine/src/core/event/opengl/InputOpenGl.hpp index 5e563bb57..655375342 100644 --- a/engine/src/core/event/opengl/InputOpenGl.hpp +++ b/engine/src/core/event/opengl/InputOpenGl.hpp @@ -19,7 +19,7 @@ namespace nexo::event { class InputOpenGl final : public Input { public: - explicit InputOpenGl(const std::shared_ptr& window) : Input(window) + explicit InputOpenGl(const std::shared_ptr& window) : Input(window) { LOG(NEXO_DEV, "Opengl input handler initialized"); }; diff --git a/engine/src/renderer/Buffer.cpp b/engine/src/renderer/Buffer.cpp index 62fa1752e..f71b1cc99 100644 --- a/engine/src/renderer/Buffer.cpp +++ b/engine/src/renderer/Buffer.cpp @@ -13,34 +13,34 @@ /////////////////////////////////////////////////////////////////////////////// #include "Buffer.hpp" #include "renderer/RendererExceptions.hpp" -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlBuffer.hpp" #endif namespace nexo::renderer { - std::shared_ptr createVertexBuffer(float *vertices, unsigned int size) + std::shared_ptr createVertexBuffer(float *vertices, unsigned int size) { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(vertices, size); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(vertices, size); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } - std::shared_ptr createVertexBuffer(unsigned int size) + std::shared_ptr createVertexBuffer(unsigned int size) { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(size); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(size); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } - std::shared_ptr createIndexBuffer() + std::shared_ptr createIndexBuffer() { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } } diff --git a/engine/src/renderer/Buffer.hpp b/engine/src/renderer/Buffer.hpp index df6025a5d..2e88c245a 100644 --- a/engine/src/renderer/Buffer.hpp +++ b/engine/src/renderer/Buffer.hpp @@ -22,10 +22,10 @@ namespace nexo::renderer { /** - * @enum ShaderDataType + * @enum NxShaderDataType * @brief Enum representing the various data types supported in shaders. * - * ShaderDataType is used to define the type of data stored in a buffer element, + * NxShaderDataType is used to define the type of data stored in a buffer element, * such as float, integer, matrix, or boolean types. This enum ensures consistent * representation and handling of data types in vertex and index buffers. * @@ -36,7 +36,7 @@ namespace nexo::renderer { * - INT, INT2, INT3, INT4: Represents one or more integer values. * - BOOL: Represents a boolean value. */ - enum class ShaderDataType { + enum class NxShaderDataType { NONE = 0, FLOAT, FLOAT2, @@ -52,40 +52,40 @@ namespace nexo::renderer { }; /** - * @brief Returns the size (in bytes) of a given ShaderDataType. + * @brief Returns the size (in bytes) of a given NxShaderDataType. * - * This function calculates the memory size required for a specific ShaderDataType. + * This function calculates the memory size required for a specific NxShaderDataType. * It supports floats, integers, matrices, and boolean types. * - * @param type The ShaderDataType whose size is to be calculated. - * @return The size in bytes of the provided ShaderDataType. + * @param type The NxShaderDataType whose size is to be calculated. + * @return The size in bytes of the provided NxShaderDataType. * * Example: * - FLOAT3 will return 12 (3 floats, 4 bytes each). * - MAT4 will return 64 (4x4 matrix of floats). */ - static unsigned int shaderDataTypeSize(ShaderDataType type) + static unsigned int shaderDataTypeSize(NxShaderDataType type) { switch (type) { - case ShaderDataType::FLOAT: return 4; // 1 float (4 bytes) - case ShaderDataType::FLOAT2: return 4 * 2; // 2 floats (8 bytes) - case ShaderDataType::FLOAT3: return 4 * 3; // 3 floats (12 bytes) - case ShaderDataType::FLOAT4: return 4 * 4; // 4 floats (16 bytes) - case ShaderDataType::MAT3: return 4 * 3 * 3; // 3x3 matrix (36 bytes) - case ShaderDataType::MAT4: return 4 * 4 * 4; // 4x4 matrix (64 bytes) - case ShaderDataType::INT: return 4; // 1 int (4 bytes) - case ShaderDataType::INT2: return 4 * 2; // 2 ints (8 bytes) - case ShaderDataType::INT3: return 4 * 3; // 3 ints (12 bytes) - case ShaderDataType::INT4: return 4 * 4; // 4 ints (16 bytes) - case ShaderDataType::BOOL: return 1; // 1 byte (1 bool) - case ShaderDataType::NONE: return 0; // No type, return 0 + 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 } return 0; // Default case for undefined types } /** - * @struct BufferElements + * @struct NxBufferElements * @brief Represents an individual element in a buffer layout. * * Each buffer element specifies its name, type, size, and offset within a buffer. @@ -101,15 +101,15 @@ namespace nexo::renderer { * Functions: * - @return getComponentCount() Retrieves the number of components (e.g., FLOAT3 = 3). */ - struct BufferElements { + struct NxBufferElements { std::string name; - ShaderDataType type{}; + NxShaderDataType type{}; unsigned int size{}; unsigned int offset{}; bool normalized{}; - BufferElements() = default; - BufferElements(const ShaderDataType Type, std::string name, const bool normalized = false) + 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) { @@ -119,24 +119,24 @@ namespace nexo::renderer { { switch(type) { - case ShaderDataType::FLOAT: return 1; - case ShaderDataType::FLOAT2: return 2; - case ShaderDataType::FLOAT3: return 3; - case ShaderDataType::FLOAT4: return 4; - case ShaderDataType::INT: return 1; - case ShaderDataType::INT2: return 2; - case ShaderDataType::INT3: return 3; - case ShaderDataType::INT4: return 4; - case ShaderDataType::MAT3: return 3 * 3; - case ShaderDataType::MAT4: return 4 * 4; - case ShaderDataType::BOOL: return 1; + 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 -1; } } }; /** - * @class BufferLayout + * @class NxBufferLayout * @brief Defines the structure and layout of elements in a vertex buffer. * * A BufferLayout is a collection of BufferElements, each specifying a data type, @@ -158,24 +158,24 @@ namespace nexo::renderer { * Iterators: * - Supports begin() and end() iterators for easy iteration over elements. */ - class BufferLayout { + class NxBufferLayout { public: - BufferLayout() = default; - BufferLayout(const std::initializer_list elements) + NxBufferLayout() = default; + NxBufferLayout(const std::initializer_list elements) : _elements(elements) { calculateOffsetAndStride(); }; - [[nodiscard]] std::vector getElements() const { return _elements; }; + [[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(); }; + 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; + std::vector _elements; unsigned int _stride{}; void calculateOffsetAndStride() @@ -192,7 +192,7 @@ namespace nexo::renderer { }; /** - * @class VertexBuffer + * @class NxVertexBuffer * @brief Abstract class representing a vertex buffer in the graphics pipeline. * * A vertex buffer is a GPU memory buffer that stores per-vertex attributes, such as @@ -200,7 +200,7 @@ namespace nexo::renderer { * the interface for creating, binding, and managing vertex buffers, allowing for * implementation across various graphics APIs. */ - class VertexBuffer { + class NxVertexBuffer { public: /** * @brief Destroys the vertex buffer. @@ -211,7 +211,7 @@ namespace nexo::renderer { * Usage: * - Automatically called when a VertexBuffer object goes out of scope. */ - virtual ~VertexBuffer() = default; + virtual ~NxVertexBuffer() = default; /** * @brief Binds the vertex buffer as the active buffer in the graphics pipeline. @@ -220,7 +220,7 @@ namespace nexo::renderer { * the data stored in this buffer. * * Pure Virtual Function: - * - Must be implemented by platform-specific subclasses (e.g., OpenGLVertexBuffer). + * - Must be implemented by platform-specific subclasses (e.g., NxOpenGlVertexBuffer). */ virtual void bind() const = 0; @@ -231,7 +231,7 @@ namespace nexo::renderer { * 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., OpenGLVertexBuffer). + * - Must be implemented by platform-specific subclasses (e.g., NxOpenGlVertexBuffer). */ virtual void unbind() const = 0; @@ -246,7 +246,7 @@ namespace nexo::renderer { * Pure Virtual Function: * - Must be implemented by platform-specific subclasses. */ - virtual void setLayout(const BufferLayout &layout) = 0; + virtual void setLayout(const NxBufferLayout &layout) = 0; /** * @brief Retrieves the layout of the vertex buffer. @@ -259,7 +259,7 @@ namespace nexo::renderer { * Pure Virtual Function: * - Must be implemented by platform-specific subclasses. */ - [[nodiscard]] virtual BufferLayout getLayout() const = 0; + [[nodiscard]] virtual NxBufferLayout getLayout() const = 0; /** * @brief Uploads new data to the vertex buffer. @@ -279,14 +279,14 @@ namespace nexo::renderer { }; /** - * @class IndexBuffer + * @class NxIndexBuffer * @brief Abstract class representing an index buffer in the graphics pipeline. * * An index buffer stores indices into a vertex buffer, allowing for efficient reuse * of vertex data. This class provides an abstract interface for creating, binding, * and managing index buffers, enabling compatibility with multiple graphics APIs. */ - class IndexBuffer { + class NxIndexBuffer { public: /** * @brief Destroys the index buffer. @@ -297,7 +297,7 @@ namespace nexo::renderer { * Usage: * - Automatically called when an IndexBuffer object goes out of scope. */ - virtual ~IndexBuffer() = default; + virtual ~NxIndexBuffer() = default; /** * @brief Binds the index buffer as the active buffer in the graphics pipeline. @@ -363,9 +363,9 @@ namespace nexo::renderer { * @return A shared pointer to the created VertexBuffer instance. * * Throws: - * - UnknownGraphicsApi exception if no graphics API is defined. + * - NxUnknownGraphicsApi exception if no graphics API is defined. */ - std::shared_ptr createVertexBuffer(float *vertices, unsigned int size); + std::shared_ptr createVertexBuffer(float *vertices, unsigned int size); /** * @function createVertexBuffer(unsigned int size) @@ -377,9 +377,9 @@ namespace nexo::renderer { * @return A shared pointer to the created VertexBuffer instance. * * Throws: - * - UnknownGraphicsApi exception if no graphics API is defined. + * - NxUnknownGraphicsApi exception if no graphics API is defined. */ - std::shared_ptr createVertexBuffer(unsigned int size); + std::shared_ptr createVertexBuffer(unsigned int size); /** * @function createIndexBuffer() @@ -391,9 +391,9 @@ namespace nexo::renderer { * @return A shared pointer to the created IndexBuffer instance. * * Throws: - * - UnknownGraphicsApi exception if no graphics API is defined. + * - NxUnknownGraphicsApi exception if no graphics API is defined. */ - std::shared_ptr createIndexBuffer(); + std::shared_ptr createIndexBuffer(); } diff --git a/engine/src/renderer/Framebuffer.cpp b/engine/src/renderer/Framebuffer.cpp index 6024c3c1d..4cc5f3cd3 100644 --- a/engine/src/renderer/Framebuffer.cpp +++ b/engine/src/renderer/Framebuffer.cpp @@ -13,18 +13,18 @@ /////////////////////////////////////////////////////////////////////////////// #include "Framebuffer.hpp" #include "renderer/RendererExceptions.hpp" -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlFramebuffer.hpp" #endif namespace nexo::renderer { - std::shared_ptr Framebuffer::create(const FramebufferSpecs &specs) + std::shared_ptr NxFramebuffer::create(const NxFramebufferSpecs &specs) { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(specs); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(specs); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } diff --git a/engine/src/renderer/Framebuffer.hpp b/engine/src/renderer/Framebuffer.hpp index e5df703b5..c8722b620 100644 --- a/engine/src/renderer/Framebuffer.hpp +++ b/engine/src/renderer/Framebuffer.hpp @@ -20,7 +20,7 @@ namespace nexo::renderer { /** - * @enum FrameBufferTextureFormats + * @enum NxFrameBufferTextureFormats * @brief Enum representing the various texture formats supported for framebuffer attachments. * * Texture formats define how data is stored in framebuffer attachments. These include color, @@ -34,7 +34,7 @@ namespace nexo::renderer { * - Depth: Alias for DEPTH24STENCIL8. * - NB_TEXTURE_FORMATS: Tracks the number of texture formats (for internal use). */ - enum class FrameBufferTextureFormats { + enum class NxFrameBufferTextureFormats { NONE = 0, RGBA8, @@ -49,7 +49,7 @@ namespace nexo::renderer { }; /** - * @struct FrameBufferTextureSpecifications + * @struct NxFrameBufferTextureSpecifications * @brief Defines the format for a single framebuffer texture attachment. * * This struct specifies the properties of a single texture attachment, such as its format. @@ -59,17 +59,17 @@ namespace nexo::renderer { * * Constructors: * - FrameBufferTextureSpecifications(): Default constructor with no format. - * - FrameBufferTextureSpecifications(const FrameBufferTextureFormats format): Initializes with a specified texture format. + * - FrameBufferTextureSpecifications(const NxFrameBufferTextureFormats format): Initializes with a specified texture format. */ - struct FrameBufferTextureSpecifications { - FrameBufferTextureSpecifications() = default; - FrameBufferTextureSpecifications(const FrameBufferTextureFormats format) : textureFormat(format) {}; + struct NxFrameBufferTextureSpecifications { + NxFrameBufferTextureSpecifications() = default; + NxFrameBufferTextureSpecifications(const NxFrameBufferTextureFormats format) : textureFormat(format) {}; - FrameBufferTextureFormats textureFormat = FrameBufferTextureFormats::NONE; + NxFrameBufferTextureFormats textureFormat = NxFrameBufferTextureFormats::NONE; }; /** - * @struct FrameBufferAttachmentsSpecifications + * @struct NxFrameBufferAttachmentsSpecifications * @brief Defines the list of texture attachments for a framebuffer. * * A framebuffer can have multiple attachments (e.g., color and depth textures). This struct @@ -83,15 +83,15 @@ namespace nexo::renderer { * - FrameBufferAttachmentsSpecifications(std::initializer_list attachments): * Initializes the list with a set of predefined texture specifications. */ - struct FrameBufferAttachmentsSpecifications { - FrameBufferAttachmentsSpecifications() = default; - FrameBufferAttachmentsSpecifications(std::initializer_list attachments) : attachments(attachments) {}; + struct NxFrameBufferAttachmentsSpecifications { + NxFrameBufferAttachmentsSpecifications() = default; + NxFrameBufferAttachmentsSpecifications(std::initializer_list attachments) : attachments(attachments) {}; - std::vector attachments; + std::vector attachments; }; /** - * @struct FramebufferSpecs + * @struct NxFramebufferSpecs * @brief Represents the specifications for creating a framebuffer. * * FramebufferSpecs encapsulates all the necessary properties for initializing a framebuffer, @@ -104,10 +104,10 @@ namespace nexo::renderer { * - @param samples The number of samples for multisampling (default is 1). * - @param swapChainTarget Indicates whether the framebuffer is part of the swap chain (default is false). */ - struct FramebufferSpecs { + struct NxFramebufferSpecs { unsigned int width{}; unsigned int height{}; - FrameBufferAttachmentsSpecifications attachments; + NxFrameBufferAttachmentsSpecifications attachments; unsigned int samples = 1; @@ -115,7 +115,7 @@ namespace nexo::renderer { }; /** - * @class Framebuffer + * @class NxFramebuffer * @brief Abstract class representing a framebuffer in the rendering pipeline. * * A framebuffer is an off-screen rendering target that stores the results of @@ -132,7 +132,7 @@ namespace nexo::renderer { * * Usage: * - The `Framebuffer` class is an abstract base class. Platform or API-specific - * implementations (e.g., OpenGLFramebuffer) must inherit and implement the + * implementations (e.g., NxOpenGLFramebuffer) must inherit and implement the * pure virtual methods. * * Responsibilities: @@ -146,7 +146,7 @@ namespace nexo::renderer { * 3. Access texture attachments (e.g., for post-processing) using `getColorAttachmentId`. * 4. Unbind the framebuffer to render to the default framebuffer (screen). */ - class Framebuffer { + class NxFramebuffer { public: /** * @brief Destroys the framebuffer and releases associated resources. @@ -154,7 +154,7 @@ namespace nexo::renderer { * This virtual destructor ensures that derived classes properly clean up * framebuffer resources (e.g., OpenGL framebuffers). */ - virtual ~Framebuffer() = default; + virtual ~NxFramebuffer() = default; /** * @brief Binds the framebuffer as the active rendering target. @@ -244,7 +244,7 @@ namespace nexo::renderer { * * @return A reference to the FramebufferSpecs struct. */ - [[nodiscard]] virtual FramebufferSpecs &getSpecs() = 0; + [[nodiscard]] virtual NxFramebufferSpecs &getSpecs() = 0; /** * @brief Retrieves the specifications of the framebuffer (const version). @@ -253,7 +253,7 @@ namespace nexo::renderer { * * @return A constant reference to the FramebufferSpecs struct. */ - [[nodiscard]] virtual const FramebufferSpecs &getSpecs() const = 0; + [[nodiscard]] virtual const NxFramebufferSpecs &getSpecs() const = 0; /** * @brief Retrieves the OpenGL ID of a specific color attachment. * @@ -287,7 +287,7 @@ namespace nexo::renderer { * Notes: * - This function is typically implemented in a platform-specific or API-specific source file. */ - static std::shared_ptr create(const FramebufferSpecs& specs); + static std::shared_ptr create(const NxFramebufferSpecs& specs); }; diff --git a/engine/src/renderer/RenderCommand.cpp b/engine/src/renderer/RenderCommand.cpp index 3199a6263..6f494617f 100644 --- a/engine/src/renderer/RenderCommand.cpp +++ b/engine/src/renderer/RenderCommand.cpp @@ -13,20 +13,20 @@ /////////////////////////////////////////////////////////////////////////////// #include "RenderCommand.hpp" #include "renderer/RendererExceptions.hpp" -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlRendererAPI.hpp" #endif namespace nexo::renderer { - #ifdef GRAPHICS_API_OPENGL - RendererApi *RenderCommand::_rendererApi = new OpenGlRendererApi; + #ifdef NX_GRAPHICS_API_OPENGL + NxRendererApi *NxRenderCommand::_rendererApi = new NxOpenGlRendererApi; #endif - void RenderCommand::init() + void NxRenderCommand::init() { if (!_rendererApi) - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); _rendererApi->init(); } } diff --git a/engine/src/renderer/RenderCommand.hpp b/engine/src/renderer/RenderCommand.hpp index ced9e006a..5ce02c5e1 100644 --- a/engine/src/renderer/RenderCommand.hpp +++ b/engine/src/renderer/RenderCommand.hpp @@ -17,10 +17,10 @@ namespace nexo::renderer { /** - * @class RenderCommand + * @class NxRenderCommand * @brief Provides a high-level interface for issuing rendering commands. * - * The `RenderCommand` class serves as an abstraction layer over the graphics API + * The `NxRenderCommand` class serves as an abstraction layer over the graphics API * (e.g., OpenGL, DirectX, Vulkan), allowing the application to issue rendering * commands without being tightly coupled to a specific API. * @@ -30,30 +30,30 @@ namespace nexo::renderer { * and drawing indexed primitives. * * Responsibilities: - * - Delegates rendering operations to the underlying `RendererApi` implementation. + * - Delegates rendering operations to the underlying `NxRendererApi` implementation. * - Provides static methods for commonly used rendering commands. * * Usage: * - Before issuing any rendering commands, call `init()` to initialize the underlying - * `RendererApi`. + * `NxRendererApi`. * - Use static methods like `setViewport`, `setClearColor`, `clear`, and `drawIndexed` * for rendering operations. * * Notes: - * - The specific implementation of `RendererApi` (e.g., `OpenGlRendererApi`) is - * determined by the preprocessor directive `GRAPHICS_API_OPENGL` or similar. + * - The specific implementation of `RendererApi` (e.g., `NxOpenGlRendererApi`) is + * determined by the preprocessor directive `NX_GRAPHICS_API_OPENGL` or similar. */ - class RenderCommand { + class NxRenderCommand { public: /** * @brief Initializes the rendering API. * - * This method initializes the underlying `RendererApi` instance, ensuring that the + * 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: - * - UnknownGraphicsApi exception if the `_rendererApi` instance is null. + * - NxUnknownGraphicsApi exception if the `_rendererApi` instance is null. * * Usage: * - Typically called once during application initialization. @@ -64,7 +64,7 @@ namespace nexo::renderer { * @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 `RendererApi` implementation. + * 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. @@ -80,7 +80,7 @@ namespace nexo::renderer { * @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 `RendererApi` implementation. + * `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]). @@ -95,7 +95,7 @@ namespace nexo::renderer { * @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 `RendererApi` + * rendering the next frame. This command is delegated to the `NxRendererApi` * implementation. * * Usage: @@ -108,7 +108,7 @@ namespace nexo::renderer { * * 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 `RendererApi` implementation. + * 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 @@ -117,7 +117,7 @@ namespace nexo::renderer { * Usage: * - Use this method to draw meshes or primitives with indexed geometry. */ - static void drawIndexed(const std::shared_ptr &vertexArray, unsigned int indexCount = 0) + static void drawIndexed(const std::shared_ptr &vertexArray, unsigned int indexCount = 0) { _rendererApi->drawIndexed(vertexArray, indexCount); } @@ -210,18 +210,18 @@ namespace nexo::renderer { private: /** - * @brief Static pointer to the active `RendererApi` implementation. + * @brief Static pointer to the active `NxRendererApi` implementation. * - * This member holds a pointer to the concrete `RendererApi` instance (e.g., - * `OpenGlRendererApi`). It is initialized based on the active graphics API, - * as determined by preprocessor directives (e.g., `GRAPHICS_API_OPENGL`). + * 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 - * `RenderCommand` methods. + * `NxRenderCommand` methods. * - The application must ensure that `_rendererApi` is initialized via `init()` * before issuing any render commands. */ - static RendererApi *_rendererApi; + static NxRendererApi *_rendererApi; }; } diff --git a/engine/src/renderer/Renderer.cpp b/engine/src/renderer/Renderer.cpp index 48511c716..51fdb70cb 100644 --- a/engine/src/renderer/Renderer.cpp +++ b/engine/src/renderer/Renderer.cpp @@ -16,15 +16,15 @@ namespace nexo::renderer { - Renderer::SceneData *Renderer::_sceneData = new Renderer::SceneData; + NxRenderer::NxSceneData *NxRenderer::_sceneData = new NxRenderer::NxSceneData; - void Renderer::init() + void NxRenderer::init() { - RenderCommand::init(); + NxRenderCommand::init(); } - void Renderer::onWindowResize(unsigned int width, unsigned int height) + void NxRenderer::onWindowResize(unsigned int width, unsigned int height) { - RenderCommand::setViewport(0, 0, width, height); + NxRenderCommand::setViewport(0, 0, width, height); } } diff --git a/engine/src/renderer/Renderer.hpp b/engine/src/renderer/Renderer.hpp index 3b03f36cb..d6f8bbeef 100644 --- a/engine/src/renderer/Renderer.hpp +++ b/engine/src/renderer/Renderer.hpp @@ -20,15 +20,15 @@ namespace nexo::renderer { - class Renderer { + class NxRenderer { public: static void init(); static void onWindowResize(unsigned int width, unsigned int height); - struct SceneData { + struct NxSceneData { glm::mat4 projectionMatrix; }; - static SceneData *_sceneData; + static NxSceneData *_sceneData; }; } diff --git a/engine/src/renderer/Renderer2D.cpp b/engine/src/renderer/Renderer2D.cpp index f3e7630af..7b7aa4f90 100644 --- a/engine/src/renderer/Renderer2D.cpp +++ b/engine/src/renderer/Renderer2D.cpp @@ -23,21 +23,21 @@ #include namespace nexo::renderer { - void Renderer2D::init() + void NxRenderer2D::init() { - m_storage = std::make_shared(); + m_storage = std::make_shared(); // Initialize vertex array and buffer m_storage->vertexArray = createVertexArray(); - m_storage->vertexBuffer = createVertexBuffer(m_storage->maxVertices * sizeof(QuadVertex)); + m_storage->vertexBuffer = createVertexBuffer(m_storage->maxVertices * sizeof(NxQuadVertex)); // Define layout for the vertex buffer - const BufferLayout quadVertexBufferLayout = { - {ShaderDataType::FLOAT3, "aPos"}, // Position - {ShaderDataType::FLOAT4, "aColor"}, // Color - {ShaderDataType::FLOAT2, "aTexCoord"}, // Texture Coordinates - {ShaderDataType::FLOAT, "aTexIndex"}, // Texture Index - {ShaderDataType::INT, "aEntityID"} + const NxBufferLayout quadVertexBufferLayout = { + {NxShaderDataType::FLOAT3, "aPos"}, // Position + {NxShaderDataType::FLOAT4, "aColor"}, // Color + {NxShaderDataType::FLOAT2, "aTexCoord"}, // Texture Coordinates + {NxShaderDataType::FLOAT, "aTexIndex"}, // Texture Index + {NxShaderDataType::INT, "aEntityID"} }; m_storage->vertexBuffer->setLayout(quadVertexBufferLayout); m_storage->vertexArray->addVertexBuffer(m_storage->vertexBuffer); @@ -53,20 +53,20 @@ namespace nexo::renderer { m_storage->vertexArray->setIndexBuffer(m_storage->indexBuffer); // Initialize white texture - m_storage->whiteTexture = Texture2D::create(1, 1); + m_storage->whiteTexture = NxTexture2D::create(1, 1); unsigned int whiteTextureData = 0xffffffff; m_storage->whiteTexture->setData(&whiteTextureData, sizeof(unsigned int)); // Setup texture samplers - std::array samplers{}; - for (int i = 0; i < static_cast(Renderer2DStorage::maxTextureSlots); ++i) + std::array samplers{}; + for (int i = 0; i < static_cast(NxRenderer2DStorage::maxTextureSlots); ++i) samplers[i] = i; try { - m_storage->textureShader = Shader::create( + m_storage->textureShader = NxShader::create( Path::resolvePathRelativeToExe("../resources/shaders/texture.glsl").string()); m_storage->textureShader->bind(); - m_storage->textureShader->setUniformIntArray("uTexture", samplers.data(), Renderer2DStorage::maxTextureSlots); + m_storage->textureShader->setUniformIntArray("uTexture", samplers.data(), NxRenderer2DStorage::maxTextureSlots); } catch (const Exception &e) { LOG_EXCEPTION(e); } @@ -84,17 +84,17 @@ namespace nexo::renderer { } - void Renderer2D::shutdown() + void NxRenderer2D::shutdown() { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_2D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_2D); m_storage.reset(); } - void Renderer2D::beginScene(const glm::mat4 &viewProjection) + void NxRenderer2D::beginScene(const glm::mat4 &viewProjection) { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_2D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_2D); m_storage->textureShader->bind(); m_storage->vertexArray->bind(); m_storage->vertexBuffer->bind(); @@ -106,19 +106,19 @@ namespace nexo::renderer { m_renderingScene = true; } - void Renderer2D::flush() const + void NxRenderer2D::flush() const { m_storage->textureShader->bind(); for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) { m_storage->textureSlots[i]->bind(i); } - RenderCommand::drawIndexed(m_storage->vertexArray, m_storage->indexCount); + NxRenderCommand::drawIndexed(m_storage->vertexArray, m_storage->indexCount); m_storage->stats.drawCalls++; m_storage->vertexArray->unbind(); m_storage->vertexBuffer->unbind(); } - void Renderer2D::flushAndReset() const + void NxRenderer2D::flushAndReset() const { flush(); m_storage->indexCount = 0; @@ -127,12 +127,12 @@ namespace nexo::renderer { m_storage->textureSlotIndex = 1; } - void Renderer2D::endScene() const + void NxRenderer2D::endScene() const { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_2D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_2D); if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); const auto vertexDataSize = static_cast( reinterpret_cast(m_storage->vertexBufferPtr) - @@ -146,7 +146,7 @@ namespace nexo::renderer { } - void Renderer2D::generateQuadVertices(const glm::mat4 &transform, const glm::vec4 color, const float textureIndex, + void NxRenderer2D::generateQuadVertices(const glm::mat4 &transform, const glm::vec4 color, const float textureIndex, const glm::vec2 *textureCoords, int entityID) const { constexpr unsigned int quadVertexCount = 4; @@ -185,7 +185,7 @@ namespace nexo::renderer { } - float Renderer2D::getTextureIndex(const std::shared_ptr &texture) const + float NxRenderer2D::getTextureIndex(const std::shared_ptr &texture) const { float textureIndex = 0.0f; @@ -205,18 +205,18 @@ namespace nexo::renderer { return textureIndex; } - void Renderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const glm::vec4 &color, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const glm::vec4 &color, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); drawQuad({pos.x, pos.y, 0.0f}, size, color, entityID); } - void Renderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const glm::vec4 &color, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const glm::vec4 &color, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); if (m_storage->indexCount >= m_storage->maxIndices) flushAndReset(); @@ -236,20 +236,20 @@ namespace nexo::renderer { m_storage->stats.quadCount++; } - void Renderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, - const std::shared_ptr &texture, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, + const std::shared_ptr &texture, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); drawQuad({pos.x, pos.y, 0.0f}, size, texture, entityID); } - void Renderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, - const std::shared_ptr &texture, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, + const std::shared_ptr &texture, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); if (m_storage->indexCount >= m_storage->maxIndices) flushAndReset(); @@ -271,20 +271,20 @@ namespace nexo::renderer { m_storage->stats.quadCount++; } - void Renderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, - const std::shared_ptr &subTexture, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, + const std::shared_ptr &subTexture, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); drawQuad({pos.x, pos.y, 0.0f}, size, subTexture, entityID); } - void Renderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, - const std::shared_ptr &subTexture, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, + const std::shared_ptr &subTexture, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); if (m_storage->indexCount >= m_storage->maxIndices) flushAndReset(); @@ -301,19 +301,19 @@ namespace nexo::renderer { m_storage->stats.quadCount++; } - void Renderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const float rotation, + void NxRenderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const float rotation, const glm::vec4 &color, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); drawQuad({pos.x, pos.y, 0.0f}, size, rotation, color, entityID); } - void Renderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, float rotation, const glm::vec4 &color, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, float rotation, const glm::vec4 &color, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); if (m_storage->indexCount >= m_storage->maxIndices) flushAndReset(); @@ -333,20 +333,20 @@ namespace nexo::renderer { m_storage->stats.quadCount++; } - void Renderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const float rotation, - const std::shared_ptr &texture, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const float rotation, + const std::shared_ptr &texture, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); drawQuad({pos.x, pos.y, 0.0f}, size, rotation, texture, entityID); } - void Renderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const float rotation, - const std::shared_ptr &texture, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const float rotation, + const std::shared_ptr &texture, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); if (m_storage->indexCount >= m_storage->maxIndices) flushAndReset(); @@ -369,20 +369,20 @@ namespace nexo::renderer { m_storage->stats.quadCount++; } - void Renderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const float rotation, - const std::shared_ptr &subTexture, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const float rotation, + const std::shared_ptr &subTexture, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); drawQuad({pos.x, pos.y, 0.0f}, size, rotation, subTexture, entityID); } - void Renderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const float rotation, - const std::shared_ptr &subTexture, int entityID) const + void NxRenderer2D::drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const float rotation, + const std::shared_ptr &subTexture, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_2D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_2D, "Renderer not rendering a scene, make sure to call beginScene first"); if (m_storage->indexCount >= m_storage->maxIndices) flushAndReset(); @@ -400,18 +400,18 @@ namespace nexo::renderer { m_storage->stats.quadCount++; } - void Renderer2D::resetStats() const + void NxRenderer2D::resetStats() const { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_2D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_2D); m_storage->stats.drawCalls = 0; m_storage->stats.quadCount = 0; } - RendererStats Renderer2D::getStats() const + NxRendererStats NxRenderer2D::getStats() const { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_2D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_2D); return m_storage->stats; } diff --git a/engine/src/renderer/Renderer2D.hpp b/engine/src/renderer/Renderer2D.hpp index 9619d0cda..fa54d8d45 100644 --- a/engine/src/renderer/Renderer2D.hpp +++ b/engine/src/renderer/Renderer2D.hpp @@ -22,7 +22,7 @@ namespace nexo::renderer { - struct QuadVertex { + struct NxQuadVertex { glm::vec3 position; glm::vec4 color; glm::vec2 texCoord; @@ -32,7 +32,7 @@ namespace nexo::renderer { int entityID; }; - struct RendererStats { + struct NxRendererStats { unsigned int drawCalls = 0; unsigned int quadCount = 0; @@ -40,37 +40,37 @@ namespace nexo::renderer { [[nodiscard]] unsigned int getTotalIndexCount() const { return quadCount * 6; } }; - struct Renderer2DStorage { + struct NxRenderer2DStorage { const unsigned int maxQuads = 10000; const unsigned int maxVertices = maxQuads * 4; const unsigned int maxIndices = maxQuads * 6; static constexpr unsigned int maxTextureSlots = 32; - std::shared_ptr textureShader; - std::shared_ptr vertexArray; - std::shared_ptr vertexBuffer; - std::shared_ptr indexBuffer; - std::shared_ptr whiteTexture; + std::shared_ptr textureShader; + std::shared_ptr vertexArray; + std::shared_ptr vertexBuffer; + std::shared_ptr indexBuffer; + std::shared_ptr whiteTexture; unsigned int indexCount = 0; - std::array vertexBufferBase; + std::array vertexBufferBase; std::array indexBufferBase; - QuadVertex* vertexBufferPtr = nullptr; + NxQuadVertex* vertexBufferPtr = nullptr; unsigned int *indexBufferPtr = nullptr; - std::array, maxTextureSlots> textureSlots; + std::array, maxTextureSlots> textureSlots; unsigned int textureSlotIndex = 1; glm::vec4 quadVertexPositions[4]; - RendererStats stats; + NxRendererStats stats; }; /** - * @class Renderer2D + * @class NxRenderer2D * @brief Provides a 2D rendering system for drawing quads, textures, and sprites. * - * The `Renderer2D` class is a high-performance 2D rendering engine that supports + * The `NxRenderer2D` class is a high-performance 2D rendering engine that supports * batching, texture binding, and transformation. * * Features: @@ -91,17 +91,17 @@ 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 Renderer2D { + class NxRenderer2D { public: /** - * @brief Destroys the Renderer2D instance and releases resources. + * @brief Destroys the NxRenderer2D instance and releases resources. * * Ensures proper cleanup of the internal storage and associated buffers. */ - ~Renderer2D() = default; + ~NxRenderer2D() = default; /** - * @brief Initializes the Renderer2D and allocates required resources. + * @brief Initializes the NxRenderer2D and allocates required resources. * * This method sets up the internal storage, including vertex arrays, buffers, * textures, and shaders. It also predefines the vertex positions for quads. @@ -121,13 +121,13 @@ namespace nexo::renderer { void init(); /** - * @brief Shuts down the Renderer2D and releases allocated resources. + * @brief Shuts down the NxRenderer2D and releases allocated resources. * * This method deletes internal storage, including vertex and index buffers, * and resets the internal storage pointer. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. + * - NxRendererNotInitialized if the renderer is not initialized. */ void shutdown(); @@ -140,8 +140,8 @@ namespace nexo::renderer { * @param viewProjection The combined view and projection matrix for the scene. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. - * - RendererSceneLifeCycleFailure if called without proper initialization. + * - NxRendererNotInitialized if the renderer is not initialized. + * - NxRendererSceneLifeCycleFailure if called without proper initialization. */ void beginScene(const glm::mat4 &viewProjection); @@ -152,8 +152,8 @@ namespace nexo::renderer { * and resets internal buffers for the next frame. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. - * - RendererSceneLifeCycleFailure if no scene was started with `beginScene()`. + * - NxRendererNotInitialized if the renderer is not initialized. + * - NxRendererSceneLifeCycleFailure if no scene was started with `beginScene()`. */ void endScene() const; void flush() const; @@ -181,11 +181,11 @@ namespace nexo::renderer { * Overloaded for: * - 2D position (`glm::vec2`) and 3D position (`glm::vec3`). */ - void drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const std::shared_ptr &texture, int entityID = -1) const; - void drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const std::shared_ptr &texture, int entityID = -1) const; + void drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const std::shared_ptr &texture, int entityID = -1) const; + void drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const std::shared_ptr &texture, int entityID = -1) const; - void drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const std::shared_ptr &subTexture, int entityID = -1) const; - void drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const std::shared_ptr &subTexture, int entityID = -1) const; + void drawQuad(const glm::vec2 &pos, const glm::vec2 &size, const std::shared_ptr &subTexture, int entityID = -1) const; + void drawQuad(const glm::vec3 &pos, const glm::vec2 &size, const std::shared_ptr &subTexture, int entityID = -1) const; /** @@ -203,43 +203,43 @@ namespace nexo::renderer { void drawQuad(const glm::vec2 &pos, const glm::vec2 &size, float rotation, const glm::vec4 &color, int entityID = -1) const; void drawQuad(const glm::vec3 &pos, const glm::vec2 &size, float rotation, const glm::vec4 &color, int entityID = -1) const; - void drawQuad(const glm::vec2 &pos, const glm::vec2 &size, float rotation, const std::shared_ptr &texture, int entityID = -1) const; - void drawQuad(const glm::vec3 &pos, const glm::vec2 &size, float rotation, const std::shared_ptr &texture, int entityID = -1) const; + void drawQuad(const glm::vec2 &pos, const glm::vec2 &size, float rotation, const std::shared_ptr &texture, int entityID = -1) const; + void drawQuad(const glm::vec3 &pos, const glm::vec2 &size, float rotation, const std::shared_ptr &texture, int entityID = -1) const; - void drawQuad(const glm::vec2 &pos, const glm::vec2 &size, float rotation, const std::shared_ptr &subTexture, int entityID = -1) const; - void drawQuad(const glm::vec3 &pos, const glm::vec2 &size, float rotation, const std::shared_ptr &subTexture, int entityID = -1) const; + void drawQuad(const glm::vec2 &pos, const glm::vec2 &size, float rotation, const std::shared_ptr &subTexture, int entityID = -1) const; + void drawQuad(const glm::vec3 &pos, const glm::vec2 &size, float rotation, const std::shared_ptr &subTexture, int entityID = -1) const; /** * @brief Resets rendering statistics. * - * Clears the draw call and quad counters in `RendererStats`. + * Clears the draw call and quad counters in `NxRendererStats`. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. + * - NxRendererNotInitialized if the renderer is not initialized. */ void resetStats() const; /** * @brief Retrieves the current rendering statistics. * - * @return A `RendererStats` struct containing the number of draw calls and + * @return A `NxRendererStats` struct containing the number of draw calls and * quads rendered. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. + * - NxRendererNotInitialized if the renderer is not initialized. */ - [[nodiscard]] RendererStats getStats() const; + [[nodiscard]] NxRendererStats getStats() const; - std::shared_ptr getInternalStorage() const { return m_storage; }; + std::shared_ptr getInternalStorage() const { return m_storage; }; private: - std::shared_ptr m_storage; + std::shared_ptr m_storage; bool m_renderingScene = false; void flushAndReset() const; // Helper functions void generateQuadVertices(const glm::mat4 &transform, glm::vec4 color, float textureIndex, const glm::vec2 *textureCoords, int entityID) const; - [[nodiscard]] float getTextureIndex(const std::shared_ptr &texture) const; + [[nodiscard]] float getTextureIndex(const std::shared_ptr &texture) const; }; } diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index 942014801..f5ae4b5fc 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -26,21 +26,21 @@ namespace nexo::renderer { - void Renderer3D::init() + void NxRenderer3D::init() { - m_storage = std::make_shared(); + m_storage = std::make_shared(); m_storage->vertexArray = createVertexArray(); - m_storage->vertexBuffer = createVertexBuffer(m_storage->maxVertices * sizeof(Vertex)); + m_storage->vertexBuffer = createVertexBuffer(m_storage->maxVertices * sizeof(NxVertex)); // Layout - const BufferLayout cubeVertexBufferLayout = { - {ShaderDataType::FLOAT3, "aPos"}, - {ShaderDataType::FLOAT2, "aTexCoord"}, - {ShaderDataType::FLOAT3, "aNormal"}, - {ShaderDataType::FLOAT3, "aTangent"}, - {ShaderDataType::FLOAT3, "aBiTangent"}, - {ShaderDataType::INT, "aEntityID"} + const NxBufferLayout cubeVertexBufferLayout = { + {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); @@ -49,13 +49,13 @@ namespace nexo::renderer { m_storage->vertexArray->setIndexBuffer(m_storage->indexBuffer); // Texture - m_storage->whiteTexture = Texture2D::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(Renderer3DStorage::maxTextureSlots); ++i) + std::array samplers{}; + for (int i = 0; i < static_cast(NxRenderer3DStorage::maxTextureSlots); ++i) samplers[i] = i; auto phong = m_storage->shaderLibrary.load("Phong", Path::resolvePathRelativeToExe( @@ -85,17 +85,17 @@ namespace nexo::renderer { LOG(NEXO_DEV, "Renderer3D initialized"); } - void Renderer3D::shutdown() + void NxRenderer3D::shutdown() { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_3D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); m_storage.reset(); } - void Renderer3D::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(RendererNotInitialized, RendererType::RENDERER_3D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); if (shader.empty()) m_storage->currentSceneShader = m_storage->shaderLibrary.get("Phong"); else @@ -113,12 +113,12 @@ namespace nexo::renderer { m_renderingScene = true; } - void Renderer3D::endScene() const + void NxRenderer3D::endScene() const { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_3D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + 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) - @@ -133,14 +133,14 @@ namespace nexo::renderer { flushAndReset(); } - void Renderer3D::flush() const + void NxRenderer3D::flush() const { m_storage->currentSceneShader->bind(); for (unsigned int i = 0; i < m_storage->textureSlotIndex; ++i) { m_storage->textureSlots[i]->bind(i); } - RenderCommand::drawIndexed(m_storage->vertexArray, m_storage->indexCount); + NxRenderCommand::drawIndexed(m_storage->vertexArray, m_storage->indexCount); m_storage->stats.drawCalls++; m_storage->vertexArray->unbind(); m_storage->vertexBuffer->unbind(); @@ -151,7 +151,7 @@ namespace nexo::renderer { } } - void Renderer3D::flushAndReset() const + void NxRenderer3D::flushAndReset() const { flush(); m_storage->indexCount = 0; @@ -160,7 +160,7 @@ namespace nexo::renderer { m_storage->textureSlotIndex = 1; } - int Renderer3D::getTextureIndex(const std::shared_ptr &texture) const + int NxRenderer3D::getTextureIndex(const std::shared_ptr &texture) const { int textureIndex = 0.0f; @@ -186,10 +186,10 @@ namespace nexo::renderer { return textureIndex; } - void Renderer3D::setMaterialUniforms(const renderer::InternalMaterial& material) const + void NxRenderer3D::setMaterialUniforms(const renderer::NxIndexedMaterial& material) const { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_3D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); m_storage->currentSceneShader->setUniformFloat4("uMaterial.albedoColor", material.albedoColor); m_storage->currentSceneShader->setUniformInt("uMaterial.albedoTexIndex", material.albedoTexIndex); @@ -205,18 +205,18 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformInt("uMaterial.opacityTexIndex", material.opacityTexIndex); } - void Renderer3D::resetStats() const + void NxRenderer3D::resetStats() const { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_3D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); m_storage->stats.drawCalls = 0; m_storage->stats.cubeCount = 0; } - Renderer3DStats Renderer3D::getStats() const + NxRenderer3DStats NxRenderer3D::getStats() const { if (!m_storage) - THROW_EXCEPTION(RendererNotInitialized, RendererType::RENDERER_3D); + THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); return m_storage->stats; } diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index 45f3c9b7d..ef90f671e 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -25,7 +25,7 @@ namespace nexo::renderer { - struct Vertex { + struct NxVertex { glm::vec3 position; glm::vec2 texCoord; glm::vec3 normal; @@ -35,7 +35,7 @@ namespace nexo::renderer { int entityID; }; - struct InternalMaterial { + struct NxIndexedMaterial { glm::vec4 albedoColor = glm::vec4(1.0f); int albedoTexIndex = 0; // Default: 0 (white texture) glm::vec4 specularColor = glm::vec4(1.0f); @@ -50,7 +50,7 @@ namespace nexo::renderer { int opacityTexIndex = 0; // Default: 0 (white texture) }; - struct Material { + struct NxMaterial { glm::vec4 albedoColor = glm::vec4(1.0f); glm::vec4 specularColor = glm::vec4(1.0f); glm::vec3 emissiveColor = glm::vec3(0.0f); @@ -59,17 +59,17 @@ namespace nexo::renderer { 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 albedoTexture = nullptr; + std::shared_ptr normalMap = nullptr; + std::shared_ptr metallicMap = nullptr; + std::shared_ptr roughnessMap = nullptr; + std::shared_ptr emissiveMap = nullptr; - std::optional> shader = std::nullopt; + std::optional> shader = std::nullopt; }; //TODO: Add stats for the meshes - struct Renderer3DStats { + struct NxRenderer3DStats { unsigned int drawCalls = 0; unsigned int cubeCount = 0; @@ -78,8 +78,8 @@ namespace nexo::renderer { }; /** - * @struct Renderer3DStorage - * @brief Holds internal data and resources used by Renderer3D. + * @struct NxRenderer3DStorage + * @brief Holds internal data and resources used by NxRenderer3D. * * Members: * - `maxCubes`, `maxVertices`, `maxIndices`: Limits for cubes, vertices, and indices. @@ -91,7 +91,7 @@ namespace nexo::renderer { * - `vertexBufferPtr`, `indexBufferPtr`: Current pointers for batching vertices and indices. * - `stats`: Rendering statistics. */ - struct Renderer3DStorage { + struct NxRenderer3DStorage { const unsigned int maxCubes = 10000; const unsigned int maxVertices = maxCubes * 8; const unsigned int maxIndices = maxCubes * 36; @@ -102,29 +102,29 @@ namespace nexo::renderer { ShaderLibrary shaderLibrary; - std::shared_ptr currentSceneShader = nullptr; - std::shared_ptr vertexArray; - std::shared_ptr vertexBuffer; - std::shared_ptr indexBuffer; - std::shared_ptr whiteTexture; + std::shared_ptr currentSceneShader = nullptr; + std::shared_ptr vertexArray; + std::shared_ptr vertexBuffer; + std::shared_ptr indexBuffer; + std::shared_ptr whiteTexture; unsigned int indexCount = 0; - std::array vertexBufferBase; + std::array vertexBufferBase; std::array indexBufferBase; - Vertex* vertexBufferPtr = nullptr; + NxVertex* vertexBufferPtr = nullptr; unsigned int *indexBufferPtr = nullptr; - std::array, maxTextureSlots> textureSlots; + std::array, maxTextureSlots> textureSlots; unsigned int textureSlotIndex = 1; - Renderer3DStats stats; + NxRenderer3DStats stats; }; /** - * @class Renderer3D + * @class NxRenderer3D * @brief Provides a high-performance 3D rendering system for drawing cubes, textured objects, and meshes. * - * The `Renderer3D` class facilitates efficient rendering of 3D objects using batching, + * The `NxRenderer3D` class facilitates efficient rendering of 3D objects using batching, * texture binding, and transformation matrices. It supports dynamic vertex and index * buffers, enabling high performance for drawing multiple 3D primitives. * @@ -146,10 +146,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 Renderer3D { + class NxRenderer3D { public: /** - * @brief Initializes the Renderer3D and allocates required resources. + * @brief Initializes the NxRenderer3D and allocates required resources. * * Sets up internal storage, vertex buffers, index buffers, and texture samplers. * Prepares the default white texture and initializes the texture shader. @@ -169,12 +169,12 @@ namespace nexo::renderer { void init(); /** - * @brief Releases all resources and cleans up the Renderer3D. + * @brief Releases all resources and cleans up the NxRenderer3D. * * Deletes allocated vertex and index buffers and resets internal storage pointers. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. + * - NxRendererNotInitialized if the renderer is not initialized. */ void shutdown(); @@ -188,8 +188,8 @@ namespace nexo::renderer { * @param cameraPos The position of the camera in the scene. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. - * - RendererSceneLifeCycleFailure if called without proper initialization. + * - NxRendererNotInitialized if the renderer is not initialized. + * - NxRendererSceneLifeCycleFailure if called without proper initialization. */ void beginScene(const glm::mat4& viewProjection, const glm::vec3 &cameraPos, const std::string &shader = ""); @@ -200,8 +200,8 @@ namespace nexo::renderer { * and resets buffers for the next frame. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. - * - RendererSceneLifeCycleFailure if no scene was started with `beginScene()`. + * - NxRendererNotInitialized if the renderer is not initialized. + * - NxRendererSceneLifeCycleFailure if no scene was started with `beginScene()`. */ void endScene() const; @@ -216,7 +216,7 @@ namespace nexo::renderer { * @param color The color (RGBA) of the cube. * @param entityID An optional entity identifier (default is -1). * - * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. + * @throws NxRendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ void drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec4& color, int entityID = -1) const; @@ -232,7 +232,7 @@ namespace nexo::renderer { * @param color The color (RGBA) of the cube. * @param entityID An optional entity identifier (default is -1). * - * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. + * @throws NxRendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ void drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3 &rotation, const glm::vec4& color, int entityID = -1) const; @@ -246,7 +246,7 @@ namespace nexo::renderer { * @param color The color (RGBA) of the cube. * @param entityID An optional entity identifier (default is -1). * - * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. + * @throws NxRendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ void drawCube(const glm::mat4& transform, const glm::vec4& color, int entityID = -1) const; @@ -261,9 +261,9 @@ namespace nexo::renderer { * @param material The material properties of the cube. * @param entityID An optional entity identifier (default is -1). * - * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. + * @throws NxRendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ - void drawCube(const glm::vec3& position, const glm::vec3& size, const Material& material, int entityID = -1) const; + void drawCube(const glm::vec3& position, const glm::vec3& size, const NxMaterial& material, int entityID = -1) const; /** * @brief Draws a cube using a specified transformation and material. @@ -277,9 +277,9 @@ namespace nexo::renderer { * @param material The material properties of the cube. * @param entityID An optional entity identifier (default is -1). * - * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. + * @throws NxRendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ - void drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const Material& material, int entityID = -1) const; + void drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const NxMaterial& material, int entityID = -1) const; /** * @brief Draws a cube using a specified transformation and material. @@ -293,9 +293,9 @@ namespace nexo::renderer { * @param material The material properties of the cube. * @param entityID An optional entity identifier (default is -1). * - * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. + * @throws NxRendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ - void drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const Material& material, int entityID = -1) const; + void drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const NxMaterial& material, int entityID = -1) const; /** * @brief Draws a cube using a specified transformation and color. @@ -307,9 +307,9 @@ namespace nexo::renderer { * @param material The material properties of the cube. * @param entityID An optional entity identifier (default is -1). * - * @throws RendererSceneLifeCycleFailure if the renderer is not in a valid scene. + * @throws NxRendererSceneLifeCycleFailure if the renderer is not in a valid scene. */ - void drawCube(const glm::mat4& transform, const Material& material, int entityID = -1) const; + void drawCube(const glm::mat4& transform, const NxMaterial& material, int entityID = -1) const; /** * @brief Draws a custom 3D mesh. @@ -321,13 +321,13 @@ namespace nexo::renderer { * @param texture Optional texture to apply to the mesh. * * Throws: - * - RendererSceneLifeCycleFailure if no scene was started with `beginScene()`. + * - NxRendererSceneLifeCycleFailure if no scene was started with `beginScene()`. */ - void drawMesh(const std::vector& vertices, const std::vector& indices, const std::shared_ptr& texture, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const std::shared_ptr& texture, int entityID = -1) const; - void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& size, const renderer::Material& material, int entityID = -1) const; - void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& size, const renderer::Material& material, int entityID = -1) const; - void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::mat4& transform, const renderer::Material& material, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& size, const renderer::NxMaterial& material, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& size, const renderer::NxMaterial& material, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::mat4& transform, const renderer::NxMaterial& material, int entityID = -1) const; void drawBillboard(const glm::vec3& position, const glm::vec2& size, const glm::vec4& color, int entityID) const; void drawBillboard(const glm::vec3& position, const glm::vec2& size, const components::Material& material, int entityID) const; @@ -335,28 +335,28 @@ namespace nexo::renderer { /** * @brief Resets rendering statistics. * - * Clears the draw call and cube counters in `Renderer3DStats`. + * Clears the draw call and cube counters in `NxRenderer3DStats`. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. + * - NxRendererNotInitialized if the renderer is not initialized. */ void resetStats() const; /** * @brief Retrieves the current rendering statistics. * - * @return A `Renderer3DStats` struct containing the number of draw calls and + * @return A `NxRenderer3DStats` struct containing the number of draw calls and * cubes rendered. * * Throws: - * - RendererNotInitialized if the renderer is not initialized. + * - NxRendererNotInitialized if the renderer is not initialized. */ - [[nodiscard]] Renderer3DStats getStats() const; + [[nodiscard]] NxRenderer3DStats getStats() const; - std::shared_ptr &getShader() const {return m_storage->currentSceneShader;}; + std::shared_ptr &getShader() const {return m_storage->currentSceneShader;}; - std::shared_ptr getInternalStorage() const { return m_storage; }; + std::shared_ptr getInternalStorage() const { return m_storage; }; private: - std::shared_ptr m_storage; + std::shared_ptr m_storage; bool m_renderingScene = false; /** @@ -379,7 +379,7 @@ namespace nexo::renderer { * @param texture The texture to look up. * @return float The texture index. */ - [[nodiscard]] int getTextureIndex(const std::shared_ptr& texture) const; + [[nodiscard]] int getTextureIndex(const std::shared_ptr& texture) const; /** * @brief Sets material-related uniforms in the texture shader. @@ -388,9 +388,9 @@ namespace nexo::renderer { * * @param material The material whose properties are to be set. * - * @throws RendererNotInitialized if the renderer is not initialized. + * @throws NxRendererNotInitialized if the renderer is not initialized. */ - void setMaterialUniforms(const renderer::InternalMaterial& material) const; + void setMaterialUniforms(const renderer::NxIndexedMaterial& material) const; }; } diff --git a/engine/src/renderer/RendererAPI.hpp b/engine/src/renderer/RendererAPI.hpp index bf0a5d7ed..c23bc5580 100644 --- a/engine/src/renderer/RendererAPI.hpp +++ b/engine/src/renderer/RendererAPI.hpp @@ -21,10 +21,10 @@ namespace nexo::renderer { /** - * @class RendererApi + * @class NxRendererApi * @brief Abstract interface for low-level rendering API implementations. * - * The `RendererApi` class defines the essential methods required for interacting + * 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 @@ -36,11 +36,11 @@ namespace nexo::renderer { * - Support commands for clearing buffers, setting viewport size, and drawing. * * Subclasses: - * - `OpenGlRendererApi`: Implements this interface using OpenGL commands. + * - `NxOpenGlRendererApi`: Implements this interface using OpenGL commands. */ - class RendererApi { + class NxRendererApi { public: - virtual ~RendererApi() = default; + virtual ~NxRendererApi() = default; /** * @brief Initializes the graphics API. @@ -119,14 +119,14 @@ namespace nexo::renderer { * @brief Issues a draw call for indexed geometry. * * Renders geometry using indices stored in the index buffer attached to the - * specified `VertexArray`. + * specified `NxVertexArray`. * - * @param vertexArray A shared pointer to the `VertexArray` containing vertex and index data. + * @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, unsigned int count = 0) = 0; + virtual void drawIndexed(const std::shared_ptr &vertexArray, unsigned int count = 0) = 0; virtual void drawUnIndexed(unsigned int verticesCount) = 0; diff --git a/engine/src/renderer/RendererContext.hpp b/engine/src/renderer/RendererContext.hpp index 093e36b83..18b904e2d 100644 --- a/engine/src/renderer/RendererContext.hpp +++ b/engine/src/renderer/RendererContext.hpp @@ -17,10 +17,10 @@ #include "Renderer3D.hpp" namespace nexo::renderer { - class RendererContext { + class NxRendererContext { public: - RendererContext() = default; - Renderer2D renderer2D; - Renderer3D renderer3D; + NxRendererContext() = default; + NxRenderer2D renderer2D; + NxRenderer3D renderer3D; }; } diff --git a/engine/src/renderer/RendererExceptions.hpp b/engine/src/renderer/RendererExceptions.hpp index 26e176ed6..e425bab18 100644 --- a/engine/src/renderer/RendererExceptions.hpp +++ b/engine/src/renderer/RendererExceptions.hpp @@ -20,46 +20,46 @@ namespace nexo::renderer { - class OutOfRangeException final : public Exception { + class NxOutOfRangeException final : public Exception { public: - explicit OutOfRangeException(unsigned int index, unsigned int size, + explicit NxOutOfRangeException(unsigned int index, unsigned int size, const std::source_location loc = std::source_location::current()) : Exception(std::format("Index {} is out of range [0, {})", index, size), loc) {} }; - class FileNotFoundException final : public Exception { + class NxFileNotFoundException final : public Exception { public: - explicit FileNotFoundException(const std::string &filePath, + explicit NxFileNotFoundException(const std::string &filePath, const std::source_location loc = std::source_location::current()) : Exception(std::format("File not found: {}", filePath), loc) {} }; - class UnknownGraphicsApi final : public Exception { + class NxUnknownGraphicsApi final : public Exception { public: - explicit UnknownGraphicsApi(const std::string &backendApiName, + explicit NxUnknownGraphicsApi(const std::string &backendApiName, const std::source_location loc = std::source_location::current()) : Exception(std::format("Unknown graphics API: {}", backendApiName), loc) {} }; - class GraphicsApiInitFailure final : public Exception { + class NxGraphicsApiInitFailure final : public Exception { public: - explicit GraphicsApiInitFailure(const std::string &backendApiName, + 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 GraphicsApiNotInitialized final : public Exception { + class NxGraphicsApiNotInitialized final : public Exception { public: - explicit GraphicsApiNotInitialized(const std::string &backendApiName, + 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 GraphicsApiViewportResizingFailure final : public Exception { + class NxGraphicsApiViewportResizingFailure final : public Exception { public: - explicit GraphicsApiViewportResizingFailure(const std::string &backendApi, const bool tooBig, + 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()) @@ -67,49 +67,49 @@ namespace nexo::renderer { backendApi, width, height, (tooBig ? "big" : "small")), loc) {} }; - class GraphicsApiWindowInitFailure final : public Exception { + class NxGraphicsApiWindowInitFailure final : public Exception { public: - explicit GraphicsApiWindowInitFailure(const std::string &backendApiName, + 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 InvalidValue final : public Exception { + class NxInvalidValue final : public Exception { public: - explicit InvalidValue(const std::string &backendApiName, const std::string &msg, + 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 ShaderCreationFailed final : public Exception { + class NxShaderCreationFailed final : public Exception { public: - explicit ShaderCreationFailed(const std::string &backendApi, const std::string &message, + 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 ShaderInvalidUniform final : public Exception { + class NxShaderInvalidUniform final : public Exception { public: - explicit ShaderInvalidUniform(const std::string &backendApi, const std::string &shaderName, + 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 FramebufferCreationFailed final : public Exception { + class NxFramebufferCreationFailed final : public Exception { public: - explicit FramebufferCreationFailed(const std::string &backendApi, + 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 FramebufferResizingFailed final : public Exception { + class NxFramebufferResizingFailed final : public Exception { public: - explicit FramebufferResizingFailed(const std::string &backendApi, const bool tooBig, + 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()) @@ -117,67 +117,67 @@ namespace nexo::renderer { backendApi, width, height, (tooBig ? "big" : "small")), loc) {} }; - class FramebufferUnsupportedColorFormat final : public Exception { + class NxFramebufferUnsupportedColorFormat final : public Exception { public: - explicit FramebufferUnsupportedColorFormat(const std::string &backendApiName, + 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 FramebufferUnsupportedDepthFormat final : public Exception { + class NxFramebufferUnsupportedDepthFormat final : public Exception { public: - explicit FramebufferUnsupportedDepthFormat(const std::string &backendApiName, + 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 FramebufferReadFailure final : public Exception { + class NxFramebufferReadFailure final : public Exception { public: - explicit FramebufferReadFailure(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) {} + 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 FramebufferInvalidIndex final : public Exception { + class NxFramebufferInvalidIndex final : public Exception { public: - explicit FramebufferInvalidIndex(const std::string &backendApiName, int index, const std::source_location loc = std::source_location::current()) : Exception(std::format("[{}] Invalid attachment index : {}", backendApiName, index), loc) {}; + 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 BufferLayoutEmpty final : public Exception { + class NxBufferLayoutEmpty final : public Exception { public: - explicit BufferLayoutEmpty(const std::string &backendApi, + 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 RendererType { + enum class NxRendererType { RENDERER_2D, RENDERER_3D }; - class RendererNotInitialized final : public Exception { + class NxRendererNotInitialized final : public Exception { public: - explicit RendererNotInitialized(const RendererType type, + 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 == RendererType::RENDERER_2D ? "[RENDERER 2D]" : "[RENDERER 3D]")), loc) + (type == NxRendererType::RENDERER_2D ? "[RENDERER 2D]" : "[RENDERER 3D]")), loc) {} }; - class RendererSceneLifeCycleFailure : public Exception { + class NxRendererSceneLifeCycleFailure final : public Exception { public: - explicit RendererSceneLifeCycleFailure(const RendererType type, const std::string &msg, + explicit NxRendererSceneLifeCycleFailure(const NxRendererType type, const std::string &msg, const std::source_location loc = std::source_location::current()) : Exception(std::format("{} {}", - (type == RendererType::RENDERER_2D ? "[RENDERER 2D]" : "[RENDERER 3D]"), msg), + (type == NxRendererType::RENDERER_2D ? "[RENDERER 2D]" : "[RENDERER 3D]"), msg), loc) {} }; - class TextureInvalidSize final : public Exception { + class NxTextureInvalidSize final : public Exception { public: - explicit TextureInvalidSize(const std::string &backendApi, + 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()) @@ -185,9 +185,9 @@ namespace nexo::renderer { backendApi, width, height, maxTextureSize), loc) {} }; - class TextureUnsupportedFormat final : public Exception { + class NxTextureUnsupportedFormat final : public Exception { public: - explicit TextureUnsupportedFormat(const std::string &backendApi, const int channels, + explicit NxTextureUnsupportedFormat(const std::string &backendApi, const int channels, const std::string &path, const std::source_location loc = std::source_location::current()) @@ -195,17 +195,17 @@ namespace nexo::renderer { backendApi, channels, path), loc) {} }; - class TextureSizeMismatch final : public Exception { + class NxTextureSizeMismatch final : public Exception { public: - explicit TextureSizeMismatch(const std::string &backendApi, const int dataSize, const int expectedSize, + explicit NxTextureSizeMismatch(const std::string &backendApi, const int dataSize, const int 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 StbiLoadException final : public Exception { + class NxStbiLoadException final : public Exception { public: - explicit StbiLoadException(const std::string &msg, + explicit NxStbiLoadException(const std::string &msg, const std::source_location loc = std::source_location::current()) : Exception(std::format("STBI load failed: {}", msg), loc) {} }; diff --git a/engine/src/renderer/Shader.cpp b/engine/src/renderer/Shader.cpp index c0f625f9d..18ccd1181 100644 --- a/engine/src/renderer/Shader.cpp +++ b/engine/src/renderer/Shader.cpp @@ -14,7 +14,7 @@ #include "Shader.hpp" #include "renderer/RendererExceptions.hpp" #include "Logger.hpp" -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlShader.hpp" #endif @@ -22,25 +22,25 @@ namespace nexo::renderer { - std::shared_ptr Shader::create(const std::string &path) + std::shared_ptr NxShader::create(const std::string &path) { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(path); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(path); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } - std::shared_ptr Shader::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 GRAPHICS_API_OPENGL - return std::make_shared(name, vertexSource, fragmentSource); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(name, vertexSource, fragmentSource); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } - std::string Shader::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) @@ -52,18 +52,18 @@ namespace nexo::renderer { in.close(); return result; } - THROW_EXCEPTION(FileNotFoundException, filepath); + THROW_EXCEPTION(NxFileNotFoundException, filepath); } - void Shader::addStorageBuffer(const std::shared_ptr &buffer) + void NxShader::addStorageBuffer(const std::shared_ptr &buffer) { m_storageBuffers.push_back(buffer); } - void Shader::setStorageBufferData(unsigned int index, void *data, unsigned int size) + void NxShader::setStorageBufferData(unsigned int index, void *data, unsigned int size) { if (index >= m_storageBuffers.size()) - THROW_EXCEPTION(OutOfRangeException, index, m_storageBuffers.size()); + THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); m_storageBuffers[index]->setData(data, size); } } diff --git a/engine/src/renderer/Shader.hpp b/engine/src/renderer/Shader.hpp index c494e662b..42e04c9ff 100644 --- a/engine/src/renderer/Shader.hpp +++ b/engine/src/renderer/Shader.hpp @@ -55,10 +55,10 @@ namespace nexo::renderer { }; /** - * @class Shader + * @class NxShader * @brief Abstract class representing a shader program in the rendering pipeline. * - * The `Shader` class provides a generic interface for creating and managing shader + * 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: @@ -67,18 +67,18 @@ namespace nexo::renderer { * - Set uniform variables to pass data from the CPU to the GPU. * * Subclasses: - * - `OpenGlShader`: Implements this interface using OpenGL-specific functionality. + * - `NxOpenGlShader`: Implements this interface using OpenGL-specific functionality. * * Example Usage: * ```cpp - * auto shader = Shader::create("path/to/shader.glsl"); + * auto shader = NxShader::create("path/to/shader.glsl"); * shader->bind(); * shader->setUniformFloat("uTime", 1.0f); * ``` */ - class Shader { + class NxShader { public: - virtual ~Shader() = default; + virtual ~NxShader() = default; /** * @brief Creates a shader program from a source file. @@ -90,10 +90,10 @@ namespace nexo::renderer { * @return A shared pointer to the created `Shader` instance. * * Throws: - * - `UnknownGraphicsApi` if no graphics API is supported. - * - `ShaderCreationFailed` if shader compilation fails. + * - `NxUnknownGraphicsApi` if no graphics API is supported. + * - `NxShaderCreationFailed` if shader compilation fails. */ - static std::shared_ptr create(const std::string &path); + static std::shared_ptr create(const std::string &path); /** * @brief Creates a shader program from source code strings. @@ -106,10 +106,10 @@ namespace nexo::renderer { * @return A shared pointer to the created `Shader` instance. * * Throws: - * - `UnknownGraphicsApi` if no graphics API is supported. - * - `ShaderCreationFailed` if shader compilation fails. + * - `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); + 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. @@ -145,7 +145,7 @@ namespace nexo::renderer { virtual bool setUniformInt(const ShaderUniforms uniform, int value) const = 0; virtual bool setUniformIntArray(const ShaderUniforms uniform, const int *values, unsigned int count) const = 0; - void addStorageBuffer(const std::shared_ptr &buffer); + void addStorageBuffer(const std::shared_ptr &buffer); void setStorageBufferData(unsigned int index, void *data, unsigned int size); virtual void bindStorageBufferBase(unsigned int index, unsigned int bindingPoint) const = 0; virtual void bindStorageBuffer(unsigned int index) const = 0; @@ -155,7 +155,7 @@ namespace nexo::renderer { virtual unsigned int getProgramId() const = 0; protected: static std::string readFile(const std::string &filepath); - std::vector> m_storageBuffers; + std::vector> m_storageBuffers; std::unordered_map m_uniformLocations; }; diff --git a/engine/src/renderer/ShaderStorageBuffer.cpp b/engine/src/renderer/ShaderStorageBuffer.cpp index 6c0b11d80..878484e85 100644 --- a/engine/src/renderer/ShaderStorageBuffer.cpp +++ b/engine/src/renderer/ShaderStorageBuffer.cpp @@ -15,16 +15,16 @@ #include "ShaderStorageBuffer.hpp" #include "renderer/RendererExceptions.hpp" #include -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlShaderStorageBuffer.hpp" #endif namespace nexo::renderer { - std::shared_ptr ShaderStorageBuffer::create(unsigned int size) + std::shared_ptr NxShaderStorageBuffer::create(unsigned int size) { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(size); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(size); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } } diff --git a/engine/src/renderer/ShaderStorageBuffer.hpp b/engine/src/renderer/ShaderStorageBuffer.hpp index a042df9aa..56b3a1e3e 100644 --- a/engine/src/renderer/ShaderStorageBuffer.hpp +++ b/engine/src/renderer/ShaderStorageBuffer.hpp @@ -17,11 +17,11 @@ #include namespace nexo::renderer { - class ShaderStorageBuffer { + class NxShaderStorageBuffer { public: - virtual ~ShaderStorageBuffer() = default; + virtual ~NxShaderStorageBuffer() = default; - static std::shared_ptr create(unsigned int size); + static std::shared_ptr create(unsigned int size); virtual void bind() const = 0; virtual void bindBase(unsigned int bindingLocation) const = 0; diff --git a/engine/src/renderer/SubTexture2D.cpp b/engine/src/renderer/SubTexture2D.cpp index 50cb31031..5ec3e1046 100644 --- a/engine/src/renderer/SubTexture2D.cpp +++ b/engine/src/renderer/SubTexture2D.cpp @@ -14,7 +14,7 @@ #include "SubTexture2D.hpp" namespace nexo::renderer { - SubTexture2D::SubTexture2D(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 +23,11 @@ namespace nexo::renderer { m_texCoords[3] = {min.x, max.y}; } - std::shared_ptr SubTexture2D::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())}; - return std::make_shared(texture, min, max); + return std::make_shared(texture, min, max); } } \ No newline at end of file diff --git a/engine/src/renderer/SubTexture2D.hpp b/engine/src/renderer/SubTexture2D.hpp index 197a54daf..1062e5722 100644 --- a/engine/src/renderer/SubTexture2D.hpp +++ b/engine/src/renderer/SubTexture2D.hpp @@ -19,10 +19,10 @@ namespace nexo::renderer { /** - * @class SubTexture2D + * @class NxSubTexture2D * @brief Represents a portion of a 2D texture, useful for sprite rendering. * - * The `SubTexture2D` class allows defining a sub-region within a larger texture. + * 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. * @@ -33,26 +33,26 @@ namespace nexo::renderer { * * Example Usage: * ```cpp - * auto texture = Texture2D::create("path/to/texture.png"); - * auto subTexture = SubTexture2D::createFromCoords(texture, {1, 1}, {64, 64}); + * auto texture = NxTexture2D::create("path/to/texture.png"); + * auto subTexture = NxSubTexture2D::createFromCoords(texture, {1, 1}, {64, 64}); * ``` */ - class SubTexture2D { + class NxSubTexture2D { public: /** - * @brief Constructs a `SubTexture2D` from specified texture coordinates. + * @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 `Texture2D`. + * @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. */ - SubTexture2D(const std::shared_ptr &texture, const glm::vec2 &min, const glm::vec2 &max); + NxSubTexture2D(const std::shared_ptr &texture, const glm::vec2 &min, const glm::vec2 &max); - [[nodiscard]] const std::shared_ptr &getTexture() const { return m_texture; }; + [[nodiscard]] const std::shared_ptr &getTexture() const { return m_texture; }; /** * @brief Retrieves the texture coordinates for the subtexture. * @@ -68,21 +68,21 @@ namespace nexo::renderer { [[nodiscard]] const glm::vec2 *getTextureCoords() const { return m_texCoords; }; /** - * @brief Creates a `SubTexture2D` from grid-based coordinates within a 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 `Texture2D`. + * @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 `SubTexture2D` instance. + * @return A shared pointer to the created `NxSubTexture2D` instance. * * Example: * ```cpp - * auto texture = Texture2D::create("path/to/spritesheet.png"); - * auto subTexture = SubTexture2D::createFromCoords(texture, {1, 1}, {64, 64}); + * auto texture = NxTexture2D::create("path/to/spritesheet.png"); + * auto subTexture = NxSubTexture2D::createFromCoords(texture, {1, 1}, {64, 64}); * ``` * * Example Explanation: @@ -90,9 +90,9 @@ namespace nexo::renderer { * - 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}); + 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; + std::shared_ptr m_texture; glm::vec2 m_texCoords[4]{}; }; -} \ No newline at end of file +} diff --git a/engine/src/renderer/Texture.cpp b/engine/src/renderer/Texture.cpp index 2c69e4137..740b588fe 100644 --- a/engine/src/renderer/Texture.cpp +++ b/engine/src/renderer/Texture.cpp @@ -15,34 +15,34 @@ #include "Texture.hpp" #include "Renderer.hpp" #include "renderer/RendererExceptions.hpp" -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlTexture2D.hpp" #endif namespace nexo::renderer { - std::shared_ptr Texture2D::create(unsigned int width, unsigned int height) + std::shared_ptr NxTexture2D::create(unsigned int width, unsigned int height) { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(width, height); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(width, height); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } - std::shared_ptr Texture2D::create(uint8_t* buffer, unsigned int len) + std::shared_ptr NxTexture2D::create(uint8_t* buffer, unsigned int len) { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(buffer, len); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(buffer, len); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } - std::shared_ptr Texture2D::create(const std::string &path) + std::shared_ptr NxTexture2D::create(const std::string &path) { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(path); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(path); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } } diff --git a/engine/src/renderer/Texture.hpp b/engine/src/renderer/Texture.hpp index 9dc878c71..209417ac5 100644 --- a/engine/src/renderer/Texture.hpp +++ b/engine/src/renderer/Texture.hpp @@ -19,7 +19,7 @@ namespace nexo::renderer { /** - * @class Texture + * @class NxTexture * @brief Abstract base class for representing textures in a rendering system. * * The `Texture` class provides a common interface for managing texture resources @@ -31,12 +31,12 @@ namespace nexo::renderer { * - Manage texture data. * - Bind and unbind textures to specific texture slots. * - * Derived classes (e.g., `OpenGlTexture2D`) implement platform-specific behavior for + * Derived classes (e.g., `NxOpenGlTexture2D`) implement platform-specific behavior for * managing textures in different rendering backends. */ - class Texture { + class NxTexture { public: - virtual ~Texture() = default; + virtual ~NxTexture() = default; [[nodiscard]] virtual unsigned int getWidth() const = 0; [[nodiscard]] virtual unsigned int getHeight() const = 0; @@ -49,10 +49,10 @@ namespace nexo::renderer { virtual void setData(void *data, unsigned int size) = 0; - bool operator==(const Texture &other) const { return this->getId() == other.getId(); }; + bool operator==(const NxTexture &other) const { return this->getId() == other.getId(); }; }; - class Texture2D : public Texture { + class NxTexture2D : public NxTexture { public: /** * @brief Creates a blank 2D texture with the specified dimensions. @@ -62,14 +62,14 @@ namespace nexo::renderer { * * @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 `Texture2D` instance. + * @return A shared pointer to the created `NxTexture2D` instance. * * Example: * ```cpp - * auto blankTexture = Texture2D::create(512, 512); + * auto blankTexture = NxTexture2D::create(512, 512); * ``` */ - static std::shared_ptr create(unsigned int width, unsigned int height); + static std::shared_ptr create(unsigned int width, unsigned int height); /** * @brief Creates a 2D texture from file in memory. @@ -80,15 +80,15 @@ namespace nexo::renderer { * * @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 `Texture2D` instance. + * @return A shared pointer to the created `NxTexture2D` instance. * * Example: * ```cpp * std::vector imageData = ...; // Load image data into a buffer - * auto texture = Texture2D::create(imageData.data(), imageData.size()); + * auto texture = NxTexture2D::create(imageData.data(), imageData.size()); * ``` */ - static std::shared_ptr create(uint8_t *buffer, unsigned int len); + static std::shared_ptr create(uint8_t *buffer, unsigned int len); /** * @brief Creates a 2D texture from an image file. @@ -98,14 +98,14 @@ namespace nexo::renderer { * for rendering after creation. * * @param path The file path to the texture image. - * @return A shared pointer to the created `Texture2D` instance. + * @return A shared pointer to the created `NxTexture2D` instance. * * Example: * ```cpp - * auto texture = Texture2D::create("assets/textures/brick_wall.png"); + * auto texture = NxTexture2D::create("assets/textures/brick_wall.png"); * ``` */ - static std::shared_ptr create(const std::string &path); + static std::shared_ptr create(const std::string &path); }; } diff --git a/engine/src/renderer/VertexArray.cpp b/engine/src/renderer/VertexArray.cpp index 6c0bc499c..c60076238 100644 --- a/engine/src/renderer/VertexArray.cpp +++ b/engine/src/renderer/VertexArray.cpp @@ -13,18 +13,18 @@ /////////////////////////////////////////////////////////////////////////////// #include "VertexArray.hpp" #include "renderer/RendererExceptions.hpp" -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlVertexArray.hpp" #endif namespace nexo::renderer { - std::shared_ptr createVertexArray() + std::shared_ptr createVertexArray() { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } } diff --git a/engine/src/renderer/VertexArray.hpp b/engine/src/renderer/VertexArray.hpp index 6b4b372a7..227ea6f15 100644 --- a/engine/src/renderer/VertexArray.hpp +++ b/engine/src/renderer/VertexArray.hpp @@ -18,10 +18,10 @@ namespace nexo::renderer { /** - * @class VertexArray + * @class NxVertexArray * @brief Abstract class representing a vertex array in the rendering system. * - * The `VertexArray` class manages the collection of vertex buffers and an optional + * 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. * @@ -30,21 +30,21 @@ namespace nexo::renderer { * - Bind/unbind the vertex array for rendering. * - Provide access to underlying buffers. * - * Derived classes (e.g., `OpenGlVertexArray`) implement platform-specific behavior + * Derived classes (e.g., `NxOpenGlVertexArray`) implement platform-specific behavior * for managing vertex arrays. */ - class VertexArray { + class NxVertexArray { public: - virtual ~VertexArray() = default; + virtual ~NxVertexArray() = default; virtual void bind() const = 0; virtual void unbind() const = 0; - virtual void addVertexBuffer(const std::shared_ptr &vertexBuffer) = 0; - virtual void setIndexBuffer(const std::shared_ptr &indexBuffer) = 0; + virtual void addVertexBuffer(const std::shared_ptr &vertexBuffer) = 0; + virtual void setIndexBuffer(const std::shared_ptr &indexBuffer) = 0; - [[nodiscard]] virtual const std::vector> &getVertexBuffers() const = 0; - [[nodiscard]] virtual const std::shared_ptr &getIndexBuffer() const = 0; + [[nodiscard]] virtual const std::vector> &getVertexBuffers() const = 0; + [[nodiscard]] virtual const std::shared_ptr &getIndexBuffer() const = 0; virtual unsigned int getId() const = 0; }; @@ -53,9 +53,9 @@ namespace nexo::renderer { * @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 `VertexArray` implementation. + * of the corresponding `NxVertexArray` implementation. * - * @return A shared pointer to the created `VertexArray` instance. + * @return A shared pointer to the created `NxVertexArray` instance. */ - std::shared_ptr createVertexArray(); + std::shared_ptr createVertexArray(); } diff --git a/engine/src/renderer/Window.cpp b/engine/src/renderer/Window.cpp index 0ef65744e..aefad4add 100644 --- a/engine/src/renderer/Window.cpp +++ b/engine/src/renderer/Window.cpp @@ -14,17 +14,17 @@ #include "Window.hpp" #include "renderer/RendererExceptions.hpp" -#ifdef GRAPHICS_API_OPENGL +#ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlWindow.hpp" #endif namespace nexo::renderer { - std::shared_ptr Window::create(int width, int height, const char *title) + std::shared_ptr NxWindow::create(int width, int height, const char *title) { - #ifdef GRAPHICS_API_OPENGL - return std::make_shared(width, height, title); + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(width, height, title); #endif - THROW_EXCEPTION(UnknownGraphicsApi, "UNKNOWN"); + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } } diff --git a/engine/src/renderer/Window.hpp b/engine/src/renderer/Window.hpp index 8b8482857..976ffed41 100644 --- a/engine/src/renderer/Window.hpp +++ b/engine/src/renderer/Window.hpp @@ -30,7 +30,7 @@ namespace nexo::renderer { using MouseScrollCallback = std::function; using MouseMoveCallback = std::function; - struct WindowProperty + struct NxWindowProperty { unsigned int width; unsigned int height; @@ -44,14 +44,14 @@ namespace nexo::renderer { MouseScrollCallback mouseScrollCallback; MouseMoveCallback mouseMoveCallback; - WindowProperty(const unsigned int w, const unsigned h, const char * t) : width(w), height(h), title(t) {} + NxWindowProperty(const unsigned int w, const unsigned h, const char * t) : width(w), height(h), title(t) {} }; /** - * @class Window + * @class NxWindow * @brief Abstract class for managing window operations in the rendering system. * - * The `Window` class provides an interface for creating, configuring, and + * 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. * @@ -60,14 +60,14 @@ namespace nexo::renderer { * - Handle window properties such as size, title, and VSync. * - Provide event handling through callbacks. * - * Derived classes (e.g., `OpenGlWindow`) implement platform-specific behavior + * Derived classes (e.g., `NxOpenGlWindow`) implement platform-specific behavior * for managing windows. */ - class Window { + class NxWindow { public: - Window() = default; + NxWindow() = default; - virtual ~Window() = default; + virtual ~NxWindow() = default; virtual void init() = 0; virtual void shutdown() = 0; @@ -92,14 +92,14 @@ namespace nexo::renderer { * @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 `Window` implementation. + * 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 `Window` instance. + * @return A shared pointer to the created `NxWindow` instance. */ - static std::shared_ptr create(int width = 1920, int height = 1080, const char *title = "Nexo window"); + static std::shared_ptr create(int width = 1920, int height = 1080, const char *title = "Nexo window"); virtual void setErrorCallback(void *fctPtr) = 0; virtual void setResizeCallback(ResizeCallback callback) = 0; diff --git a/engine/src/renderer/opengl/OpenGlBuffer.cpp b/engine/src/renderer/opengl/OpenGlBuffer.cpp index 671785ef8..6028ec5bd 100644 --- a/engine/src/renderer/opengl/OpenGlBuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlBuffer.cpp @@ -19,36 +19,36 @@ namespace nexo::renderer { // VERTEX BUFFER - OpenGlVertexBuffer::OpenGlVertexBuffer(const float *vertices, const unsigned int size) + NxOpenGlVertexBuffer::NxOpenGlVertexBuffer(const float *vertices, const unsigned int size) { glGenBuffers(1, &_id); glBindBuffer(GL_ARRAY_BUFFER, _id); glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW); } - OpenGlVertexBuffer::OpenGlVertexBuffer(const unsigned int size) + NxOpenGlVertexBuffer::NxOpenGlVertexBuffer(const unsigned int size) { glGenBuffers(1, &_id); glBindBuffer(GL_ARRAY_BUFFER, _id); glBufferData(GL_ARRAY_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); } - OpenGlVertexBuffer::~OpenGlVertexBuffer() + NxOpenGlVertexBuffer::~NxOpenGlVertexBuffer() { glDeleteBuffers(1, &_id); } - void OpenGlVertexBuffer::bind() const + void NxOpenGlVertexBuffer::bind() const { glBindBuffer(GL_ARRAY_BUFFER, _id); } - void OpenGlVertexBuffer::unbind() const + void NxOpenGlVertexBuffer::unbind() const { glBindBuffer(GL_ARRAY_BUFFER, 0); } - void OpenGlVertexBuffer::setData(void *data, const unsigned int size) + void NxOpenGlVertexBuffer::setData(void *data, const unsigned int size) { glBindBuffer(GL_ARRAY_BUFFER, _id); glBufferSubData(GL_ARRAY_BUFFER, 0, size, data); @@ -57,34 +57,34 @@ namespace nexo::renderer { // INDEX BUFFER - OpenGlIndexBuffer::OpenGlIndexBuffer() + NxOpenGlIndexBuffer::NxOpenGlIndexBuffer() { glGenBuffers(1, &_id); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _id); } - OpenGlIndexBuffer::~OpenGlIndexBuffer() + NxOpenGlIndexBuffer::~NxOpenGlIndexBuffer() { glDeleteBuffers(1, &_id); } - void OpenGlIndexBuffer::bind() const + void NxOpenGlIndexBuffer::bind() const { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _id); } - void OpenGlIndexBuffer::unbind() const + void NxOpenGlIndexBuffer::unbind() const { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } - void OpenGlIndexBuffer::setData(unsigned int *indices, unsigned int count) + void NxOpenGlIndexBuffer::setData(unsigned int *indices, unsigned int count) { _count = count; glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(unsigned int), indices, GL_STATIC_DRAW); } - unsigned int OpenGlIndexBuffer::getCount() const + unsigned int NxOpenGlIndexBuffer::getCount() const { return _count; } diff --git a/engine/src/renderer/opengl/OpenGlBuffer.hpp b/engine/src/renderer/opengl/OpenGlBuffer.hpp index f960bcdbe..dfd71cca0 100644 --- a/engine/src/renderer/opengl/OpenGlBuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlBuffer.hpp @@ -18,7 +18,7 @@ namespace nexo::renderer { - class OpenGlVertexBuffer final : public VertexBuffer { + class NxOpenGlVertexBuffer final : public NxVertexBuffer { public: /** * @brief Constructs a new vertex buffer and initializes it with vertex data. @@ -34,7 +34,7 @@ namespace nexo::renderer { * - `glBindBuffer`: Binds the buffer as the current vertex buffer (GL_ARRAY_BUFFER). * - `glBufferData`: Allocates GPU memory and uploads the vertex data. */ - OpenGlVertexBuffer(const float *vertices, unsigned int size); + NxOpenGlVertexBuffer(const float *vertices, unsigned int size); /** * @brief Constructs an empty vertex buffer with a specified size. @@ -52,7 +52,7 @@ namespace nexo::renderer { * Usage: * - Call `setData` later to populate the buffer with vertex data dynamically. */ - explicit OpenGlVertexBuffer(unsigned int size); + explicit NxOpenGlVertexBuffer(unsigned int size); /** * @brief Destroys the vertex buffer and releases GPU resources. @@ -62,7 +62,7 @@ namespace nexo::renderer { * OpenGL Calls: * - `glDeleteBuffers`: Deletes the buffer object associated with the buffer ID. */ - ~OpenGlVertexBuffer() override; + ~NxOpenGlVertexBuffer() override; /** * @brief Binds the vertex buffer as the active buffer in the OpenGL context. @@ -88,8 +88,8 @@ namespace nexo::renderer { */ void unbind() const override; - void setLayout(const BufferLayout &layout) override { _layout = layout; }; - [[nodiscard]] BufferLayout getLayout() const override { return _layout; }; + void setLayout(const NxBufferLayout &layout) override { _layout = layout; }; + [[nodiscard]] NxBufferLayout getLayout() const override { return _layout; }; /** * @brief Updates the data in the vertex buffer. @@ -113,10 +113,10 @@ namespace nexo::renderer { private: unsigned int _id{}; - BufferLayout _layout; + NxBufferLayout _layout; }; - class OpenGlIndexBuffer final : public IndexBuffer { + class NxOpenGlIndexBuffer final : public NxIndexBuffer { public: /** * @brief Constructs a new OpenGL index buffer. @@ -128,7 +128,7 @@ namespace nexo::renderer { * - `glGenBuffers`: Generates a new buffer object. * - `glBindBuffer`: Binds the buffer as the current index buffer (GL_ELEMENT_ARRAY_BUFFER). */ - OpenGlIndexBuffer(); + NxOpenGlIndexBuffer(); /** * @brief Destroys the index buffer and releases GPU resources. @@ -138,7 +138,7 @@ namespace nexo::renderer { * OpenGL Calls: * - `glDeleteBuffers`: Deletes the buffer object associated with the buffer ID. */ - ~OpenGlIndexBuffer() override; + ~NxOpenGlIndexBuffer() override; /** * @brief Binds the index buffer as the active buffer in the OpenGL context. diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp index a1f9c6838..988eda37a 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp @@ -30,14 +30,14 @@ namespace nexo::renderer { * Maps internal framebuffer texture formats (e.g., RGBA8, DEPTH24STENCIL8) to their * corresponding OpenGL formats. Used during texture attachment creation. * - * @param format The `FrameBufferTextureFormats` value to convert. + * @param format The `NxFrameBufferTextureFormats` value to convert. * @return The OpenGL format (GLenum) corresponding to the specified texture format, * or -1 if the format is invalid or unsupported. */ - static int framebufferTextureFormatToOpenGlInternalFormat(FrameBufferTextureFormats format) + static int framebufferTextureFormatToOpenGlInternalFormat(NxFrameBufferTextureFormats format) { constexpr GLenum internalFormats[] = {GL_NONE, GL_RGBA8, GL_RGBA16, GL_R32I, GL_DEPTH24_STENCIL8, GL_DEPTH24_STENCIL8}; - if (static_cast(format) == 0 || format >= FrameBufferTextureFormats::NB_TEXTURE_FORMATS) + if (static_cast(format) == 0 || format >= NxFrameBufferTextureFormats::NB_TEXTURE_FORMATS) return -1; return static_cast(internalFormats[static_cast(format)]); } @@ -172,27 +172,27 @@ namespace nexo::renderer { /** * @brief Checks if a texture format is a depth format. * - * Determines whether the specified `FrameBufferTextureFormats` value corresponds to + * Determines whether the specified `NxFrameBufferTextureFormats` value corresponds to * a depth or depth-stencil format. * * @param format The texture format to check. * @return True if the format is a depth format, false otherwise. */ - static bool isDepthFormat(const FrameBufferTextureFormats format) + static bool isDepthFormat(const NxFrameBufferTextureFormats format) { switch (format) { - case FrameBufferTextureFormats::DEPTH24STENCIL8: return true; + case NxFrameBufferTextureFormats::DEPTH24STENCIL8: return true; default: return false; } } - OpenGlFramebuffer::OpenGlFramebuffer(FramebufferSpecs specs) : m_specs(std::move(specs)) + NxOpenGlFramebuffer::NxOpenGlFramebuffer(NxFramebufferSpecs specs) : m_specs(std::move(specs)) { if (!m_specs.width || !m_specs.height) - THROW_EXCEPTION(FramebufferResizingFailed, "OPENGL", false, m_specs.width, m_specs.height); + THROW_EXCEPTION(NxFramebufferResizingFailed, "OPENGL", false, m_specs.width, m_specs.height); if (m_specs.width > sMaxFramebufferSize || m_specs.height > sMaxFramebufferSize) - THROW_EXCEPTION(FramebufferResizingFailed, "OPENGL", true, m_specs.width, m_specs.height); + THROW_EXCEPTION(NxFramebufferResizingFailed, "OPENGL", true, m_specs.width, m_specs.height); for (auto format: m_specs.attachments.attachments) { if (!isDepthFormat(format.textureFormat)) @@ -203,14 +203,14 @@ namespace nexo::renderer { invalidate(); } - OpenGlFramebuffer::~OpenGlFramebuffer() + NxOpenGlFramebuffer::~NxOpenGlFramebuffer() { glDeleteFramebuffers(1, &m_id); glDeleteTextures(static_cast(m_colorAttachments.size()), m_colorAttachments.data()); glDeleteTextures(1, &m_depthAttachment); } - void OpenGlFramebuffer::invalidate() + void NxOpenGlFramebuffer::invalidate() { if (m_id) { @@ -239,22 +239,22 @@ namespace nexo::renderer { const int glTextureInternalFormat = framebufferTextureFormatToOpenGlInternalFormat( m_colorAttachmentsSpecs[i].textureFormat); if (glTextureInternalFormat == -1) - THROW_EXCEPTION(FramebufferUnsupportedColorFormat, "OPENGL"); + THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); const int glTextureFormat = framebufferTextureFormatToOpenGlFormat(m_colorAttachmentsSpecs[i].textureFormat); if (glTextureFormat == -1) - THROW_EXCEPTION(FramebufferUnsupportedColorFormat, "OPENGL"); + THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); attachColorTexture(m_colorAttachments[i], m_specs.samples, glTextureInternalFormat, glTextureFormat, m_specs.width, m_specs.height, i); } } - if (m_depthAttachmentSpec.textureFormat != FrameBufferTextureFormats::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(FramebufferUnsupportedDepthFormat, "OPENGL"); + THROW_EXCEPTION(NxFramebufferUnsupportedDepthFormat, "OPENGL"); attachDepthTexture(m_depthAttachment, m_specs.samples, glDepthFormat, GL_DEPTH_STENCIL_ATTACHMENT, m_specs.width, m_specs.height); } @@ -262,7 +262,7 @@ namespace nexo::renderer { if (m_colorAttachments.size() > 1) { if (m_colorAttachments.size() >= 4) - THROW_EXCEPTION(FramebufferCreationFailed, "OPENGL"); + THROW_EXCEPTION(NxFramebufferCreationFailed, "OPENGL"); constexpr GLenum buffers[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 }; @@ -271,12 +271,12 @@ namespace nexo::renderer { glDrawBuffer(GL_NONE); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - THROW_EXCEPTION(FramebufferCreationFailed, "OPENGL"); + THROW_EXCEPTION(NxFramebufferCreationFailed, "OPENGL"); glBindFramebuffer(GL_FRAMEBUFFER, 0); } - void OpenGlFramebuffer::bind() + void NxOpenGlFramebuffer::bind() { if (toResize) { @@ -291,43 +291,43 @@ namespace nexo::renderer { //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } - void OpenGlFramebuffer::unbind() + void NxOpenGlFramebuffer::unbind() { glBindFramebuffer(GL_FRAMEBUFFER, 0); } - unsigned int OpenGlFramebuffer::getFramebufferId() const + unsigned int NxOpenGlFramebuffer::getFramebufferId() const { return m_id; } - void OpenGlFramebuffer::resize(const unsigned int width, const unsigned int height) + void NxOpenGlFramebuffer::resize(const unsigned int width, const unsigned int height) { if (!width || !height) - THROW_EXCEPTION(FramebufferResizingFailed, "OPENGL", false, width, height); + THROW_EXCEPTION(NxFramebufferResizingFailed, "OPENGL", false, width, height); if (width > sMaxFramebufferSize || height > sMaxFramebufferSize) - THROW_EXCEPTION(FramebufferResizingFailed, "OPENGL", true, width, height); + THROW_EXCEPTION(NxFramebufferResizingFailed, "OPENGL", true, width, height); m_specs.width = width; m_specs.height = height; toResize = true; } - const glm::vec2 OpenGlFramebuffer::getSize() const + const glm::vec2 NxOpenGlFramebuffer::getSize() const { return glm::vec2(m_specs.width, m_specs.height); } - void OpenGlFramebuffer::getPixelWrapper(unsigned int attachementIndex, int x, int y, void *result, const std::type_info &ti) const + void NxOpenGlFramebuffer::getPixelWrapper(unsigned int attachementIndex, int x, 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); else - THROW_EXCEPTION(FramebufferUnsupportedColorFormat, "OPENGL"); + THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); } - void OpenGlFramebuffer::clearAttachmentWrapper(unsigned int attachmentIndex, const void *value, const std::type_info &ti) const + void NxOpenGlFramebuffer::clearAttachmentWrapper(unsigned int attachmentIndex, const void *value, const std::type_info &ti) const { // Add more types here when necessary if (ti == typeid(int)) @@ -335,7 +335,7 @@ namespace nexo::renderer { else if (ti == typeid(glm::vec4)) clearAttachmentImpl(attachmentIndex, value); else - THROW_EXCEPTION(FramebufferUnsupportedColorFormat, "OPENGL"); + THROW_EXCEPTION(NxFramebufferUnsupportedColorFormat, "OPENGL"); } } diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp index f725688ca..8facdee19 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp @@ -37,15 +37,15 @@ namespace nexo::renderer { return 0; } - static int framebufferTextureFormatToOpenGlFormat(FrameBufferTextureFormats format) + static int framebufferTextureFormatToOpenGlFormat(NxFrameBufferTextureFormats format) { constexpr GLenum formats[] = {GL_NONE, GL_RGBA, GL_RGBA, GL_RED_INTEGER}; - if (static_cast(format) == 0 || format >= FrameBufferTextureFormats::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 OpenGlFramebuffer final : public Framebuffer { + class NxOpenGlFramebuffer final : public NxFramebuffer { public: /** * @brief Constructs an OpenGL framebuffer with the specified specifications. @@ -57,12 +57,12 @@ namespace nexo::renderer { * attachments, and sampling options. * * Throws: - * - FramebufferResizingFailed if the dimensions are invalid (e.g., zero or exceeding limits). - * - FramebufferUnsupportedColorFormat if the color attachment format is unsupported. - * - FramebufferUnsupportedDepthFormat if the depth attachment format is unsupported. - * - FramebufferCreationFailed if the framebuffer status is not complete. + * - 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 OpenGlFramebuffer(FramebufferSpecs specs); + explicit NxOpenGlFramebuffer(NxFramebufferSpecs specs); /** * @brief Destroys the OpenGL framebuffer and releases associated resources. @@ -74,7 +74,7 @@ namespace nexo::renderer { * - `glDeleteFramebuffers`: Deletes the framebuffer object. * - `glDeleteTextures`: Deletes the textures associated with color and depth attachments. */ - ~OpenGlFramebuffer() override; + ~NxOpenGlFramebuffer() override; /** * @brief Recreates the OpenGL framebuffer and its attachments. @@ -90,9 +90,9 @@ namespace nexo::renderer { * - Validates the framebuffer status. * * Throws: - * - FramebufferUnsupportedColorFormat if a specified color format is unsupported. - * - FramebufferUnsupportedDepthFormat if a specified depth format is unsupported. - * - FramebufferCreationFailed if the framebuffer is not complete. + * - 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(); @@ -134,7 +134,7 @@ namespace nexo::renderer { * @param height The new height of the framebuffer in pixels. * * Throws: - * - FramebufferResizingFailed if the new dimensions are zero or exceed the maximum supported size. + * - NxFramebufferResizingFailed if the new dimensions are zero or exceed the maximum supported size. */ void resize(unsigned int width, unsigned int height) override; @@ -156,7 +156,7 @@ namespace nexo::renderer { T getPixelImpl(unsigned int attachmentIndex, int x, int y) const { if (attachmentIndex >= m_colorAttachments.size()) - THROW_EXCEPTION(FramebufferInvalidIndex, "OPENGL", attachmentIndex); + THROW_EXCEPTION(NxFramebufferInvalidIndex, "OPENGL", attachmentIndex); glReadBuffer(GL_COLOR_ATTACHMENT0 + attachmentIndex); @@ -185,7 +185,7 @@ namespace nexo::renderer { void clearAttachmentImpl(unsigned int attachmentIndex, const void *value) const { if (attachmentIndex >= m_colorAttachments.size()) - THROW_EXCEPTION(FramebufferInvalidIndex, "OPENGL", attachmentIndex); + THROW_EXCEPTION(NxFramebufferInvalidIndex, "OPENGL", attachmentIndex); auto &spec = m_colorAttachmentsSpecs[attachmentIndex]; constexpr GLenum type = getGLTypeFromTemplate(); @@ -193,20 +193,20 @@ namespace nexo::renderer { } void clearAttachmentWrapper(unsigned int attachmentIndex, const void *value, const std::type_info &ti) const override; - FramebufferSpecs &getSpecs() override {return m_specs;}; - [[nodiscard]] const FramebufferSpecs &getSpecs() const override {return m_specs;}; + NxFramebufferSpecs &getSpecs() override {return m_specs;}; + [[nodiscard]] const NxFramebufferSpecs &getSpecs() const override {return m_specs;}; [[nodiscard]] unsigned int getColorAttachmentId(const unsigned int index = 0) const override {return m_colorAttachments[index];}; [[nodiscard]] unsigned int getDepthAttachmentId() const override { return m_depthAttachment; } private: unsigned int m_id = 0; bool toResize = false; - FramebufferSpecs m_specs; + NxFramebufferSpecs m_specs; glm::vec4 m_clearColor = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); - std::vector m_colorAttachmentsSpecs; - FrameBufferTextureSpecifications m_depthAttachmentSpec; + std::vector m_colorAttachmentsSpecs; + NxFrameBufferTextureSpecifications m_depthAttachmentSpec; std::vector m_colorAttachments; unsigned int m_depthAttachment = 0; diff --git a/engine/src/renderer/opengl/OpenGlRendererAPI.hpp b/engine/src/renderer/opengl/OpenGlRendererAPI.hpp index 7ffdb66fa..9bf5acc7c 100644 --- a/engine/src/renderer/opengl/OpenGlRendererAPI.hpp +++ b/engine/src/renderer/opengl/OpenGlRendererAPI.hpp @@ -18,11 +18,11 @@ namespace nexo::renderer { /** - * @class OpenGlRendererApi + * @class NxOpenGlRendererApi * @brief Implementation of the RendererApi interface using OpenGL. * - * The `OpenGlRendererApi` class provides OpenGL-specific implementations for the - * methods defined in `RendererApi`. It interacts directly with OpenGL functions + * 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: @@ -30,7 +30,7 @@ namespace nexo::renderer { * - Provide access to OpenGL-specific features like blending and depth testing. * - Issue draw calls for indexed geometry. */ - class OpenGlRendererApi final : public RendererApi { + class NxOpenGlRendererApi final : public NxRendererApi { public: /** * @brief Initializes the OpenGL renderer API. @@ -41,7 +41,7 @@ namespace nexo::renderer { * - Configuring maximum viewport dimensions. * * Throws: - * - GraphicsApiNotInitialized if OpenGL fails to initialize. + * - NxGraphicsApiNotInitialized if OpenGL fails to initialize. */ void init() override; @@ -57,7 +57,7 @@ namespace nexo::renderer { * @param height The height of the viewport in pixels. * * Throws: - * - GraphicsApiViewportResizingFailure if the dimensions exceed the maximum allowed size. + * - NxGraphicsApiViewportResizingFailure if the dimensions exceed the maximum allowed size. */ void setViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height) override; @@ -78,7 +78,7 @@ namespace nexo::renderer { * Resets the color and depth buffers using the current clear color and depth values. * * Throws: - * - GraphicsApiNotInitialized if OpenGL is not initialized. + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. */ void clear() override; @@ -90,7 +90,7 @@ namespace nexo::renderer { * @param color A `glm::vec4` containing the red, green, blue, and alpha components of the clear color. * * Throws: - * - GraphicsApiNotInitialized if OpenGL is not initialized. + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. */ void setClearColor(const glm::vec4 &color) override; @@ -102,7 +102,7 @@ namespace nexo::renderer { * @param depth A float value representing the clear depth. * * Throws: - * - GraphicsApiNotInitialized if OpenGL is not initialized. + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. */ void setClearDepth(float depth) override; @@ -113,16 +113,16 @@ namespace nexo::renderer { /** * @brief Renders indexed geometry using OpenGL. * - * Issues a draw call for indexed primitives using data from the specified `VertexArray`. + * Issues a draw call for indexed primitives using data from the specified `NxVertexArray`. * - * @param vertexArray A shared pointer to the `VertexArray` containing vertex and index data. + * @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: - * - GraphicsApiNotInitialized if OpenGL is not initialized. - * - InvalidValue if the `vertexArray` is null. + * - NxGraphicsApiNotInitialized if OpenGL is not initialized. + * - NxInvalidValue if the `vertexArray` is null. */ - void drawIndexed(const std::shared_ptr &vertexArray, unsigned int indexCount = 0) override; + void drawIndexed(const std::shared_ptr &vertexArray, unsigned int indexCount = 0) override; void drawUnIndexed(unsigned int verticesCount) override; diff --git a/engine/src/renderer/opengl/OpenGlRendererApi.cpp b/engine/src/renderer/opengl/OpenGlRendererApi.cpp index 18776ce9c..b974be426 100644 --- a/engine/src/renderer/opengl/OpenGlRendererApi.cpp +++ b/engine/src/renderer/opengl/OpenGlRendererApi.cpp @@ -21,7 +21,7 @@ namespace nexo::renderer { - void OpenGlRendererApi::init() + void NxOpenGlRendererApi::init() { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -43,45 +43,45 @@ namespace nexo::renderer { LOG(NEXO_DEV, "Opengl renderer api initialized"); } - void OpenGlRendererApi::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(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (!width || !height) - THROW_EXCEPTION(GraphicsApiViewportResizingFailure, "OPENGL", false, width, height); + THROW_EXCEPTION(NxGraphicsApiViewportResizingFailure, "OPENGL", false, width, height); if (width > m_maxWidth || height > m_maxHeight) - THROW_EXCEPTION(GraphicsApiViewportResizingFailure, "OPENGL", true, width, height); + THROW_EXCEPTION(NxGraphicsApiViewportResizingFailure, "OPENGL", true, width, height); glViewport(static_cast(x), static_cast(y), static_cast(width), static_cast(height)); } - void OpenGlRendererApi::getMaxViewportSize(unsigned int *width, unsigned int *height) + void NxOpenGlRendererApi::getMaxViewportSize(unsigned int *width, unsigned int *height) { *width = m_maxWidth; *height = m_maxHeight; } - void OpenGlRendererApi::clear() + void NxOpenGlRendererApi::clear() { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } - void OpenGlRendererApi::setClearColor(const glm::vec4 &color) + void NxOpenGlRendererApi::setClearColor(const glm::vec4 &color) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glClearColor(color.r, color.g, color.b, color.a); } - void OpenGlRendererApi::setClearDepth(const float depth) + void NxOpenGlRendererApi::setClearDepth(const float depth) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glClearDepth(depth); } - void OpenGlRendererApi::setDepthTest(bool enable) + void NxOpenGlRendererApi::setDepthTest(bool enable) { if (!m_initialized) THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); @@ -91,14 +91,14 @@ namespace nexo::renderer { glDisable(GL_DEPTH_TEST); } - void OpenGlRendererApi::setDepthFunc(unsigned int func) + void NxOpenGlRendererApi::setDepthFunc(unsigned int func) { if (!m_initialized) THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); glDepthFunc(func); } - void OpenGlRendererApi::setDepthMask(bool enable) + void NxOpenGlRendererApi::setDepthMask(bool enable) { if (!m_initialized) THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); @@ -108,44 +108,44 @@ namespace nexo::renderer { glDepthMask(GL_FALSE); } - void OpenGlRendererApi::drawIndexed(const std::shared_ptr &vertexArray, const unsigned int indexCount) + void NxOpenGlRendererApi::drawIndexed(const std::shared_ptr &vertexArray, const unsigned int indexCount) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (!vertexArray) - THROW_EXCEPTION(InvalidValue, "OPENGL", "Vertex array cannot be null"); + THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Vertex array cannot be null"); const unsigned int count = indexCount ? vertexArray->getIndexBuffer()->getCount() : indexCount; glDrawElements(GL_TRIANGLES, static_cast(count), GL_UNSIGNED_INT, nullptr); } - void OpenGlRendererApi::drawUnIndexed(unsigned int verticesCount) + void NxOpenGlRendererApi::drawUnIndexed(unsigned int verticesCount) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glDrawArrays(GL_TRIANGLES, 0, verticesCount); } - void OpenGlRendererApi::setStencilTest(bool enable) + void NxOpenGlRendererApi::setStencilTest(bool enable) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (enable) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); } - void OpenGlRendererApi::setStencilMask(unsigned int mask) + void NxOpenGlRendererApi::setStencilMask(unsigned int mask) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glStencilMask(mask); } - void OpenGlRendererApi::setStencilFunc(unsigned int func, int ref, unsigned int mask) + void NxOpenGlRendererApi::setStencilFunc(unsigned int func, int ref, unsigned int mask) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glStencilFunc(func, ref, mask); } diff --git a/engine/src/renderer/opengl/OpenGlShader.cpp b/engine/src/renderer/opengl/OpenGlShader.cpp index de6795421..8c00a1e0b 100644 --- a/engine/src/renderer/opengl/OpenGlShader.cpp +++ b/engine/src/renderer/opengl/OpenGlShader.cpp @@ -35,7 +35,7 @@ namespace nexo::renderer { return 0; } - OpenGlShader::OpenGlShader(const std::string &path) + NxOpenGlShader::NxOpenGlShader(const std::string &path) { const std::string src = readFile(path); auto shaderSources = preProcess(src, path); @@ -49,7 +49,7 @@ namespace nexo::renderer { setupUniformLocations(); } - OpenGlShader::OpenGlShader(std::string name, const std::string_view &vertexSource, + NxOpenGlShader::NxOpenGlShader(std::string name, const std::string_view &vertexSource, const std::string_view &fragmentSource) : m_name(std::move(name)) { std::unordered_map preProcessedSource; @@ -59,12 +59,12 @@ namespace nexo::renderer { setupUniformLocations(); } - OpenGlShader::~OpenGlShader() + NxOpenGlShader::~NxOpenGlShader() { glDeleteProgram(m_id); } - std::unordered_map OpenGlShader::preProcess(const std::string_view &src, + std::unordered_map NxOpenGlShader::preProcess(const std::string_view &src, const std::string &filePath) { std::unordered_map shaderSources; @@ -77,18 +77,18 @@ namespace nexo::renderer { constexpr size_t typeTokenLength = 5; const size_t eol = src.find_first_of("\r\n", pos); if (eol == std::string::npos) - THROW_EXCEPTION(ShaderCreationFailed, "OPENGL", + THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", "Syntax error at line: " + std::to_string(currentLine), filePath); const size_t begin = pos + typeTokenLength + 1; std::string_view type = src.substr(begin, eol - begin); if (!shaderTypeFromString(type)) - THROW_EXCEPTION(ShaderCreationFailed, "OPENGL", + THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", "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(ShaderCreationFailed, "OPENGL", + THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", "Syntax error at line: " + std::to_string(currentLine), filePath); pos = src.find(typeToken, nextLinePos); @@ -103,13 +103,13 @@ namespace nexo::renderer { return shaderSources; } - void OpenGlShader::compile(const std::unordered_map &shaderSources) + void NxOpenGlShader::compile(const std::unordered_map &shaderSources) { // Vertex and fragment shaders are successfully compiled. // Now time to link them together into a program. // Get a program object. if (shaderSources.size() > 2) - THROW_EXCEPTION(ShaderCreationFailed, "OPENGL", + THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", "Only two shader type (vertex/fragment) are supported for now", ""); const GLuint program = glCreateProgram(); std::array glShaderIds{}; @@ -142,7 +142,7 @@ namespace nexo::renderer { // We don't need the shader anymore. glDeleteShader(shader); - THROW_EXCEPTION(ShaderCreationFailed, "OPENGL", + THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", "Opengl failed to compile the shader: " + std::string(infoLog.data()), ""); } glAttachShader(program, shader); @@ -171,7 +171,7 @@ namespace nexo::renderer { for (auto id: glShaderIds) glDeleteShader(id); - THROW_EXCEPTION(ShaderCreationFailed, "OPENGL", + THROW_EXCEPTION(NxShaderCreationFailed, "OPENGL", "Opengl failed to compile the shader: " + std::string(infoLog.data()), ""); } @@ -180,7 +180,7 @@ namespace nexo::renderer { glDetachShader(program, id); } - void OpenGlShader::setupUniformLocations() + void NxOpenGlShader::setupUniformLocations() { glUseProgram(m_id); for (const auto &[key, name] : ShaderUniformsName) { @@ -190,17 +190,17 @@ namespace nexo::renderer { glUseProgram(0); } - void OpenGlShader::bind() const + void NxOpenGlShader::bind() const { glUseProgram(m_id); } - void OpenGlShader::unbind() const + void NxOpenGlShader::unbind() const { glUseProgram(0); } - bool OpenGlShader::setUniformFloat(const std::string &name, const float value) const + bool NxOpenGlShader::setUniformFloat(const std::string &name, const float value) const { const int loc = glGetUniformLocation(m_id, name.c_str()); if (loc == -1) @@ -210,7 +210,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformFloat(const ShaderUniforms uniform, const float value) const + bool NxOpenGlShader::setUniformFloat(const ShaderUniforms uniform, const float value) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -220,7 +220,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformFloat2(const std::string &name, const glm::vec2 &values) const + bool NxOpenGlShader::setUniformFloat2(const std::string &name, const glm::vec2 &values) const { const int loc = glGetUniformLocation(m_id, name.c_str()); if (loc == -1) @@ -230,7 +230,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformFloat3(const std::string &name, const glm::vec3 &values) const + bool NxOpenGlShader::setUniformFloat3(const std::string &name, const glm::vec3 &values) const { const int loc = glGetUniformLocation(m_id, name.c_str()); if (loc == -1) @@ -240,7 +240,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformFloat3(const ShaderUniforms uniform, const glm::vec3 &values) const + bool NxOpenGlShader::setUniformFloat3(const ShaderUniforms uniform, const glm::vec3 &values) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -250,7 +250,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformFloat4(const std::string &name, const glm::vec4 &values) const + bool NxOpenGlShader::setUniformFloat4(const std::string &name, const glm::vec4 &values) const { const int loc = glGetUniformLocation(m_id, name.c_str()); if (loc == -1) @@ -260,7 +260,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformFloat4(const ShaderUniforms uniform, const glm::vec4 &values) const + bool NxOpenGlShader::setUniformFloat4(const ShaderUniforms uniform, const glm::vec4 &values) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -270,7 +270,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformMatrix(const std::string &name, const glm::mat4 &matrix) const + bool NxOpenGlShader::setUniformMatrix(const std::string &name, const glm::mat4 &matrix) const { const int loc = glGetUniformLocation(m_id, name.c_str()); if (loc == -1) @@ -280,7 +280,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformMatrix(const ShaderUniforms uniform, const glm::mat4 &matrix) const + bool NxOpenGlShader::setUniformMatrix(const ShaderUniforms uniform, const glm::mat4 &matrix) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -290,7 +290,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformInt(const std::string &name, const int value) const + bool NxOpenGlShader::setUniformInt(const std::string &name, const int value) const { const int loc = glGetUniformLocation(m_id, name.c_str()); if (loc == -1) @@ -300,7 +300,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformBool(const std::string &name, bool value) const + bool NxOpenGlShader::setUniformBool(const std::string &name, bool value) const { const int loc = glGetUniformLocation(m_id, name.c_str()); if (loc == -1) @@ -310,7 +310,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformInt(const ShaderUniforms uniform, const int value) const + bool NxOpenGlShader::setUniformInt(const ShaderUniforms uniform, const int value) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -320,7 +320,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformIntArray(const std::string &name, const int *values, const unsigned int count) const + bool NxOpenGlShader::setUniformIntArray(const std::string &name, const int *values, const unsigned int count) const { const int loc = glGetUniformLocation(m_id, name.c_str()); if (loc == -1) @@ -330,7 +330,7 @@ namespace nexo::renderer { return true; } - bool OpenGlShader::setUniformIntArray(const ShaderUniforms uniform, const int *values, const unsigned int count) const + bool NxOpenGlShader::setUniformIntArray(const ShaderUniforms uniform, const int *values, const unsigned int count) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -340,24 +340,24 @@ namespace nexo::renderer { return true; } - void OpenGlShader::bindStorageBuffer(unsigned int index) const + void NxOpenGlShader::bindStorageBuffer(unsigned int index) const { if (index > m_storageBuffers.size()) - THROW_EXCEPTION(OutOfRangeException, index, m_storageBuffers.size()); + THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); m_storageBuffers[index]->bind(); } - void OpenGlShader::unbindStorageBuffer(unsigned int index) const + void NxOpenGlShader::unbindStorageBuffer(unsigned int index) const { if (index > m_storageBuffers.size()) - THROW_EXCEPTION(OutOfRangeException, index, m_storageBuffers.size()); + THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); m_storageBuffers[index]->unbind(); } - void OpenGlShader::bindStorageBufferBase(unsigned int index, unsigned int bindingLocation) const + void NxOpenGlShader::bindStorageBufferBase(unsigned int index, unsigned int bindingLocation) const { if (index > m_storageBuffers.size()) - THROW_EXCEPTION(OutOfRangeException, index, m_storageBuffers.size()); + THROW_EXCEPTION(NxOutOfRangeException, index, m_storageBuffers.size()); m_storageBuffers[index]->bindBase(bindingLocation); } } diff --git a/engine/src/renderer/opengl/OpenGlShader.hpp b/engine/src/renderer/opengl/OpenGlShader.hpp index 7ff93e6a0..39f17e81b 100644 --- a/engine/src/renderer/opengl/OpenGlShader.hpp +++ b/engine/src/renderer/opengl/OpenGlShader.hpp @@ -19,10 +19,10 @@ namespace nexo::renderer { /** - * @class OpenGlShader + * @class NxOpenGlShader * @brief Implementation of the Shader interface using OpenGL. * - * The `OpenGlShader` class provides OpenGL-specific functionality for creating + * 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. * @@ -31,7 +31,7 @@ namespace nexo::renderer { * - Set uniform variables for rendering operations. * - Manage the lifecycle of OpenGL shader programs. */ - class OpenGlShader final : public Shader { + class NxOpenGlShader final : public NxShader { public: /** * @brief Constructs a shader program from a source file. @@ -42,12 +42,12 @@ namespace nexo::renderer { * @param path The file path to the shader source code. * * Throws: - * - `FileNotFoundException` if the file cannot be found. - * - `ShaderCreationFailed` if shader compilation fails. + * - `NxFileNotFoundException` if the file cannot be found. + * - `NxShaderCreationFailed` if shader compilation fails. */ - OpenGlShader(const std::string &path); - OpenGlShader(std::string name, const std::string_view &vertexSource, const std::string_view &fragmentSource); - ~OpenGlShader() override; + 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. diff --git a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp index c40ba1008..5b101e801 100644 --- a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp @@ -16,7 +16,7 @@ #include "OpenGlShaderStorageBuffer.hpp" namespace nexo::renderer { - OpenGlShaderStorageBuffer::OpenGlShaderStorageBuffer(unsigned int size) + NxOpenGlShaderStorageBuffer::NxOpenGlShaderStorageBuffer(unsigned int size) { glCreateBuffers(1, &m_id); glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); @@ -24,22 +24,22 @@ namespace nexo::renderer { glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); } - void OpenGlShaderStorageBuffer::bind() const + void NxOpenGlShaderStorageBuffer::bind() const { glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); } - void OpenGlShaderStorageBuffer::bindBase(unsigned int bindingLocation) const + void NxOpenGlShaderStorageBuffer::bindBase(unsigned int bindingLocation) const { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingLocation, m_id); } - void OpenGlShaderStorageBuffer::unbind() const + void NxOpenGlShaderStorageBuffer::unbind() const { glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); } - void OpenGlShaderStorageBuffer::setData(void* data, unsigned int size) + void NxOpenGlShaderStorageBuffer::setData(void* data, unsigned int size) { glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, size, data); diff --git a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp index 6ac770ebc..2e7a9e69d 100644 --- a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp @@ -16,10 +16,10 @@ #include "renderer/ShaderStorageBuffer.hpp" namespace nexo::renderer { - class OpenGlShaderStorageBuffer : public ShaderStorageBuffer { + class NxOpenGlShaderStorageBuffer : public NxShaderStorageBuffer { public: - explicit OpenGlShaderStorageBuffer(unsigned int size); - ~OpenGlShaderStorageBuffer() override = default; + explicit NxOpenGlShaderStorageBuffer(unsigned int size); + ~NxOpenGlShaderStorageBuffer() override = default; void bind() const override; void bindBase(unsigned int bindingLocation) const override; diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index bc76d19fd..de1e86cfa 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -22,11 +22,11 @@ namespace nexo::renderer { - OpenGlTexture2D::OpenGlTexture2D(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) { const unsigned int maxTextureSize = getMaxTextureSize(); if (width > maxTextureSize || height > maxTextureSize) - THROW_EXCEPTION(TextureInvalidSize, "OPENGL", width, height, maxTextureSize); + THROW_EXCEPTION(NxTextureInvalidSize, "OPENGL", width, height, maxTextureSize); m_internalFormat = GL_RGBA8; m_dataFormat = GL_RGBA; @@ -40,7 +40,7 @@ namespace nexo::renderer { glBindTexture(GL_TEXTURE_2D, 0); } - OpenGlTexture2D::OpenGlTexture2D(const std::string &path) + NxOpenGlTexture2D::NxOpenGlTexture2D(const std::string &path) { int width = 0; int height = 0; @@ -49,16 +49,16 @@ namespace nexo::renderer { //stbi_set_flip_vertically_on_load(1); stbi_uc *data = stbi_load(path.c_str(), &width, &height, &channels, 0); if (!data) - THROW_EXCEPTION(FileNotFoundException, path); + THROW_EXCEPTION(NxFileNotFoundException, path); ingestDataFromStb(data, width, height, channels, path); } - OpenGlTexture2D::~OpenGlTexture2D() + NxOpenGlTexture2D::~NxOpenGlTexture2D() { glDeleteTextures(1, &m_id); } - OpenGlTexture2D::OpenGlTexture2D(const uint8_t* buffer, unsigned int len) + NxOpenGlTexture2D::NxOpenGlTexture2D(const uint8_t* buffer, unsigned int len) { int width = 0; int height = 0; @@ -67,28 +67,28 @@ namespace nexo::renderer { //stbi_set_flip_vertically_on_load(1); stbi_uc *data = stbi_load_from_memory(buffer, len, &width, &height, &channels, 0); if (!data) - THROW_EXCEPTION(TextureUnsupportedFormat, "OPENGL", channels, "(buffer)"); + THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", channels, "(buffer)"); ingestDataFromStb(data, width, height, channels, "(buffer)"); } - unsigned int OpenGlTexture2D::getMaxTextureSize() const + unsigned int NxOpenGlTexture2D::getMaxTextureSize() const { int maxTextureSize = 0; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); return static_cast(maxTextureSize); } - void OpenGlTexture2D::setData(void *data, const unsigned int size) + void NxOpenGlTexture2D::setData(void *data, const unsigned int size) { if (const unsigned int expectedSize = m_width * m_height * (m_dataFormat == GL_RGBA ? 4 : 3); size != expectedSize) - THROW_EXCEPTION(TextureSizeMismatch, "OPENGL", 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); glBindTexture(GL_TEXTURE_2D, 0); } - void OpenGlTexture2D::ingestDataFromStb(uint8_t* data, int width, int height, int channels, + void NxOpenGlTexture2D::ingestDataFromStb(uint8_t* data, int width, int height, int channels, const std::string& debugPath) { m_width = width; @@ -116,7 +116,7 @@ namespace nexo::renderer { break; default: stbi_image_free(data); - THROW_EXCEPTION(TextureUnsupportedFormat, "OPENGL", channels, debugPath); + THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", channels, debugPath); } m_internalFormat = internalFormat; @@ -135,13 +135,13 @@ namespace nexo::renderer { } - void OpenGlTexture2D::bind(const unsigned int slot) const + void NxOpenGlTexture2D::bind(const unsigned int slot) const { glActiveTexture(GL_TEXTURE0 + slot); glBindTexture(GL_TEXTURE_2D, m_id); } - void OpenGlTexture2D::unbind(const unsigned int slot) const + void NxOpenGlTexture2D::unbind(const unsigned int slot) const { glActiveTexture(GL_TEXTURE0 + slot); glBindTexture(GL_TEXTURE_2D, 0); diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.hpp b/engine/src/renderer/opengl/OpenGlTexture2D.hpp index 82919ec78..e957850c2 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.hpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.hpp @@ -19,10 +19,10 @@ namespace nexo::renderer { /** - * @class OpenGlTexture2D - * @brief OpenGL-specific implementation of the `Texture2D` class. + * @class NxOpenGlTexture2D + * @brief OpenGL-specific implementation of the `NxTexture2D` class. * - * The `OpenGlTexture2D` class manages 2D textures in an OpenGL rendering context. + * The `NxOpenGlTexture2D` class manages 2D textures in an OpenGL rendering context. * It supports texture creation, data uploading, and binding/unbinding operations. * * Responsibilities: @@ -30,9 +30,9 @@ namespace nexo::renderer { * - Load texture data from files or raw memory. * - Provide texture binding and unbinding functionality. */ - class OpenGlTexture2D final : public Texture2D { + class NxOpenGlTexture2D final : public NxTexture2D { public: - ~OpenGlTexture2D() override; + ~NxOpenGlTexture2D() override; /** * @brief Loads an OpenGL 2D texture from an image file. @@ -42,15 +42,15 @@ namespace nexo::renderer { * internal and data formats based on the number of channels in the image. * * @param path The file path to the texture image. - * @throw FileNotFoundException If the file cannot be found. - * @throw TextureUnsupportedFormat If the image format is unsupported. + * @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"); + * auto texture = std::make_shared("textures/wood.jpg"); * ``` */ - explicit OpenGlTexture2D(const std::string &path); + explicit NxOpenGlTexture2D(const std::string &path); /** * @brief Creates a blank OpenGL 2D texture with the specified dimensions. @@ -63,10 +63,10 @@ namespace nexo::renderer { * * Example: * ```cpp - * auto texture = std::make_shared(256, 256); + * auto texture = std::make_shared(256, 256); * ``` */ - OpenGlTexture2D(unsigned int width, unsigned int height); + NxOpenGlTexture2D(unsigned int width, unsigned int height); /** * @brief Creates a OpenGL 2D texture from file in memory. @@ -81,10 +81,10 @@ namespace nexo::renderer { * Example: * ```cpp * std::vector imageData = ...; // Load image data into a buffer - * auto texture = std::make_shared(imageData.data(), imageData.size()); + * auto texture = std::make_shared(imageData.data(), imageData.size()); * ``` */ - OpenGlTexture2D(const uint8_t *buffer, unsigned int len); + 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;}; @@ -138,7 +138,7 @@ namespace nexo::renderer { * * @param data A pointer to the pixel data. * @param size The size of the data in bytes. - * @throw TextureSizeMismatch If the size of the data does not match the texture's dimensions. + * @throw NxTextureSizeMismatch If the size of the data does not match the texture's dimensions. * * Example: * ```cpp diff --git a/engine/src/renderer/opengl/OpenGlVertexArray.cpp b/engine/src/renderer/opengl/OpenGlVertexArray.cpp index 14ad3fe83..a4f4673d9 100644 --- a/engine/src/renderer/opengl/OpenGlVertexArray.cpp +++ b/engine/src/renderer/opengl/OpenGlVertexArray.cpp @@ -21,73 +21,73 @@ namespace nexo::renderer { /** - * @brief Converts a `ShaderDataType` enum value to the corresponding OpenGL type. + * @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 = shaderDataTypeToOpenGltype(ShaderDataType::FLOAT3); + * GLenum glType = nxShaderDataTypeToOpenGltype(NxShaderDataType::FLOAT3); * ``` */ - static GLenum shaderDataTypeToOpenGltype(const ShaderDataType type) + static GLenum nxShaderDataTypeToOpenGltype(const NxShaderDataType type) { switch (type) { - case ShaderDataType::FLOAT: return GL_FLOAT; - case ShaderDataType::FLOAT2: return GL_FLOAT; - case ShaderDataType::FLOAT3: return GL_FLOAT; - case ShaderDataType::FLOAT4: return GL_FLOAT; - case ShaderDataType::INT: return GL_INT; - case ShaderDataType::INT2: return GL_INT; - case ShaderDataType::INT3: return GL_INT; - case ShaderDataType::INT4: return GL_INT; - case ShaderDataType::MAT3: return GL_FLOAT; - case ShaderDataType::MAT4: return GL_FLOAT; - case ShaderDataType::BOOL: return GL_BOOL; + 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; } } - static bool isInt(const ShaderDataType type) + static bool isInt(const NxShaderDataType type) { switch (type) { - case ShaderDataType::INT: return true; - case ShaderDataType::INT2: return true; - case ShaderDataType::INT3: return true; - case ShaderDataType::INT4: return true; - case ShaderDataType::BOOL: return true; + 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; } return false; } - OpenGlVertexArray::OpenGlVertexArray() + NxOpenGlVertexArray::NxOpenGlVertexArray() { glGenVertexArrays(1, &_id); } - void OpenGlVertexArray::bind() const + void NxOpenGlVertexArray::bind() const { glBindVertexArray(_id); } - void OpenGlVertexArray::unbind() const + void NxOpenGlVertexArray::unbind() const { glBindVertexArray(0); } - void OpenGlVertexArray::addVertexBuffer(const std::shared_ptr &vertexBuffer) + void NxOpenGlVertexArray::addVertexBuffer(const std::shared_ptr &vertexBuffer) { if (!vertexBuffer) - THROW_EXCEPTION(InvalidValue, "OPENGL", "Vertex buffer is null"); + THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Vertex buffer is null"); glBindVertexArray(_id); vertexBuffer->bind(); if (vertexBuffer->getLayout().getElements().empty()) - THROW_EXCEPTION(BufferLayoutEmpty, "OPENGL"); + THROW_EXCEPTION(NxBufferLayoutEmpty, "OPENGL"); auto index = static_cast(!_vertexBuffers.empty() ? _vertexBuffers.back()->getLayout().getElements().size() @@ -100,7 +100,7 @@ namespace nexo::renderer { glVertexAttribIPointer( index, static_cast(element.getComponentCount()), - shaderDataTypeToOpenGltype(element.type), + nxShaderDataTypeToOpenGltype(element.type), static_cast(layout.getStride()), reinterpret_cast(static_cast(element.offset)) ); @@ -110,7 +110,7 @@ namespace nexo::renderer { glVertexAttribPointer( index, static_cast(element.getComponentCount()), - shaderDataTypeToOpenGltype(element.type), + nxShaderDataTypeToOpenGltype(element.type), element.normalized ? GL_TRUE : GL_FALSE, static_cast(layout.getStride()), reinterpret_cast(static_cast(element.offset)) @@ -121,27 +121,27 @@ namespace nexo::renderer { _vertexBuffers.push_back(vertexBuffer); } - void OpenGlVertexArray::setIndexBuffer(const std::shared_ptr &indexBuffer) + void NxOpenGlVertexArray::setIndexBuffer(const std::shared_ptr &indexBuffer) { if (!indexBuffer) - THROW_EXCEPTION(InvalidValue, "OPENGL", "Index buffer cannot be null"); + THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Index buffer cannot be null"); glBindVertexArray(_id); indexBuffer->bind(); _indexBuffer = indexBuffer; } - const std::vector> &OpenGlVertexArray::getVertexBuffers() const + const std::vector> &NxOpenGlVertexArray::getVertexBuffers() const { return _vertexBuffers; } - const std::shared_ptr &OpenGlVertexArray::getIndexBuffer() const + const std::shared_ptr &NxOpenGlVertexArray::getIndexBuffer() const { return _indexBuffer; } - unsigned int OpenGlVertexArray::getId() const + unsigned int NxOpenGlVertexArray::getId() const { return _id; } diff --git a/engine/src/renderer/opengl/OpenGlVertexArray.hpp b/engine/src/renderer/opengl/OpenGlVertexArray.hpp index 94a43004f..43a4befb7 100644 --- a/engine/src/renderer/opengl/OpenGlVertexArray.hpp +++ b/engine/src/renderer/opengl/OpenGlVertexArray.hpp @@ -19,10 +19,10 @@ namespace nexo::renderer { /** - * @class OpenGlVertexArray - * @brief OpenGL-specific implementation of the `VertexArray` class. + * @class NxOpenGlVertexArray + * @brief OpenGL-specific implementation of the `NxVertexArray` class. * - * The `OpenGlVertexArray` class manages vertex and index buffers in an OpenGL + * 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. * @@ -31,7 +31,7 @@ namespace nexo::renderer { * - Configure vertex attributes using buffer layouts. * - Bind/unbind the VAO for rendering operations. */ - class OpenGlVertexArray final : public VertexArray { + class NxOpenGlVertexArray final : public NxVertexArray { public: /** * @brief Creates an OpenGL vertex array object (VAO). @@ -39,8 +39,8 @@ namespace nexo::renderer { * Initializes a new VAO and assigns it a unique ID. This ID is used to reference * the VAO in OpenGL operations. */ - OpenGlVertexArray(); - ~OpenGlVertexArray() override = default; + NxOpenGlVertexArray(); + ~NxOpenGlVertexArray() override = default; /** * @brief Binds the vertex array object (VAO) to the OpenGL context. @@ -65,10 +65,10 @@ namespace nexo::renderer { * buffer layout. The attributes are assigned sequential indices. * * @param vertexBuffer The vertex buffer to add. - * @throw InvalidValue If the vertex buffer is null. - * @throw BufferLayoutEmpty If the vertex buffer's layout is empty. + * @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; + void addVertexBuffer(const std::shared_ptr &vertexBuffer) override; /** * @brief Sets the index buffer for the vertex array. @@ -76,17 +76,17 @@ namespace nexo::renderer { * Associates an index buffer with the vertex array, enabling indexed rendering. * * @param indexBuffer The index buffer to set. - * @throw InvalidValue If the index buffer is null. + * @throw NxInvalidValue If the index buffer is null. */ - void setIndexBuffer(const std::shared_ptr &indexBuffer) override; + void setIndexBuffer(const std::shared_ptr &indexBuffer) override; - [[nodiscard]] const std::vector> &getVertexBuffers() const override; - [[nodiscard]] const std::shared_ptr &getIndexBuffer() const override; + [[nodiscard]] const std::vector> &getVertexBuffers() const override; + [[nodiscard]] const std::shared_ptr &getIndexBuffer() const override; unsigned int getId() const override; private: - std::vector> _vertexBuffers; - std::shared_ptr _indexBuffer; + std::vector> _vertexBuffers; + std::shared_ptr _indexBuffer; unsigned int _id{}; }; diff --git a/engine/src/renderer/opengl/OpenGlWindow.cpp b/engine/src/renderer/opengl/OpenGlWindow.cpp index ef67b74f0..e6730e90b 100644 --- a/engine/src/renderer/opengl/OpenGlWindow.cpp +++ b/engine/src/renderer/opengl/OpenGlWindow.cpp @@ -27,17 +27,17 @@ namespace nexo::renderer { std::cerr << "[GLFW ERROR] Code : " << errorCode << " / Description : " << errorStr << std::endl; } - void OpenGlWindow::setupCallback() const + 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)); + auto *props = static_cast(glfwGetWindowUserPointer(window)); props->width = width; props->height = height; - Renderer::onWindowResize(width, height); + NxRenderer::onWindowResize(width, height); if (props->resizeCallback) props->resizeCallback(width, height); }); @@ -45,7 +45,7 @@ namespace nexo::renderer { // Close event glfwSetWindowCloseCallback(_openGlWindow, [](GLFWwindow *window) { - const auto *props = static_cast(glfwGetWindowUserPointer(window)); + const auto *props = static_cast(glfwGetWindowUserPointer(window)); if (props->closeCallback) props->closeCallback(); }); @@ -53,7 +53,7 @@ namespace nexo::renderer { // Keyboard events glfwSetKeyCallback(_openGlWindow, [](GLFWwindow *window, const int key, [[maybe_unused]]int scancode, const int action, const int mods) { - const auto *props = static_cast(glfwGetWindowUserPointer(window)); + const auto *props = static_cast(glfwGetWindowUserPointer(window)); if (props->keyCallback) props->keyCallback(key, action, mods); }); @@ -61,7 +61,7 @@ namespace nexo::renderer { // Mouse click callback glfwSetMouseButtonCallback(_openGlWindow, [](GLFWwindow *window, const int button, const int action, const int mods) { - const auto *props = static_cast(glfwGetWindowUserPointer(window)); + const auto *props = static_cast(glfwGetWindowUserPointer(window)); if (props->mouseClickCallback) props->mouseClickCallback(button, action, mods); }); @@ -69,7 +69,7 @@ namespace nexo::renderer { // Mouse scroll event glfwSetScrollCallback(_openGlWindow, [](GLFWwindow *window, const double xOffset, const double yOffset) { - const auto *props = static_cast(glfwGetWindowUserPointer(window)); + const auto *props = static_cast(glfwGetWindowUserPointer(window)); if (props->mouseScrollCallback) props->mouseScrollCallback(xOffset, yOffset); }); @@ -77,16 +77,16 @@ namespace nexo::renderer { // Mouse move event glfwSetCursorPosCallback(_openGlWindow, [](GLFWwindow *window, const double xpos, const double ypos) { - const auto *props = static_cast(glfwGetWindowUserPointer(window)); + const auto *props = static_cast(glfwGetWindowUserPointer(window)); if (props->mouseMoveCallback) props->mouseMoveCallback(xpos, ypos); }); } - void OpenGlWindow::init() + void NxOpenGlWindow::init() { if (!glfwInit()) - THROW_EXCEPTION(GraphicsApiInitFailure, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiInitFailure, "OPENGL"); LOG(NEXO_DEV, "Initializing opengl window"); glfwSetErrorCallback(glfwErrorCallback); @@ -109,7 +109,7 @@ namespace nexo::renderer { glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); _openGlWindow = glfwCreateWindow(static_cast(_props.width), static_cast(_props.height), _props.title, nullptr, nullptr); if (!_openGlWindow) - THROW_EXCEPTION(GraphicsApiWindowInitFailure, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiWindowInitFailure, "OPENGL"); glfwMakeContextCurrent(_openGlWindow); glfwSetWindowUserPointer(_openGlWindow, &_props); setVsync(true); @@ -117,19 +117,19 @@ namespace nexo::renderer { LOG(NEXO_DEV, "Opengl window ({}, {}) initialized", _props.width, _props.height); } - void OpenGlWindow::shutdown() + void NxOpenGlWindow::shutdown() { glfwDestroyWindow(_openGlWindow); glfwTerminate(); } - void OpenGlWindow::onUpdate() + void NxOpenGlWindow::onUpdate() { glfwSwapBuffers(_openGlWindow); glfwPollEvents(); } - void OpenGlWindow::setVsync(const bool enabled) + void NxOpenGlWindow::setVsync(const bool enabled) { if (enabled) glfwSwapInterval(1); @@ -138,23 +138,23 @@ namespace nexo::renderer { _props.vsync = enabled; } - bool OpenGlWindow::isVsync() const + bool NxOpenGlWindow::isVsync() const { return _props.vsync; } - void OpenGlWindow::getDpiScale(float *x, float *y) const + void NxOpenGlWindow::getDpiScale(float *x, float *y) const { glfwGetWindowContentScale(_openGlWindow, x, y); } - void OpenGlWindow::setWindowIcon(const std::filesystem::path& iconPath) + void NxOpenGlWindow::setWindowIcon(const std::filesystem::path& iconPath) { GLFWimage icon; const auto iconStringPath = iconPath.string(); icon.pixels = stbi_load(iconStringPath.c_str(), &icon.width, &icon.height, nullptr, 4); if (!icon.pixels) { - THROW_EXCEPTION(StbiLoadException, + THROW_EXCEPTION(NxStbiLoadException, std::format("Failed to load icon '{}': {}", iconStringPath, stbi_failure_reason())); } @@ -166,20 +166,20 @@ namespace nexo::renderer { stbi_image_free(icon.pixels); } - void OpenGlWindow::setErrorCallback(void *fctPtr) + void NxOpenGlWindow::setErrorCallback(void *fctPtr) { glfwSetErrorCallback(reinterpret_cast(fctPtr)); } // Linux specific method #ifdef __linux__ - void OpenGlWindow::setWaylandAppId(const char* appId) + void NxOpenGlWindow::setWaylandAppId(const char* appId) { _waylandAppId = appId; LOG(NEXO_DEV, "Wayland app id set to '{}'", appId); } - void OpenGlWindow::setWmClass(const char* className, const char* instanceName) + void NxOpenGlWindow::setWmClass(const char* className, const char* instanceName) { _x11ClassName = className; _x11InstanceName = instanceName; diff --git a/engine/src/renderer/opengl/OpenGlWindow.hpp b/engine/src/renderer/opengl/OpenGlWindow.hpp index bcf1ad5c7..73f4c1023 100644 --- a/engine/src/renderer/opengl/OpenGlWindow.hpp +++ b/engine/src/renderer/opengl/OpenGlWindow.hpp @@ -19,10 +19,10 @@ namespace nexo::renderer { /** - * @class OpenGlWindow - * @brief OpenGL-specific implementation of the `Window` class. + * @class NxOpenGlWindow + * @brief OpenGL-specific implementation of the `NxWindow` class. * - * The `OpenGlWindow` class manages the creation and behavior of a window in + * 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. * @@ -31,19 +31,19 @@ namespace nexo::renderer { * - Provide event handling for window, keyboard, and mouse events. * - Manage OpenGL context initialization and VSync settings. */ - class OpenGlWindow final : public Window { + class NxOpenGlWindow final : public NxWindow { public: /** * @brief Creates an OpenGL window with the specified properties. * - * Initializes the `WindowProperty` structure with the given width, height, + * 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 OpenGlWindow(const int width = 1920, + explicit NxOpenGlWindow(const int width = 1920, const int height = 1080, const char *title = "Nexo window") : _props(width, height, title) {} @@ -54,8 +54,8 @@ namespace nexo::renderer { * Creates the window using GLFW, sets up the OpenGL context, and configures * callbacks for handling window events like resizing, closing, and input. * - * @throw GraphicsApiInitFailure If GLFW initialization fails. - * @throw GraphicsApiWindowInitFailure If the window creation fails. + * @throw NxGraphicsApiInitFailure If GLFW initialization fails. + * @throw NxGraphicsApiWindowInitFailure If the window creation fails. */ void init() override; @@ -116,7 +116,7 @@ namespace nexo::renderer { #endif private: GLFWwindow *_openGlWindow{}; - WindowProperty _props; + NxWindowProperty _props; void setupCallback() const; }; diff --git a/engine/src/renderer/primitives/Cube.cpp b/engine/src/renderer/primitives/Cube.cpp index cb2e0691e..c1c020510 100644 --- a/engine/src/renderer/primitives/Cube.cpp +++ b/engine/src/renderer/primitives/Cube.cpp @@ -116,11 +116,11 @@ namespace nexo::renderer { std::ranges::copy(norm, normals.begin()); } - void Renderer3D::drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::vec4 &color, const int entityID) const + void NxRenderer3D::drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::vec4 &color, const int entityID) const { if (!m_renderingScene) { - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); } @@ -130,7 +130,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::InternalMaterial mat; + renderer::NxIndexedMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -164,11 +164,11 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3 &rotation, const glm::vec4& color, int entityID) const + void NxRenderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3 &rotation, const glm::vec4& color, int entityID) const { if (!m_renderingScene) { - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); } @@ -181,7 +181,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::InternalMaterial mat; + renderer::NxIndexedMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -215,18 +215,18 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::mat4& transform, const glm::vec4& color, int entityID) const + void NxRenderer3D::drawCube(const glm::mat4& transform, const glm::vec4& color, int entityID) const { if (!m_renderingScene) { - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); } m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::InternalMaterial mat; + renderer::NxIndexedMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -260,11 +260,11 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const Material& material, int entityID) const + void NxRenderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const NxMaterial& material, int entityID) const { if (!m_renderingScene) { - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); } @@ -274,7 +274,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::InternalMaterial mat; + renderer::NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; @@ -312,11 +312,11 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const Material& material, int entityID) const + void NxRenderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const NxMaterial& material, int entityID) const { if (!m_renderingScene) { - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); } @@ -329,7 +329,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::InternalMaterial mat; + renderer::NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; @@ -367,11 +367,11 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const Material& material, int entityID) const + void NxRenderer3D::drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const NxMaterial& material, int entityID) const { if (!m_renderingScene) { - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); } @@ -384,7 +384,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::InternalMaterial mat; + renderer::NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; @@ -422,17 +422,17 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void Renderer3D::drawCube(const glm::mat4& transform, const Material& material, int entityID) const + void NxRenderer3D::drawCube(const glm::mat4& transform, const NxMaterial& material, int entityID) const { if (!m_renderingScene) { - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); } m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::InternalMaterial mat; + renderer::NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; diff --git a/engine/src/renderer/primitives/Mesh.cpp b/engine/src/renderer/primitives/Mesh.cpp index 3e601ab5b..20a005138 100644 --- a/engine/src/renderer/primitives/Mesh.cpp +++ b/engine/src/renderer/primitives/Mesh.cpp @@ -18,26 +18,26 @@ namespace nexo::renderer { - void Renderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::vec3& position, [[maybe_unused]] const glm::vec3& size, [[maybe_unused]] const renderer::Material& material, [[maybe_unused]] int entityID) const + void NxRenderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::vec3& position, [[maybe_unused]] const glm::vec3& size, [[maybe_unused]] const renderer::NxMaterial& material, [[maybe_unused]] int entityID) const { } - void Renderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::vec3& position, [[maybe_unused]] const glm::vec3& rotation, [[maybe_unused]] const glm::vec3& size, [[maybe_unused]] const renderer::Material& material, [[maybe_unused]] int entityID) const + void NxRenderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::vec3& position, [[maybe_unused]] const glm::vec3& rotation, [[maybe_unused]] const glm::vec3& size, [[maybe_unused]] const renderer::NxMaterial& material, [[maybe_unused]] int entityID) const { } - void Renderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::mat4& transform, [[maybe_unused]] const renderer::Material& material, [[maybe_unused]] int entityID) const + void NxRenderer3D::drawMesh([[maybe_unused]] const std::vector& vertices, [[maybe_unused]] const std::vector& indices, [[maybe_unused]] const glm::mat4& transform, [[maybe_unused]] const renderer::NxMaterial& material, [[maybe_unused]] int entityID) const { } - void Renderer3D::drawMesh(const std::vector &vertices, const std::vector &indices, - [[maybe_unused]] const std::shared_ptr &texture, int entityID) const + void NxRenderer3D::drawMesh(const std::vector &vertices, const std::vector &indices, + [[maybe_unused]] const std::shared_ptr &texture, int entityID) const { if (!m_renderingScene) - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); if ((m_storage->vertexBufferPtr - m_storage->vertexBufferBase.data()) + vertices.size() > m_storage->maxVertices || m_storage->indexCount + indices.size() > m_storage->maxIndices) diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index 06e50b5a7..c01275f3b 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -36,11 +36,11 @@ namespace nexo::system { RenderSystem::RenderSystem() { - renderer::FramebufferSpecs maskFramebufferSpecs; - maskFramebufferSpecs.attachments = { renderer::FrameBufferTextureFormats::RGBA8 }; + renderer::NxFramebufferSpecs maskFramebufferSpecs; + maskFramebufferSpecs.attachments = { renderer::NxFrameBufferTextureFormats::RGBA8 }; maskFramebufferSpecs.width = 1280; // Default size, will be resized as needed maskFramebufferSpecs.height = 720; - m_maskFramebuffer = renderer::Framebuffer::create(maskFramebufferSpecs); + m_maskFramebuffer = renderer::NxFramebuffer::create(maskFramebufferSpecs); // Create fullscreen quad for post-processing m_fullscreenQuad = renderer::createVertexArray(); @@ -310,8 +310,8 @@ namespace nexo::system { if (camera.renderTarget != nullptr) { camera.renderTarget->bind(); //TODO: Find a way to automatically clear color attachments - renderer::RenderCommand::setClearColor(camera.clearColor); - renderer::RenderCommand::clear(); + renderer::NxRenderCommand::setClearColor(camera.clearColor); + renderer::NxRenderCommand::clear(); camera.renderTarget->clearAttachment(1, -1); } @@ -339,7 +339,7 @@ namespace nexo::system { renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, material.shader); auto shader = renderContext.renderer3D.getShader(); setupLights(shader, renderContext.sceneLights); - auto context = std::make_shared(); + auto context = std::make_shared(); context->renderer3D = renderContext.renderer3D; render.draw(context, transform, static_cast(entity)); renderContext.renderer3D.endScene(); diff --git a/tests/engine/components/Camera.test.cpp b/tests/engine/components/Camera.test.cpp index d91bacd10..6970434bb 100644 --- a/tests/engine/components/Camera.test.cpp +++ b/tests/engine/components/Camera.test.cpp @@ -22,7 +22,7 @@ #include // Dummy implementation of the Framebuffer interface for testing. -class DummyFramebuffer : public nexo::renderer::Framebuffer { +class DummyFramebuffer : public nexo::renderer::NxFramebuffer { public: void bind() override {} void unbind() override {} @@ -32,13 +32,13 @@ class DummyFramebuffer : public nexo::renderer::Framebuffer { void resize(unsigned int, unsigned int ) override {} void getPixelWrapper(unsigned int, int, int, void *, const std::type_info &) const override {} void clearAttachmentWrapper(unsigned int, const void *, const std::type_info &) const override {} - [[nodiscard]] nexo::renderer::FramebufferSpecs &getSpecs() override { static nexo::renderer::FramebufferSpecs specs; return specs; } - [[nodiscard]] const nexo::renderer::FramebufferSpecs &getSpecs() const override { static nexo::renderer::FramebufferSpecs specs; return specs; } + [[nodiscard]] nexo::renderer::NxFramebufferSpecs &getSpecs() override { static nexo::renderer::NxFramebufferSpecs specs; return specs; } + [[nodiscard]] const nexo::renderer::NxFramebufferSpecs &getSpecs() const override { static nexo::renderer::NxFramebufferSpecs specs; return specs; } [[nodiscard]] unsigned int getColorAttachmentId(unsigned int) const override { return 0; } unsigned int getDepthAttachmentId() const override { return 0; } }; -std::shared_ptr createDummyFramebuffer() { +std::shared_ptr createDummyFramebuffer() { return std::make_shared(); } diff --git a/tests/renderer/Buffer.test.cpp b/tests/renderer/Buffer.test.cpp index 6da261674..33516cb77 100644 --- a/tests/renderer/Buffer.test.cpp +++ b/tests/renderer/Buffer.test.cpp @@ -24,17 +24,17 @@ namespace nexo::renderer { // Mock for testing abstract classes - class MockVertexBuffer : public VertexBuffer { + class MockVertexBuffer : public NxVertexBuffer { public: MOCK_METHOD(void, bind, (), (const, override)); MOCK_METHOD(void, unbind, (), (const, override)); - MOCK_METHOD(void, setLayout, (const BufferLayout&), (override)); - MOCK_METHOD(BufferLayout, getLayout, (), (const, override)); + MOCK_METHOD(void, setLayout, (const NxBufferLayout&), (override)); + MOCK_METHOD(NxBufferLayout, getLayout, (), (const, override)); MOCK_METHOD(void, setData, (void*, unsigned int), (override)); MOCK_METHOD(unsigned int, getId, (), (const, override)); }; - class MockIndexBuffer : public IndexBuffer { + class MockIndexBuffer : public NxIndexBuffer { public: MOCK_METHOD(void, bind, (), (const, override)); MOCK_METHOD(void, unbind, (), (const, override)); @@ -135,52 +135,52 @@ namespace nexo::renderer { // Tests for ShaderDataType operations TEST(ShaderDataTypeTest, ShaderDataTypeSizeReturnsCorrectSizes) { - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::FLOAT), 4); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::FLOAT2), 8); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::FLOAT3), 12); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::FLOAT4), 16); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::MAT3), 36); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::MAT4), 64); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::INT), 4); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::INT2), 8); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::INT3), 12); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::INT4), 16); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::BOOL), 1); - EXPECT_EQ(shaderDataTypeSize(ShaderDataType::NONE), 0); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::FLOAT), 4); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::FLOAT2), 8); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::FLOAT3), 12); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::FLOAT4), 16); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::MAT3), 36); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::MAT4), 64); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::INT), 4); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::INT2), 8); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::INT3), 12); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::INT4), 16); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::BOOL), 1); + EXPECT_EQ(shaderDataTypeSize(NxShaderDataType::NONE), 0); } // Tests for BufferElements TEST(BufferElementsTest, ConstructorSetsProperties) { - BufferElements element(ShaderDataType::FLOAT3, "Position", false); + NxBufferElements element(NxShaderDataType::FLOAT3, "Position", false); EXPECT_EQ(element.name, "Position"); - EXPECT_EQ(element.type, ShaderDataType::FLOAT3); + EXPECT_EQ(element.type, NxShaderDataType::FLOAT3); EXPECT_EQ(element.size, 12); // FLOAT3 = 3 * 4 bytes EXPECT_EQ(element.offset, 0); // Initial offset is 0 EXPECT_FALSE(element.normalized); } TEST(BufferElementsTest, GetComponentCountReturnsCorrectCount) { - EXPECT_EQ(BufferElements(ShaderDataType::FLOAT, "").getComponentCount(), 1); - EXPECT_EQ(BufferElements(ShaderDataType::FLOAT2, "").getComponentCount(), 2); - EXPECT_EQ(BufferElements(ShaderDataType::FLOAT3, "").getComponentCount(), 3); - EXPECT_EQ(BufferElements(ShaderDataType::FLOAT4, "").getComponentCount(), 4); - EXPECT_EQ(BufferElements(ShaderDataType::INT, "").getComponentCount(), 1); - EXPECT_EQ(BufferElements(ShaderDataType::INT2, "").getComponentCount(), 2); - EXPECT_EQ(BufferElements(ShaderDataType::INT3, "").getComponentCount(), 3); - EXPECT_EQ(BufferElements(ShaderDataType::INT4, "").getComponentCount(), 4); - EXPECT_EQ(BufferElements(ShaderDataType::MAT3, "").getComponentCount(), 9); - EXPECT_EQ(BufferElements(ShaderDataType::MAT4, "").getComponentCount(), 16); - EXPECT_EQ(BufferElements(ShaderDataType::BOOL, "").getComponentCount(), 1); - EXPECT_EQ(BufferElements(ShaderDataType::NONE, "").getComponentCount(), -1); + EXPECT_EQ(NxBufferElements(NxShaderDataType::FLOAT, "").getComponentCount(), 1); + EXPECT_EQ(NxBufferElements(NxShaderDataType::FLOAT2, "").getComponentCount(), 2); + EXPECT_EQ(NxBufferElements(NxShaderDataType::FLOAT3, "").getComponentCount(), 3); + EXPECT_EQ(NxBufferElements(NxShaderDataType::FLOAT4, "").getComponentCount(), 4); + EXPECT_EQ(NxBufferElements(NxShaderDataType::INT, "").getComponentCount(), 1); + EXPECT_EQ(NxBufferElements(NxShaderDataType::INT2, "").getComponentCount(), 2); + EXPECT_EQ(NxBufferElements(NxShaderDataType::INT3, "").getComponentCount(), 3); + EXPECT_EQ(NxBufferElements(NxShaderDataType::INT4, "").getComponentCount(), 4); + EXPECT_EQ(NxBufferElements(NxShaderDataType::MAT3, "").getComponentCount(), 9); + EXPECT_EQ(NxBufferElements(NxShaderDataType::MAT4, "").getComponentCount(), 16); + EXPECT_EQ(NxBufferElements(NxShaderDataType::BOOL, "").getComponentCount(), 1); + EXPECT_EQ(NxBufferElements(NxShaderDataType::NONE, "").getComponentCount(), -1); } // Tests for BufferLayout TEST(BufferLayoutTest, ConstructorWithInitializerListCalculatesOffsetsAndStride) { - BufferLayout layout = { - {ShaderDataType::FLOAT3, "Position"}, - {ShaderDataType::FLOAT4, "Color"}, - {ShaderDataType::FLOAT2, "TexCoord"} + NxBufferLayout layout = { + {NxShaderDataType::FLOAT3, "Position"}, + {NxShaderDataType::FLOAT4, "Color"}, + {NxShaderDataType::FLOAT2, "TexCoord"} }; EXPECT_EQ(layout.getStride(), 36); // 12 + 16 + 8 @@ -194,15 +194,15 @@ namespace nexo::renderer { } TEST(BufferLayoutTest, EmptyConstructorCreatesEmptyLayout) { - BufferLayout layout; + NxBufferLayout layout; EXPECT_EQ(layout.getStride(), 0); EXPECT_TRUE(layout.getElements().empty()); } TEST(BufferLayoutTest, IteratorFunctionsWork) { - BufferLayout layout = { - {ShaderDataType::FLOAT3, "Position"}, - {ShaderDataType::FLOAT4, "Color"} + NxBufferLayout layout = { + {NxShaderDataType::FLOAT3, "Position"}, + {NxShaderDataType::FLOAT4, "Color"} }; int count = 0; @@ -215,8 +215,8 @@ namespace nexo::renderer { // Mocking tests for abstract classes TEST(BufferMockTest, MockVertexBufferCanCallMethods) { MockVertexBuffer buffer; - BufferLayout layout = { - {ShaderDataType::FLOAT3, "Position"} + NxBufferLayout layout = { + {NxShaderDataType::FLOAT3, "Position"} }; EXPECT_CALL(buffer, setLayout(testing::_)).Times(1); @@ -227,7 +227,7 @@ namespace nexo::renderer { EXPECT_CALL(buffer, getId()).WillOnce(testing::Return(1)); buffer.setLayout(layout); - BufferLayout retrievedLayout = buffer.getLayout(); + NxBufferLayout retrievedLayout = buffer.getLayout(); buffer.bind(); buffer.unbind(); float data[] = {1.0f, 2.0f, 3.0f}; @@ -262,7 +262,7 @@ namespace nexo::renderer { TEST_F(BufferFactoryTest, CreateVertexBufferWithDataReturnsValidBuffer) { float vertices[] = {1.0f, 2.0f, 3.0f}; - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL auto buffer = createVertexBuffer(vertices, sizeof(vertices)); ASSERT_NE(buffer, nullptr); EXPECT_NE(buffer->getId(), 0); @@ -272,7 +272,7 @@ namespace nexo::renderer { } TEST_F(BufferFactoryTest, CreateVertexBufferWithSizeReturnsValidBuffer) { - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL auto buffer = createVertexBuffer(1024); ASSERT_NE(buffer, nullptr); EXPECT_NE(buffer->getId(), 0); @@ -282,7 +282,7 @@ namespace nexo::renderer { } TEST_F(BufferFactoryTest, CreateIndexBufferReturnsValidBuffer) { - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL auto buffer = createIndexBuffer(); ASSERT_NE(buffer, nullptr); EXPECT_NE(buffer->getId(), 0); @@ -292,11 +292,11 @@ namespace nexo::renderer { } // OpenGL specific implementation tests - using TEST_F with OpenGLBufferTest fixture - #ifdef GRAPHICS_API_OPENGL + #ifdef NX_GRAPHICS_API_OPENGL TEST_F(OpenGLBufferTest, OpenGlVertexBufferWithDataWorksCorrectly) { float vertices[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; - OpenGlVertexBuffer buffer(vertices, sizeof(vertices)); + NxOpenGlVertexBuffer buffer(vertices, sizeof(vertices)); EXPECT_NE(buffer.getId(), 0); @@ -305,11 +305,11 @@ namespace nexo::renderer { EXPECT_NO_THROW(buffer.unbind()); // Test layout setting and retrieval - BufferLayout layout = { - {ShaderDataType::FLOAT3, "Position"} + NxBufferLayout layout = { + {NxShaderDataType::FLOAT3, "Position"} }; EXPECT_NO_THROW(buffer.setLayout(layout)); - BufferLayout retrievedLayout = buffer.getLayout(); + NxBufferLayout retrievedLayout = buffer.getLayout(); EXPECT_EQ(retrievedLayout.getStride(), layout.getStride()); // Test setting new data @@ -319,7 +319,7 @@ namespace nexo::renderer { TEST_F(OpenGLBufferTest, OpenGlVertexBufferEmptyConstructorWorksCorrectly) { - OpenGlVertexBuffer buffer(1024); + NxOpenGlVertexBuffer buffer(1024); EXPECT_NE(buffer.getId(), 0); @@ -334,7 +334,7 @@ namespace nexo::renderer { TEST_F(OpenGLBufferTest, OpenGlIndexBufferWorksCorrectly) { - OpenGlIndexBuffer buffer; + NxOpenGlIndexBuffer buffer; EXPECT_NE(buffer.getId(), 0); @@ -347,5 +347,5 @@ namespace nexo::renderer { EXPECT_NO_THROW(buffer.setData(indices, 6)); EXPECT_EQ(buffer.getCount(), 6); } - #endif // GRAPHICS_API_OPENGL + #endif // NX_GRAPHICS_API_OPENGL } diff --git a/tests/renderer/CMakeLists.txt b/tests/renderer/CMakeLists.txt index cde699127..507db26a9 100644 --- a/tests/renderer/CMakeLists.txt +++ b/tests/renderer/CMakeLists.txt @@ -74,7 +74,7 @@ find_package(Stb REQUIRED) target_include_directories(renderer_tests PRIVATE ${Stb_INCLUDE_DIR}) target_sources(renderer_tests PRIVATE ${CMAKE_SOURCE_DIR}/engine/external/stb_image.cpp) -target_compile_definitions(renderer_tests PRIVATE GRAPHICS_API_OPENGL) +target_compile_definitions(renderer_tests PRIVATE NX_GRAPHICS_API_OPENGL) find_package(OpenGL REQUIRED) find_package(glfw3 3.3 REQUIRED) find_package(glad CONFIG REQUIRED) diff --git a/tests/renderer/Exceptions.test.cpp b/tests/renderer/Exceptions.test.cpp index ffd9d4786..0922a1d9d 100644 --- a/tests/renderer/Exceptions.test.cpp +++ b/tests/renderer/Exceptions.test.cpp @@ -21,7 +21,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; // Account for the next line - FileNotFoundException ex("test_file.txt"); + NxFileNotFoundException ex("test_file.txt"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("File not found: test_file.txt"), std::string::npos); @@ -33,7 +33,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - UnknownGraphicsApi ex("Vulkan"); + NxUnknownGraphicsApi ex("Vulkan"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("Unknown graphics API: Vulkan"), std::string::npos); @@ -45,7 +45,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - GraphicsApiInitFailure ex("OpenGL"); + NxGraphicsApiInitFailure ex("OpenGL"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("Failed to initialize graphics API: OpenGL"), std::string::npos); @@ -57,7 +57,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - ShaderCreationFailed ex("OpenGL", "Compilation error", "shader.glsl"); + NxShaderCreationFailed ex("OpenGL", "Compilation error", "shader.glsl"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Failed to create the shader (shader.glsl): Compilation error"), std::string::npos); @@ -69,7 +69,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - FramebufferResizingFailed ex("Vulkan", false, 800, 600); + NxFramebufferResizingFailed ex("Vulkan", false, 800, 600); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[Vulkan] Framebuffer resizing failed: 800x600 is too small"), std::string::npos); @@ -81,7 +81,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - RendererNotInitialized ex(RendererType::RENDERER_3D); + NxRendererNotInitialized ex(NxRendererType::RENDERER_3D); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[RENDERER 3D] Renderer not initialized, call the init function first"), std::string::npos); @@ -93,7 +93,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - TextureInvalidSize ex("OpenGL", 4096, 4096, 2048); + NxTextureInvalidSize ex("OpenGL", 4096, 4096, 2048); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Invalid size for texture: 4096x4096 is too big, max texture size is : 2048"), std::string::npos); @@ -105,7 +105,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - StbiLoadException ex("Invalid PNG file"); + NxStbiLoadException ex("Invalid PNG file"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("STBI load failed: Invalid PNG file"), std::string::npos); @@ -117,7 +117,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - OutOfRangeException ex(10, 5); + NxOutOfRangeException ex(10, 5); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("Index 10 is out of range [0, 5)"), std::string::npos); @@ -141,7 +141,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - GraphicsApiViewportResizingFailure ex("OpenGL", true, 4096, 4096); + NxGraphicsApiViewportResizingFailure ex("OpenGL", true, 4096, 4096); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Viewport resizing failed: 4096x4096 is too big"), std::string::npos); @@ -153,7 +153,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - GraphicsApiWindowInitFailure ex("OpenGL"); + NxGraphicsApiWindowInitFailure ex("OpenGL"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("Failed to initialize graphics API: OpenGL"), std::string::npos); @@ -165,7 +165,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - InvalidValue ex("OpenGL", "Negative width value"); + NxInvalidValue ex("OpenGL", "Negative width value"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Invalid value: Negative width value"), std::string::npos); @@ -177,7 +177,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - ShaderInvalidUniform ex("OpenGL", "main.glsl", "u_ViewProjection"); + NxShaderInvalidUniform ex("OpenGL", "main.glsl", "u_ViewProjection"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Failed to retrieve uniform \"u_ViewProjection\" in shader: main.glsl"), std::string::npos); @@ -189,7 +189,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - FramebufferCreationFailed ex("OpenGL"); + NxFramebufferCreationFailed ex("OpenGL"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Failed to create the framebuffer"), std::string::npos); @@ -201,7 +201,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - FramebufferUnsupportedColorFormat ex("OpenGL"); + NxFramebufferUnsupportedColorFormat ex("OpenGL"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Unsupported framebuffer color attachment format"), std::string::npos); @@ -213,7 +213,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - FramebufferUnsupportedDepthFormat ex("OpenGL"); + NxFramebufferUnsupportedDepthFormat ex("OpenGL"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Unsupported framebuffer depth attachment format"), std::string::npos); @@ -225,7 +225,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - FramebufferReadFailure ex("OpenGL", 0, 100, 200); + NxFramebufferReadFailure ex("OpenGL", 0, 100, 200); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Unable to read framebuffer with index 0 at coordinate (100, 200)"), std::string::npos); @@ -237,7 +237,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - FramebufferInvalidIndex ex("OpenGL", 5); + NxFramebufferInvalidIndex ex("OpenGL", 5); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Invalid attachment index : 5"), std::string::npos); @@ -249,7 +249,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - BufferLayoutEmpty ex("OpenGL"); + NxBufferLayoutEmpty ex("OpenGL"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Vertex buffer layout cannot be empty"), std::string::npos); @@ -261,7 +261,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - TextureUnsupportedFormat ex("OpenGL", 5, "texture.exr"); + NxTextureUnsupportedFormat ex("OpenGL", 5, "texture.exr"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Unsupported image format with 5 channels in texture.exr"), std::string::npos); diff --git a/tests/renderer/Framebuffer.test.cpp b/tests/renderer/Framebuffer.test.cpp index a301fd06e..3dcefe245 100644 --- a/tests/renderer/Framebuffer.test.cpp +++ b/tests/renderer/Framebuffer.test.cpp @@ -27,15 +27,15 @@ namespace nexo::renderer { TEST_F(OpenGLTest, FramebufferCreationAndBinding) { - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 800; specs.height = 600; specs.samples = 1; specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8}, - {FrameBufferTextureFormats::DEPTH24STENCIL8} + {NxFrameBufferTextureFormats::RGBA8}, + {NxFrameBufferTextureFormats::DEPTH24STENCIL8} }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); // Validate framebuffer id EXPECT_NE(framebuffer.getFramebufferId(), 0); @@ -70,15 +70,15 @@ namespace nexo::renderer { TEST_F(OpenGLTest, FramebufferResize) { - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 800; specs.height = 600; specs.samples = 1; specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8}, - {FrameBufferTextureFormats::DEPTH24STENCIL8} + {NxFrameBufferTextureFormats::RGBA8}, + {NxFrameBufferTextureFormats::DEPTH24STENCIL8} }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); framebuffer.resize(1024, 768); @@ -90,50 +90,50 @@ namespace nexo::renderer { TEST_F(OpenGLTest, ResizeWithInvalidDimensions) { - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 800; specs.height = 600; specs.samples = 1; specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8}, - {FrameBufferTextureFormats::DEPTH24STENCIL8} + {NxFrameBufferTextureFormats::RGBA8}, + {NxFrameBufferTextureFormats::DEPTH24STENCIL8} }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); - EXPECT_THROW(framebuffer.resize(0, 600), FramebufferResizingFailed); - EXPECT_THROW(framebuffer.resize(800, 0), FramebufferResizingFailed); - EXPECT_THROW(framebuffer.resize(9000, 600), FramebufferResizingFailed); - EXPECT_THROW(framebuffer.resize(800, 9000), FramebufferResizingFailed); + EXPECT_THROW(framebuffer.resize(0, 600), NxFramebufferResizingFailed); + EXPECT_THROW(framebuffer.resize(800, 0), NxFramebufferResizingFailed); + EXPECT_THROW(framebuffer.resize(9000, 600), NxFramebufferResizingFailed); + EXPECT_THROW(framebuffer.resize(800, 9000), NxFramebufferResizingFailed); } TEST_F(OpenGLTest, InvalidFramebufferCreation) { - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 0; specs.height = 600; - EXPECT_THROW(OpenGlFramebuffer framebuffer(specs), FramebufferResizingFailed); + EXPECT_THROW(NxOpenGlFramebuffer framebuffer(specs), NxFramebufferResizingFailed); specs.width = 800; specs.height = 0; - EXPECT_THROW(OpenGlFramebuffer framebuffer(specs), FramebufferResizingFailed); + EXPECT_THROW(NxOpenGlFramebuffer framebuffer(specs), NxFramebufferResizingFailed); specs.width = 9000; specs.height = 600; - EXPECT_THROW(OpenGlFramebuffer framebuffer(specs), FramebufferResizingFailed); + EXPECT_THROW(NxOpenGlFramebuffer framebuffer(specs), NxFramebufferResizingFailed); specs.width = 800; specs.height = 9000; - EXPECT_THROW(OpenGlFramebuffer framebuffer(specs), FramebufferResizingFailed); + EXPECT_THROW(NxOpenGlFramebuffer framebuffer(specs), NxFramebufferResizingFailed); } TEST_F(OpenGLTest, MultipleColorAttachments) { - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 800; specs.height = 600; specs.samples = 1; specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8}, - {FrameBufferTextureFormats::RGBA16}, - {FrameBufferTextureFormats::DEPTH24STENCIL8} + {NxFrameBufferTextureFormats::RGBA8}, + {NxFrameBufferTextureFormats::RGBA16}, + {NxFrameBufferTextureFormats::DEPTH24STENCIL8} }; // Check if the hardware supports at least the required number of attachments @@ -141,7 +141,7 @@ namespace nexo::renderer { glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxAttachments); EXPECT_GE(maxAttachments, static_cast(specs.attachments.attachments.size())); - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); // Verify the IDs of the color attachments EXPECT_NE(framebuffer.getColorAttachmentId(0), 0); @@ -169,7 +169,7 @@ namespace nexo::renderer { { EXPECT_EQ(static_cast(boundTexture), framebuffer.getColorAttachmentId(i)); } else if (framebuffer.getSpecs().attachments.attachments[i].textureFormat == - FrameBufferTextureFormats::DEPTH24STENCIL8) + NxFrameBufferTextureFormats::DEPTH24STENCIL8) { EXPECT_EQ(static_cast(boundTexture), framebuffer.getDepthAttachmentId()); @@ -265,19 +265,19 @@ namespace nexo::renderer { TEST_F(OpenGLTest, InvalidFormat) { - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 800; specs.height = 600; specs.samples = 1; // Test unsupported color format specs.attachments.attachments = { - {static_cast(999)} // Invalid format + {static_cast(999)} // Invalid format }; EXPECT_THROW( - OpenGlFramebuffer framebuffer(specs); - , FramebufferUnsupportedColorFormat); + NxOpenGlFramebuffer framebuffer(specs); + , NxFramebufferUnsupportedColorFormat); } TEST_F(OpenGLTest, GetPixelWrapperValid) @@ -286,15 +286,15 @@ namespace nexo::renderer { // TODO: fix test (see #99) GTEST_SKIP() << "This test infinitely loops on the CI on Windows, skipping for now."; #endif - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 100; specs.height = 100; specs.samples = 1; specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8} + {NxFrameBufferTextureFormats::RGBA8} }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); framebuffer.bind(); int pixelValue = 0; @@ -306,55 +306,55 @@ namespace nexo::renderer { TEST_F(OpenGLTest, GetPixelWrapperUnsupportedType) { // Verify that getPixelWrapper throws when provided with a type other than int. - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 100; specs.height = 100; specs.samples = 1; specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8} + {NxFrameBufferTextureFormats::RGBA8} }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); int dummy = 0; EXPECT_THROW( framebuffer.getPixelWrapper(0, 50, 50, &dummy, typeid(float)), - FramebufferUnsupportedColorFormat + NxFramebufferUnsupportedColorFormat ); } TEST_F(OpenGLTest, GetPixelWrapperInvalidAttachmentIndex) { // Verify that getPixelWrapper throws if the attachment index is out of bounds. - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 100; specs.height = 100; specs.samples = 1; // Only one color attachment. specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8} + {NxFrameBufferTextureFormats::RGBA8} }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); int dummy = 0; // Attachment index 1 is invalid because only index 0 exists. EXPECT_THROW( framebuffer.getPixelWrapper(1, 50, 50, &dummy, typeid(int)), - FramebufferInvalidIndex + NxFramebufferInvalidIndex ); } TEST_F(OpenGLTest, ClearAttachmentWrapperValid) { // Test that clearAttachmentWrapper does not throw when clearing a valid attachment with a supported type. - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 100; specs.height = 100; specs.samples = 1; specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8} + {NxFrameBufferTextureFormats::RGBA8} }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); int clearValue = 0; EXPECT_NO_THROW(framebuffer.clearAttachmentWrapper(0, &clearValue, typeid(int))); } @@ -362,51 +362,51 @@ namespace nexo::renderer { TEST_F(OpenGLTest, ClearAttachmentWrapperUnsupportedType) { // Test that clearAttachmentWrapper throws if called with an unsupported type. - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 100; specs.height = 100; specs.samples = 1; specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8} + {NxFrameBufferTextureFormats::RGBA8} }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); int clearValue = 0; EXPECT_THROW( framebuffer.clearAttachmentWrapper(0, &clearValue, typeid(float)), - FramebufferUnsupportedColorFormat + NxFramebufferUnsupportedColorFormat ); } TEST_F(OpenGLTest, ClearAttachmentWrapperInvalidAttachmentIndex) { // Test that clearAttachmentWrapper throws when the attachment index is out of range. - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 100; specs.height = 100; specs.samples = 1; // Only one color attachment exists. specs.attachments.attachments = { - {FrameBufferTextureFormats::RGBA8} + {NxFrameBufferTextureFormats::RGBA8} }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); int clearValue = 0; // Attachment index 1 is invalid. EXPECT_THROW( framebuffer.clearAttachmentWrapper(1, &clearValue, typeid(int)), - FramebufferInvalidIndex + NxFramebufferInvalidIndex ); } TEST_F(OpenGLTest, ClearAndGetPixelRedIntegerAttachment) { - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 100; specs.height = 100; specs.samples = 1; - specs.attachments.attachments = { FrameBufferTextureFormats::RED_INTEGER }; + specs.attachments.attachments = { NxFrameBufferTextureFormats::RED_INTEGER }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); framebuffer.bind(); int clearValue = 123; EXPECT_NO_THROW(framebuffer.clearAttachmentWrapper(0, &clearValue, typeid(int))); @@ -418,16 +418,16 @@ namespace nexo::renderer { } TEST_F(OpenGLTest, ClearAndGetPixelMultipleAttachments) { - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 100; specs.height = 100; specs.samples = 1; specs.attachments.attachments = { - { FrameBufferTextureFormats::RGBA8 }, - { FrameBufferTextureFormats::RED_INTEGER } + { NxFrameBufferTextureFormats::RGBA8 }, + { NxFrameBufferTextureFormats::RED_INTEGER } }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); framebuffer.bind(); // Clear the second (red integer) attachment to a known value. @@ -443,13 +443,13 @@ namespace nexo::renderer { // While OpenGL's glReadPixels does not throw exceptions for out–of–bounds reads, we ensure // that our wrapper does not crash. (The returned value may be undefined.) TEST_F(OpenGLTest, GetPixelOutOfBoundsRedIntegerAttachment) { - FramebufferSpecs specs; + NxFramebufferSpecs specs; specs.width = 50; specs.height = 50; specs.samples = 1; - specs.attachments.attachments = { { static_cast(3) } }; + specs.attachments.attachments = { { static_cast(3) } }; - OpenGlFramebuffer framebuffer(specs); + NxOpenGlFramebuffer framebuffer(specs); framebuffer.bind(); int pixelValue = 0; // Attempt to read a pixel well outside the 50x50 region. diff --git a/tests/renderer/Renderer3D.test.cpp b/tests/renderer/Renderer3D.test.cpp index 7be94e850..627b287e8 100644 --- a/tests/renderer/Renderer3D.test.cpp +++ b/tests/renderer/Renderer3D.test.cpp @@ -66,8 +66,8 @@ namespace nexo::renderer { glfwTerminate(); GTEST_SKIP() << "OpenGL 4.5 is required. Skipping OpenGL tests."; } - renderer3D = std::make_unique(); - Renderer::init(); + renderer3D = std::make_unique(); + NxRenderer::init(); EXPECT_NO_THROW(renderer3D->init()); } @@ -80,7 +80,7 @@ namespace nexo::renderer { } } - std::unique_ptr renderer3D; + std::unique_ptr renderer3D; }; TEST_F(Renderer3DTest, BeginEndScene) @@ -119,8 +119,8 @@ namespace nexo::renderer { // Validate vertex buffer data: GLuint vertexBufferId = renderer3D->getInternalStorage()->vertexBuffer->getId(); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId); - std::vector vertexData(36); // Expecting 36 vertices - glGetBufferSubData(GL_ARRAY_BUFFER, 0, 36 * sizeof(Vertex), vertexData.data()); + std::vector vertexData(36); // Expecting 36 vertices + glGetBufferSubData(GL_ARRAY_BUFFER, 0, 36 * sizeof(NxVertex), vertexData.data()); // Expected vertex positions for a unit cube const glm::vec3 expectedPositions[36] = { @@ -221,9 +221,9 @@ namespace nexo::renderer { glm::vec3 position = {0.0f, 0.0f, 0.0f}; glm::vec3 size = {1.0f, 1.0f, 1.0f}; - renderer::Material material; + renderer::NxMaterial material; material.albedoColor = {1.0f, 0.0f, 0.0f, 1.0f}; // Red color - material.albedoTexture = Texture2D::create(4, 4); // Example texture + material.albedoTexture = NxTexture2D::create(4, 4); // Example texture GLuint query; glGenQueries(1, &query); @@ -241,7 +241,7 @@ namespace nexo::renderer { glDeleteQueries(1, &query); // Validate render stats - Renderer3DStats stats = renderer3D->getStats(); + NxRenderer3DStats stats = renderer3D->getStats(); EXPECT_EQ(stats.cubeCount, 1); EXPECT_EQ(stats.getTotalVertexCount(), 8); // 1 cube * 8 vertices per cube (as defined in struct) EXPECT_EQ(stats.getTotalIndexCount(), 36); // 1 cube * 36 indices @@ -271,7 +271,7 @@ namespace nexo::renderer { glDeleteQueries(1, &query); // Validate render stats - Renderer3DStats stats = renderer3D->getStats(); + NxRenderer3DStats stats = renderer3D->getStats(); EXPECT_EQ(stats.cubeCount, 1); EXPECT_EQ(stats.getTotalVertexCount(), 8); // 1 cube * 8 vertices EXPECT_EQ(stats.getTotalIndexCount(), 36); // 1 cube * 36 indices @@ -300,7 +300,7 @@ namespace nexo::renderer { glDeleteQueries(1, &query); // Validate render stats - Renderer3DStats stats = renderer3D->getStats(); + NxRenderer3DStats stats = renderer3D->getStats(); EXPECT_EQ(stats.cubeCount, 1); EXPECT_EQ(stats.getTotalVertexCount(), 8); // 1 cube * 8 vertices EXPECT_EQ(stats.getTotalIndexCount(), 36); // 1 cube * 36 indices @@ -312,11 +312,11 @@ namespace nexo::renderer { glm::vec3 size = {2.0f, 2.0f, 2.0f}; glm::vec3 rotation = {45.0f, 30.0f, 60.0f}; - renderer::Material material; + renderer::NxMaterial material; material.albedoColor = {0.0f, 1.0f, 1.0f, 1.0f}; // Cyan color - material.albedoTexture = Texture2D::create(4, 4); // Example texture + material.albedoTexture = NxTexture2D::create(4, 4); // Example texture material.specularColor = {1.0f, 1.0f, 1.0f, 1.0f}; - material.metallicMap = Texture2D::create(2, 2); // Example specular texture + material.metallicMap = NxTexture2D::create(2, 2); // Example specular texture GLuint query; glGenQueries(1, &query); @@ -334,7 +334,7 @@ namespace nexo::renderer { glDeleteQueries(1, &query); // Validate render stats - Renderer3DStats stats = renderer3D->getStats(); + NxRenderer3DStats stats = renderer3D->getStats(); EXPECT_EQ(stats.cubeCount, 1); EXPECT_EQ(stats.getTotalVertexCount(), 8); // 1 cube * 8 vertices EXPECT_EQ(stats.getTotalIndexCount(), 36); // 1 cube * 36 indices @@ -346,9 +346,9 @@ namespace nexo::renderer { glm::rotate(glm::mat4(1.0f), glm::radians(45.0f), {0.0f, 1.0f, 0.0f}) * glm::scale(glm::mat4(1.0f), {2.0f, 2.0f, 2.0f}); - renderer::Material material; + renderer::NxMaterial material; material.albedoColor = {1.0f, 1.0f, 0.0f, 1.0f}; // Yellow color - material.albedoTexture = Texture2D::create(4, 4); // Example texture + material.albedoTexture = NxTexture2D::create(4, 4); // Example texture GLuint query; glGenQueries(1, &query); @@ -366,7 +366,7 @@ namespace nexo::renderer { glDeleteQueries(1, &query); // Validate render stats - Renderer3DStats stats = renderer3D->getStats(); + NxRenderer3DStats stats = renderer3D->getStats(); EXPECT_EQ(stats.cubeCount, 1); EXPECT_EQ(stats.getTotalVertexCount(), 8); // 1 cube * 8 vertices EXPECT_EQ(stats.getTotalIndexCount(), 36); // 1 cube * 36 indices @@ -375,13 +375,13 @@ namespace nexo::renderer { TEST_F(Renderer3DTest, DrawMesh) { // Create a simple mesh (a triangle) - std::vector vertices = { + std::vector vertices = { {{-0.5f, -0.5f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, -1}, {{ 0.5f, -0.5f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, -1}, {{ 0.0f, 0.5f, 0.0f}, {0.5f, 1.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, -1} }; std::vector indices = {0, 1, 2}; - auto texture = Texture2D::create(4, 4); + auto texture = NxTexture2D::create(4, 4); // Use an OpenGL query to count the number of triangles drawn GLuint query; @@ -402,8 +402,8 @@ namespace nexo::renderer { // Validate vertex buffer data GLuint vertexBufferId = renderer3D->getInternalStorage()->vertexBuffer->getId(); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId); - std::vector vertexData(3); // Expecting 3 vertices for a triangle - glGetBufferSubData(GL_ARRAY_BUFFER, 0, 3 * sizeof(Vertex), vertexData.data()); + std::vector vertexData(3); // Expecting 3 vertices for a triangle + glGetBufferSubData(GL_ARRAY_BUFFER, 0, 3 * sizeof(NxVertex), vertexData.data()); // Check vertex data for (unsigned int i = 0; i < 3; ++i) @@ -440,7 +440,7 @@ namespace nexo::renderer { glm::mat4 viewProjection = glm::mat4(1.0f); glm::vec3 cameraPosition = {0.0f, 0.0f, 0.0f}; - EXPECT_THROW(renderer3D->beginScene(viewProjection, cameraPosition), RendererNotInitialized); + EXPECT_THROW(renderer3D->beginScene(viewProjection, cameraPosition), NxRendererNotInitialized); // Re-init for TearDown function renderer3D->init(); } @@ -451,6 +451,6 @@ namespace nexo::renderer { glm::vec3 size = {1.0f, 1.0f, 1.0f}; glm::vec4 color = {1.0f, 0.0f, 0.0f, 1.0f}; - EXPECT_THROW(renderer3D->drawCube(position, size, color), RendererSceneLifeCycleFailure); + EXPECT_THROW(renderer3D->drawCube(position, size, color), NxRendererSceneLifeCycleFailure); } } diff --git a/tests/renderer/RendererAPI.test.cpp b/tests/renderer/RendererAPI.test.cpp index 17f418aad..a9ad0ceea 100644 --- a/tests/renderer/RendererAPI.test.cpp +++ b/tests/renderer/RendererAPI.test.cpp @@ -22,13 +22,13 @@ namespace nexo::renderer { TEST_F(OpenGLTest, InitializationTest) { - OpenGlRendererApi rendererApi; + NxOpenGlRendererApi rendererApi; // Validate init EXPECT_NO_THROW(rendererApi.init()); } TEST_F(OpenGLTest, ViewportSetup) { - OpenGlRendererApi rendererApi; + NxOpenGlRendererApi rendererApi; rendererApi.init(); // Validate viewport resizing @@ -50,19 +50,19 @@ namespace nexo::renderer { // Validate invalid viewport values - EXPECT_THROW(rendererApi.setViewport(0, 0, 0, 600), GraphicsApiViewportResizingFailure); - EXPECT_THROW(rendererApi.setViewport(0, 0, 800, 0), GraphicsApiViewportResizingFailure); + EXPECT_THROW(rendererApi.setViewport(0, 0, 0, 600), NxGraphicsApiViewportResizingFailure); + EXPECT_THROW(rendererApi.setViewport(0, 0, 800, 0), NxGraphicsApiViewportResizingFailure); // Validate too big dimensions unsigned int width = 0; unsigned int height = 0; rendererApi.getMaxViewportSize(&width, &height); EXPECT_THROW(rendererApi.setViewport(0, 0, width + 1, height), - GraphicsApiViewportResizingFailure); + NxGraphicsApiViewportResizingFailure); } TEST_F(OpenGLTest, ClearTest) { - OpenGlRendererApi rendererApi; + NxOpenGlRendererApi rendererApi; rendererApi.init(); // Validate clear color via opengl getters @@ -104,19 +104,19 @@ namespace nexo::renderer { } TEST_F(OpenGLTest, ExceptionOnUninitializedAPI) { - OpenGlRendererApi rendererApi; + NxOpenGlRendererApi rendererApi; // Validate exception is thrown for uninitialized API methods - EXPECT_THROW(rendererApi.setViewport(0, 0, 800, 600), GraphicsApiNotInitialized); - EXPECT_THROW(rendererApi.clear(), GraphicsApiNotInitialized); - EXPECT_THROW(rendererApi.setClearColor(glm::vec4(1.0f)), GraphicsApiNotInitialized); + EXPECT_THROW(rendererApi.setViewport(0, 0, 800, 600), NxGraphicsApiNotInitialized); + EXPECT_THROW(rendererApi.clear(), NxGraphicsApiNotInitialized); + EXPECT_THROW(rendererApi.setClearColor(glm::vec4(1.0f)), NxGraphicsApiNotInitialized); - auto vertexArray = std::make_shared(); - EXPECT_THROW(rendererApi.drawIndexed(vertexArray), GraphicsApiNotInitialized); + auto vertexArray = std::make_shared(); + EXPECT_THROW(rendererApi.drawIndexed(vertexArray), NxGraphicsApiNotInitialized); // Validate exception is thrown when passing a null vertex array rendererApi.init(); - EXPECT_THROW(rendererApi.drawIndexed(nullptr), InvalidValue); + EXPECT_THROW(rendererApi.drawIndexed(nullptr), NxInvalidValue); } } diff --git a/tests/renderer/Shader.test.cpp b/tests/renderer/Shader.test.cpp index 41194c763..018afc065 100644 --- a/tests/renderer/Shader.test.cpp +++ b/tests/renderer/Shader.test.cpp @@ -63,7 +63,7 @@ namespace nexo::renderer { TEST_F(ShaderTest, ShaderCreationFromSource) { - OpenGlShader shader("TestShader", vertexShaderSource, fragmentShaderSource); + NxOpenGlShader shader("TestShader", vertexShaderSource, fragmentShaderSource); // Validate that the shader is bound EXPECT_NO_THROW(shader.bind()); @@ -96,7 +96,7 @@ namespace nexo::renderer { createTemporaryShaderFile(shaderFileContent); - OpenGlShader shader(temporaryShaderFilePath); + NxOpenGlShader shader(temporaryShaderFilePath); // Validate that the shader is bound EXPECT_NO_THROW(shader.bind()); @@ -114,7 +114,7 @@ namespace nexo::renderer { TEST_F(ShaderTest, InvalidShaderFile) { - EXPECT_THROW(OpenGlShader("non_existing_file.glsl"), FileNotFoundException); + EXPECT_THROW(NxOpenGlShader("non_existing_file.glsl"), NxFileNotFoundException); } TEST_F(ShaderTest, InvalidShaderSource) @@ -131,7 +131,7 @@ namespace nexo::renderer { createTemporaryShaderFile(invalidShaderSource); // Validate shader compiling failure - EXPECT_THROW(OpenGlShader shader(temporaryShaderFilePath), ShaderCreationFailed); + EXPECT_THROW(NxOpenGlShader shader(temporaryShaderFilePath), NxShaderCreationFailed); deleteTemporaryShaderFile(); } @@ -184,7 +184,7 @@ namespace nexo::renderer { {"uIntArray", 3} }; - OpenGlShader shader("TestShader", vertexShaderSourceTestUniforms, fragmentShaderSourceTestUniforms); + NxOpenGlShader shader("TestShader", vertexShaderSourceTestUniforms, fragmentShaderSourceTestUniforms); shader.bind(); /////////////////////// BASE UNIFORM CHECKUP /////////////////////////// @@ -295,13 +295,13 @@ namespace nexo::renderer { TEST_F(ShaderTest, GetShaderName) { - OpenGlShader shader("TestShader", vertexShaderSource, fragmentShaderSource); + NxOpenGlShader shader("TestShader", vertexShaderSource, fragmentShaderSource); EXPECT_EQ(shader.getName(), "TestShader"); } TEST_F(ShaderTest, InvalidUniformName) { - OpenGlShader shader("TestShader", vertexShaderSource, fragmentShaderSource); + NxOpenGlShader shader("TestShader", vertexShaderSource, fragmentShaderSource); shader.bind(); // Validate failure on invalid uniform name diff --git a/tests/renderer/Texture.test.cpp b/tests/renderer/Texture.test.cpp index 4a76c57b6..dc4e113af 100644 --- a/tests/renderer/Texture.test.cpp +++ b/tests/renderer/Texture.test.cpp @@ -41,8 +41,8 @@ namespace nexo::renderer { }; TEST_F(OpenGlTexture2DTest, CreateTextureFromDimensions) { - OpenGlTexture2D texture1(256, 520); - OpenGlTexture2D texture2(520, 256); + NxOpenGlTexture2D texture1(256, 520); + NxOpenGlTexture2D texture2(520, 256); // Validate that each texture is unique EXPECT_NE(texture1.getId(), texture2.getId()); @@ -71,7 +71,7 @@ namespace nexo::renderer { TEST_F(OpenGlTexture2DTest, CreateTextureFromFile) { //TODO: make this test with a real texture file createTemporaryTextureFile(); - OpenGlTexture2D texture(temporaryTextureFilePath); + NxOpenGlTexture2D texture(temporaryTextureFilePath); // Validate dimensions std::cout << texture.getWidth() << std::endl; @@ -82,13 +82,13 @@ namespace nexo::renderer { } TEST_F(OpenGlTexture2DTest, CreateTextureFromInvalidFile) { - EXPECT_THROW(OpenGlTexture2D texture("InvalidFile");, FileNotFoundException); + EXPECT_THROW(NxOpenGlTexture2D texture("InvalidFile");, NxFileNotFoundException); } TEST_F(OpenGlTexture2DTest, SetDataValidSize) { unsigned int width = 128; unsigned int height = 128; - OpenGlTexture2D texture(width, height); + NxOpenGlTexture2D texture(width, height); // Validate setting data with correct size std::vector data(width * height * 4, 255); // RGBA white @@ -105,17 +105,17 @@ namespace nexo::renderer { TEST_F(OpenGlTexture2DTest, SetDataInvalidSize) { unsigned int width = 128; unsigned int height = 128; - OpenGlTexture2D texture(width, height); + NxOpenGlTexture2D texture(width, height); // Create invalid data (size mismatch) std::vector invalidData(width * height * 3, 255); // Missing alpha channel - EXPECT_THROW(texture.setData(invalidData.data(), invalidData.size()), TextureSizeMismatch); + EXPECT_THROW(texture.setData(invalidData.data(), invalidData.size()), NxTextureSizeMismatch); } TEST_F(OpenGlTexture2DTest, BindTextureToSlot) { unsigned int width = 64; unsigned int height = 64; - OpenGlTexture2D texture(width, height); + NxOpenGlTexture2D texture(width, height); unsigned int slot = 5; texture.bind(slot); @@ -133,8 +133,8 @@ namespace nexo::renderer { TEST_F(OpenGlTexture2DTest, TextureEqualityOperator) { unsigned int width = 64; unsigned int height = 64; - OpenGlTexture2D texture1(width, height); - OpenGlTexture2D texture2(width, height); + NxOpenGlTexture2D texture1(width, height); + NxOpenGlTexture2D texture2(width, height); // Validate equality operator EXPECT_FALSE(texture1 == texture2); // Different textures diff --git a/tests/renderer/VertexArray.test.cpp b/tests/renderer/VertexArray.test.cpp index f42e039d9..9a98ac2c3 100644 --- a/tests/renderer/VertexArray.test.cpp +++ b/tests/renderer/VertexArray.test.cpp @@ -26,8 +26,8 @@ namespace nexo::renderer { TEST_F(OpenGLTest, VertexArrayCreationAndBinding) { - auto vertexArray1 = std::make_shared(); - auto vertexArray2 = std::make_shared(); + auto vertexArray1 = std::make_shared(); + auto vertexArray2 = std::make_shared(); EXPECT_NE(vertexArray1->getId(), vertexArray2->getId()); @@ -47,18 +47,18 @@ namespace nexo::renderer { TEST_F(OpenGLTest, AddVertexBuffer) { - auto vertexArray = std::make_shared(); + auto vertexArray = std::make_shared(); float vertices[] = { 0.0f, 0.0f, 0.0f, // Position 1.0f, 1.0f, 1.0f, 1.0f, // Color 3, // Texture index }; - auto vertexBuffer = std::make_shared(vertices, sizeof(vertices)); - BufferLayout layout = { - {ShaderDataType::FLOAT3, "Position"}, - {ShaderDataType::FLOAT4, "Color", true}, - {ShaderDataType::INT, "TextureIndex"}, + auto vertexBuffer = std::make_shared(vertices, sizeof(vertices)); + NxBufferLayout layout = { + {NxShaderDataType::FLOAT3, "Position"}, + {NxShaderDataType::FLOAT4, "Color", true}, + {NxShaderDataType::INT, "TextureIndex"}, }; vertexBuffer->setLayout(layout); @@ -102,42 +102,42 @@ namespace nexo::renderer { TEST_F(OpenGLTest, InvalidVertexBuffer) { - auto vertexArray = std::make_shared(); + auto vertexArray = std::make_shared(); float vertices[] = { 0.0f, 0.0f, 0.0f, // Position 1.0f, 1.0f, 1.0f, 1.0f, // Color 3, // Texture index }; - auto vertexBuffer = std::make_shared(vertices, sizeof(vertices)); + auto vertexBuffer = std::make_shared(vertices, sizeof(vertices)); // Empty layout EXPECT_THROW( vertexArray->addVertexBuffer(vertexBuffer), - BufferLayoutEmpty + NxBufferLayoutEmpty ); // Null vertex buffer EXPECT_THROW( vertexArray->addVertexBuffer(nullptr), - InvalidValue + NxInvalidValue ); } TEST_F(OpenGLTest, MultipleVertexBuffers) { - auto vertexArray = std::make_shared(); + auto vertexArray = std::make_shared(); float positions[] = {0.0f, 1.0f, 2.0f}; - auto positionBuffer = std::make_shared(positions, sizeof(positions)); + auto positionBuffer = std::make_shared(positions, sizeof(positions)); positionBuffer->setLayout({ - {ShaderDataType::FLOAT3, "Position"} + {NxShaderDataType::FLOAT3, "Position"} }); float colors[] = {1.0f, 0.0f, 0.0f}; - auto colorBuffer = std::make_shared(colors, sizeof(colors)); + auto colorBuffer = std::make_shared(colors, sizeof(colors)); colorBuffer->setLayout({ - {ShaderDataType::FLOAT3, "Color"} + {NxShaderDataType::FLOAT3, "Color"} }); vertexArray->addVertexBuffer(positionBuffer); @@ -161,10 +161,10 @@ namespace nexo::renderer { TEST_F(OpenGLTest, SetIndexBuffer) { - auto vertexArray = std::make_shared(); + auto vertexArray = std::make_shared(); unsigned int indices[] = {0, 1, 2}; - auto indexBuffer = std::make_shared(); + auto indexBuffer = std::make_shared(); indexBuffer->setData(indices, 3); vertexArray->setIndexBuffer(indexBuffer); @@ -183,11 +183,11 @@ namespace nexo::renderer { TEST_F(OpenGLTest, InvalidIndexBuffer) { - auto vertexArray = std::make_shared(); + auto vertexArray = std::make_shared(); EXPECT_THROW( vertexArray->setIndexBuffer(nullptr), - InvalidValue + NxInvalidValue ); } } diff --git a/tests/renderer/contexts/opengl.hpp b/tests/renderer/contexts/opengl.hpp index cab3b6e9c..96baf717f 100644 --- a/tests/renderer/contexts/opengl.hpp +++ b/tests/renderer/contexts/opengl.hpp @@ -67,17 +67,17 @@ namespace nexo::renderer { } }; - class MockVertexBuffer : public VertexBuffer { + class MockVertexBuffer : public NxVertexBuffer { public: MOCK_METHOD(void, bind, (), (const, override)); MOCK_METHOD(void, unbind, (), (const, override)); - MOCK_METHOD(void, setLayout, (const BufferLayout &layout), (override)); - MOCK_METHOD(BufferLayout, getLayout, (), (const, override)); + MOCK_METHOD(void, setLayout, (const NxBufferLayout &layout), (override)); + MOCK_METHOD(NxBufferLayout, getLayout, (), (const, override)); MOCK_METHOD(void, setData, (void *data, unsigned int size), (override)); MOCK_METHOD(unsigned int, getId, (), (const override)); }; - class MockIndexBuffer : public IndexBuffer { + class MockIndexBuffer : public NxIndexBuffer { public: MOCK_METHOD(void, bind, (), (const, override)); MOCK_METHOD(void, unbind, (), (const, override)); From 42c5e1e124d8e535c6d336be3a4fe3a09a27d014 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 25 Apr 2025 07:36:58 +0900 Subject: [PATCH 256/450] refactor(asset-ecs): update Model and Textures import --- engine/src/assets/Assets/Model/ModelImporter.cpp | 4 ++-- engine/src/assets/Assets/Texture/Texture.hpp | 2 +- engine/src/assets/Assets/Texture/TextureImporter.cpp | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index 0254ff475..283c56302 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -243,13 +243,13 @@ namespace nexo::assets { components::Mesh ModelImporter::processMesh(AssetImporterContext& ctx, aiMesh* mesh, const aiScene* scene) { - std::vector vertices; + std::vector vertices; std::vector indices; vertices.reserve(mesh->mNumVertices); for (unsigned int i = 0; i < mesh->mNumVertices; i++) { - renderer::Vertex vertex{}; + renderer::NxVertex vertex{}; vertex.position = {mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z}; if (mesh->HasNormals()) { diff --git a/engine/src/assets/Assets/Texture/Texture.hpp b/engine/src/assets/Assets/Texture/Texture.hpp index 9f1b826b0..aff4a1277 100644 --- a/engine/src/assets/Assets/Texture/Texture.hpp +++ b/engine/src/assets/Assets/Texture/Texture.hpp @@ -19,7 +19,7 @@ namespace nexo::assets { struct TextureData { - std::shared_ptr texture; + std::shared_ptr texture; }; /** diff --git a/engine/src/assets/Assets/Texture/TextureImporter.cpp b/engine/src/assets/Assets/Texture/TextureImporter.cpp index 29cfcd651..fd02e6a00 100644 --- a/engine/src/assets/Assets/Texture/TextureImporter.cpp +++ b/engine/src/assets/Assets/Texture/TextureImporter.cpp @@ -36,14 +36,14 @@ namespace nexo::assets { void TextureImporter::importImpl(AssetImporterContext& ctx) { - // TODO: we need to import textures independently from graphics API back end renderer::Texture2D::create implementation + // TODO: we need to import textures independently from graphics API back end renderer::NxTexture2D::create implementation auto asset = new Texture(); - std::shared_ptr rendererTexture; + std::shared_ptr rendererTexture; if (std::holds_alternative(ctx.input)) - rendererTexture = renderer::Texture2D::create(std::get(ctx.input).filePath.string()); + rendererTexture = renderer::NxTexture2D::create(std::get(ctx.input).filePath.string()); else { auto data = std::get(ctx.input).memoryData; - rendererTexture = renderer::Texture2D::create(data.data(), data.size()); + rendererTexture = renderer::NxTexture2D::create(data.data(), data.size()); } auto assetData = new TextureData(); assetData->texture = rendererTexture; From 9010e04e7bdfa9c6a066609fbfddf32746ae9997 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 25 Apr 2025 08:41:09 +0900 Subject: [PATCH 257/450] refactor(asset-ecs): rename exception class in test --- tests/renderer/Exceptions.test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/renderer/Exceptions.test.cpp b/tests/renderer/Exceptions.test.cpp index 0922a1d9d..a0a8eeb75 100644 --- a/tests/renderer/Exceptions.test.cpp +++ b/tests/renderer/Exceptions.test.cpp @@ -129,7 +129,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - GraphicsApiNotInitialized ex("OpenGL"); + NxGraphicsApiNotInitialized ex("OpenGL"); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] API is not initialized, call the init function first"), std::string::npos); @@ -273,7 +273,7 @@ namespace nexo::renderer { constexpr const char* expectedFile = __FILE__; constexpr unsigned int expectedLine = __LINE__ + 2; - TextureSizeMismatch ex("OpenGL", 1024, 2048); + NxTextureSizeMismatch ex("OpenGL", 1024, 2048); std::string formattedMessage = ex.what(); EXPECT_NE(formattedMessage.find("[OpenGL] Data size does not match the texture size: 1024 != 2048"), std::string::npos); From 3ae61dc8cc4e5b019b1519a427eaa642e301761f Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 25 Apr 2025 14:00:21 +0900 Subject: [PATCH 258/450] refactor(asset-ecs): modify document-windows code after rebase --- editor/src/ImNexo/Components.cpp | 4 +-- editor/src/ImNexo/Components.hpp | 4 +-- editor/src/ImNexo/Panels.cpp | 36 +++++++++++-------- editor/src/utils/EditorProps.cpp | 14 ++++---- engine/src/components/Shapes3D.hpp | 2 +- engine/src/renderer/Buffer.hpp | 8 ++--- engine/src/renderer/Framebuffer.hpp | 12 +++---- engine/src/renderer/Renderer3D.cpp | 10 +++--- engine/src/renderer/Renderer3D.hpp | 2 +- engine/src/renderer/ShaderLibrary.cpp | 18 +++++----- engine/src/renderer/ShaderLibrary.hpp | 14 ++++---- .../src/renderer/opengl/OpenGlRendererApi.cpp | 10 +++--- engine/src/renderer/primitives/Billboard.cpp | 21 ++++++----- engine/src/systems/RenderSystem.cpp | 28 +++++++-------- engine/src/systems/RenderSystem.hpp | 6 ++-- vcpkg | 2 +- 16 files changed, 103 insertions(+), 88 deletions(-) diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp index c1c227426..4184ecb54 100644 --- a/editor/src/ImNexo/Components.cpp +++ b/editor/src/ImNexo/Components.cpp @@ -99,7 +99,7 @@ namespace ImNexo { } - bool TextureButton(const std::string &label, std::shared_ptr &texture) + bool TextureButton(const std::string &label, std::shared_ptr &texture) { bool textureModified = false; constexpr ImVec2 previewSize(32, 32); @@ -122,7 +122,7 @@ namespace ImNexo { if (filePath) { const std::string path(filePath); - std::shared_ptr newTexture = nexo::renderer::Texture2D::create(path); + std::shared_ptr newTexture = nexo::renderer::NxTexture2D::create(path); if (newTexture) { texture = newTexture; diff --git a/editor/src/ImNexo/Components.hpp b/editor/src/ImNexo/Components.hpp index 544cbb8b0..518dc3af8 100644 --- a/editor/src/ImNexo/Components.hpp +++ b/editor/src/ImNexo/Components.hpp @@ -58,10 +58,10 @@ namespace ImNexo { * the passed texture pointer is updated and the function returns true. * * @param label A unique label identifier for the button. - * @param texture A shared pointer to the renderer::Texture2D that holds the texture. + * @param texture A shared pointer to the renderer::NxTexture2D that holds the texture. * @return true if the texture was modified; false otherwise. */ - bool TextureButton(const std::string &label, std::shared_ptr &texture); + bool TextureButton(const std::string &label, std::shared_ptr &texture); /** * @brief Creates a customizable gradient button with a centered icon. diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index 338ccbe6d..c00c27256 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -61,19 +61,27 @@ namespace ImNexo { } // --- Albedo texture --- - static ImGuiColorEditFlags colorPickerModeAlbedo = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPickerAlbedo = false; - modified = TextureButton("Albedo texture", material->albedoTexture) || modified; - ImGui::SameLine(); - modified = ColorEditor("##ColorEditor Albedo texture", &material->albedoColor, &colorPickerModeAlbedo, &showColorPickerAlbedo) || modified; + { + static ImGuiColorEditFlags colorPickerModeAlbedo = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPickerAlbedo = false; + const auto albedoTextureAsset = material->albedoTexture.lock(); + auto albedoTexture = albedoTextureAsset ? albedoTextureAsset->data->texture : nullptr; + modified = TextureButton("Albedo texture", albedoTexture) || modified; + ImGui::SameLine(); + modified = ColorEditor("##ColorEditor Albedo texture", &material->albedoColor, &colorPickerModeAlbedo, &showColorPickerAlbedo) || modified; + } // --- Specular texture --- - static ImGuiColorEditFlags colorPickerModeSpecular = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPickerSpecular = false; - modified = TextureButton("Specular texture", material->metallicMap) || modified; - ImGui::SameLine(); - modified = ColorEditor("##ColorEditor Specular texture", &material->specularColor, &colorPickerModeSpecular, &showColorPickerSpecular) || modified; - return modified; + { + static ImGuiColorEditFlags colorPickerModeSpecular = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPickerSpecular = false; + const auto metallicTextureAsset = material->metallicMap.lock(); + auto metallicTexture = metallicTextureAsset ? metallicTextureAsset->data->texture : nullptr; + modified = TextureButton("Specular texture", metallicTexture) || modified; + ImGui::SameLine(); + modified = ColorEditor("##ColorEditor Specular texture", &material->specularColor, &colorPickerModeSpecular, &showColorPickerSpecular) || modified; + return modified; + } } /** @@ -89,9 +97,9 @@ namespace ImNexo { static nexo::ecs::Entity createDefaultPerspectiveCamera(const nexo::scene::SceneId sceneId, ImVec2 sceneViewportSize) { auto &app = nexo::getApp(); - nexo::renderer::FramebufferSpecs framebufferSpecs; + nexo::renderer::NxFramebufferSpecs framebufferSpecs; framebufferSpecs.attachments = { - nexo::renderer::FrameBufferTextureFormats::RGBA8, nexo::renderer::FrameBufferTextureFormats::RED_INTEGER, nexo::renderer::FrameBufferTextureFormats::Depth + nexo::renderer::NxFrameBufferTextureFormats::RGBA8, nexo::renderer::NxFrameBufferTextureFormats::RED_INTEGER, nexo::renderer::NxFrameBufferTextureFormats::Depth }; const ImVec2 availSize = ImGui::GetContentRegionAvail(); const float totalWidth = availSize.x; @@ -102,7 +110,7 @@ namespace ImNexo { const float previewWidth = totalWidth - inspectorWidth - 8; // Subtract spacing between panel framebufferSpecs.width = static_cast(sceneViewportSize.x); framebufferSpecs.height = static_cast(sceneViewportSize.y); - const auto renderTarget = nexo::renderer::Framebuffer::create(framebufferSpecs); + const auto renderTarget = nexo::renderer::NxFramebuffer::create(framebufferSpecs); 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(static_cast(defaultCamera)); nexo::editor::utils::addPropsTo(defaultCamera, nexo::editor::utils::PropsType::CAMERA); diff --git a/editor/src/utils/EditorProps.cpp b/editor/src/utils/EditorProps.cpp index e2f367bcf..db54e20c4 100644 --- a/editor/src/utils/EditorProps.cpp +++ b/editor/src/utils/EditorProps.cpp @@ -18,6 +18,8 @@ #include "components/Shapes3D.hpp" #include "Path.hpp" #include "Nexo.hpp" +#include "assets/AssetCatalog.hpp" + namespace nexo::editor::utils { static void addCameraProps(ecs::Entity entity) @@ -25,8 +27,8 @@ namespace nexo::editor::utils { auto &app = getApp(); nexo::components::Material billboardMat{}; billboardMat.isOpaque = false; - static const std::shared_ptr cameraIconTexture = - nexo::renderer::Texture2D::create(nexo::Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png").string()); + static const std::shared_ptr cameraIconTexture = + nexo::renderer::NxTexture2D::create(nexo::Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png").string()); billboardMat.albedoTexture = cameraIconTexture; billboardMat.shader = "Albedo unshaded transparent"; auto billboard = std::make_shared(); @@ -40,8 +42,8 @@ namespace nexo::editor::utils { auto &app = getApp(); nexo::components::Material billboardMat{}; billboardMat.isOpaque = false; - static const std::shared_ptr pointLightIconTexture = - renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/pointLightIcon.png").string()); + static const std::shared_ptr pointLightIconTexture = + renderer::NxTexture2D::create(Path::resolvePathRelativeToExe("../resources/textures/pointLightIcon.png").string()); billboardMat.albedoTexture = pointLightIconTexture; billboardMat.shader = "Albedo unshaded transparent"; auto billboard = std::make_shared(); @@ -55,8 +57,8 @@ namespace nexo::editor::utils { auto &app = getApp(); nexo::components::Material billboardMat{}; billboardMat.isOpaque = false; - static const std::shared_ptr spotLightIconTexture = - renderer::Texture2D::create(Path::resolvePathRelativeToExe("../resources/textures/spotLightIcon.png").string()); + static const std::shared_ptr spotLightIconTexture = + renderer::NxTexture2D::create(Path::resolvePathRelativeToExe("../resources/textures/spotLightIcon.png").string()); billboardMat.albedoTexture = spotLightIconTexture; billboardMat.shader = "Albedo unshaded transparent"; auto billboard = std::make_shared(); diff --git a/engine/src/components/Shapes3D.hpp b/engine/src/components/Shapes3D.hpp index e0d889079..acf1271d3 100644 --- a/engine/src/components/Shapes3D.hpp +++ b/engine/src/components/Shapes3D.hpp @@ -145,7 +145,7 @@ namespace nexo::components { }; struct BillBoard final : Shape3D { - void draw(std::shared_ptr &context, const TransformComponent &transf, const Material &material, const int entityID) override + void draw(std::shared_ptr &context, const TransformComponent &transf, const Material &material, const int entityID) override { const auto renderer3D = context->renderer3D; renderer3D.drawBillboard(transf.pos, transf.size, material, entityID); diff --git a/engine/src/renderer/Buffer.hpp b/engine/src/renderer/Buffer.hpp index 2e88c245a..2d9bc6f1d 100644 --- a/engine/src/renderer/Buffer.hpp +++ b/engine/src/renderer/Buffer.hpp @@ -139,7 +139,7 @@ namespace nexo::renderer { * @class NxBufferLayout * @brief Defines the structure and layout of elements in a vertex buffer. * - * A BufferLayout is a collection of BufferElements, each specifying a data type, + * A NxBufferLayout is a collection of BufferElements, each specifying a data type, * size, and offset. The layout is essential for ensuring correct binding and * rendering of vertex attributes in a graphics pipeline. * @@ -148,7 +148,7 @@ namespace nexo::renderer { * - @param _stride The total size (in bytes) of one vertex in the layout. * * Functions: - * - @constructor BufferLayout(const std::initializer_list elements) + * - @constructor NxBufferLayout(const std::initializer_list elements) * Initializes the layout with a list of buffer elements and calculates offsets/stride. * * - @return getElements() Retrieves the list of BufferElements. @@ -241,7 +241,7 @@ namespace nexo::renderer { * 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 BufferLayout object defining the structure of the buffer data. + * @param layout The NxBufferLayout object defining the structure of the buffer data. * * Pure Virtual Function: * - Must be implemented by platform-specific subclasses. @@ -254,7 +254,7 @@ namespace nexo::renderer { * Provides information about the data structure stored in the buffer, including * element types, sizes, and offsets. * - * @return The BufferLayout object associated with this vertex buffer. + * @return The NxBufferLayout object associated with this vertex buffer. * * Pure Virtual Function: * - Must be implemented by platform-specific subclasses. diff --git a/engine/src/renderer/Framebuffer.hpp b/engine/src/renderer/Framebuffer.hpp index c8722b620..d6deb6e00 100644 --- a/engine/src/renderer/Framebuffer.hpp +++ b/engine/src/renderer/Framebuffer.hpp @@ -94,7 +94,7 @@ namespace nexo::renderer { * @struct NxFramebufferSpecs * @brief Represents the specifications for creating a framebuffer. * - * FramebufferSpecs encapsulates all the necessary properties for initializing a framebuffer, + * NxFramebufferSpecs encapsulates all the necessary properties for initializing a framebuffer, * including dimensions, attachments, sampling, and swap chain behavior. * * Members: @@ -120,7 +120,7 @@ namespace nexo::renderer { * * A framebuffer is an off-screen rendering target that stores the results of * rendering operations. It can have multiple texture attachments, such as color, - * depth, and stencil buffers. The `Framebuffer` class provides an abstraction layer + * depth, and stencil buffers. The `NxFramebuffer` class provides an abstraction layer * for creating and managing framebuffers across different graphics APIs (e.g., OpenGL, Vulkan). * * Key Features: @@ -131,7 +131,7 @@ namespace nexo::renderer { * - Resizable: The framebuffer can be resized dynamically to match the viewport dimensions. * * Usage: - * - The `Framebuffer` class is an abstract base class. Platform or API-specific + * - The `NxFramebuffer` class is an abstract base class. Platform or API-specific * implementations (e.g., NxOpenGLFramebuffer) must inherit and implement the * pure virtual methods. * @@ -242,7 +242,7 @@ namespace nexo::renderer { * This method provides access to the framebuffer's specifications, including * dimensions, attachments, and sampling options. * - * @return A reference to the FramebufferSpecs struct. + * @return A reference to the NxFramebufferSpecs struct. */ [[nodiscard]] virtual NxFramebufferSpecs &getSpecs() = 0; @@ -251,7 +251,7 @@ namespace nexo::renderer { * * This method provides read-only access to the framebuffer's specifications. * - * @return A constant reference to the FramebufferSpecs struct. + * @return A constant reference to the NxFramebufferSpecs struct. */ [[nodiscard]] virtual const NxFramebufferSpecs &getSpecs() const = 0; /** @@ -279,7 +279,7 @@ namespace nexo::renderer { * * @param specs The specifications for creating the framebuffer, including dimensions, * attachments, and sampling options. - * @return A shared pointer to the created Framebuffer instance. + * @return A shared pointer to the created NxFramebuffer instance. * * Throws: * - Implementation-specific exceptions if framebuffer creation fails. diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index f5ae4b5fc..107847fe5 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -71,18 +71,18 @@ namespace nexo::renderer { m_storage->shaderLibrary.load("Flat color", Path::resolvePathRelativeToExe( "../resources/shaders/flat_color.glsl").string()); phong->bind(); - phong->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), Renderer3DStorage::maxTextureSlots); + phong->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); phong->unbind(); outlinePulseTransparentFlat->bind(); - outlinePulseTransparentFlat->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), Renderer3DStorage::maxTextureSlots); + outlinePulseTransparentFlat->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); outlinePulseTransparentFlat->unbind(); albedoUnshadedTransparent->bind(); - albedoUnshadedTransparent->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), Renderer3DStorage::maxTextureSlots); + albedoUnshadedTransparent->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); albedoUnshadedTransparent->unbind(); m_storage->textureSlots[0] = m_storage->whiteTexture; - LOG(NEXO_DEV, "Renderer3D initialized"); + LOG(NEXO_DEV, "NxRenderer3D initialized"); } void NxRenderer3D::shutdown() @@ -162,7 +162,7 @@ namespace nexo::renderer { int NxRenderer3D::getTextureIndex(const std::shared_ptr &texture) const { - int textureIndex = 0.0f; + int textureIndex = 0; if (!texture) return textureIndex; diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index ef90f671e..4189cabcb 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -65,7 +65,7 @@ namespace nexo::renderer { std::shared_ptr roughnessMap = nullptr; std::shared_ptr emissiveMap = nullptr; - std::optional> shader = std::nullopt; + std::string shader = ""; }; //TODO: Add stats for the meshes diff --git a/engine/src/renderer/ShaderLibrary.cpp b/engine/src/renderer/ShaderLibrary.cpp index d40b6abb5..5b3394da8 100644 --- a/engine/src/renderer/ShaderLibrary.cpp +++ b/engine/src/renderer/ShaderLibrary.cpp @@ -16,39 +16,39 @@ #include "Logger.hpp" namespace nexo::renderer { - void ShaderLibrary::add(const std::shared_ptr &shader) + void ShaderLibrary::add(const std::shared_ptr &shader) { const std::string &name = shader->getName(); m_shaders[name] = shader; } - void ShaderLibrary::add(const std::string &name, const std::shared_ptr &shader) + void ShaderLibrary::add(const std::string &name, const std::shared_ptr &shader) { m_shaders[name] = shader; } - std::shared_ptr ShaderLibrary::load(const std::string &name, const std::string &path) + std::shared_ptr ShaderLibrary::load(const std::string &name, const std::string &path) { - auto shader = Shader::create(path); + auto shader = NxShader::create(path); add(name, shader); return shader; } - std::shared_ptr ShaderLibrary::load(const std::string &path) + std::shared_ptr ShaderLibrary::load(const std::string &path) { - auto shader = Shader::create(path); + auto shader = NxShader::create(path); add(shader); 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 = Shader::create(name, vertexSource, fragmentSource); + auto shader = NxShader::create(name, vertexSource, fragmentSource); add(shader); return shader; } - std::shared_ptr ShaderLibrary::get(const std::string &name) const + std::shared_ptr ShaderLibrary::get(const std::string &name) const { if (!m_shaders.contains(name)) { diff --git a/engine/src/renderer/ShaderLibrary.hpp b/engine/src/renderer/ShaderLibrary.hpp index 93dea3b86..6e4d41256 100644 --- a/engine/src/renderer/ShaderLibrary.hpp +++ b/engine/src/renderer/ShaderLibrary.hpp @@ -19,13 +19,13 @@ namespace nexo::renderer { class ShaderLibrary { 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; + 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; private: - std::unordered_map> m_shaders; + std::unordered_map> m_shaders; }; } diff --git a/engine/src/renderer/opengl/OpenGlRendererApi.cpp b/engine/src/renderer/opengl/OpenGlRendererApi.cpp index b974be426..20bfb9036 100644 --- a/engine/src/renderer/opengl/OpenGlRendererApi.cpp +++ b/engine/src/renderer/opengl/OpenGlRendererApi.cpp @@ -84,7 +84,7 @@ namespace nexo::renderer { void NxOpenGlRendererApi::setDepthTest(bool enable) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (enable) glEnable(GL_DEPTH_TEST); else @@ -94,14 +94,14 @@ namespace nexo::renderer { void NxOpenGlRendererApi::setDepthFunc(unsigned int func) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glDepthFunc(func); } void NxOpenGlRendererApi::setDepthMask(bool enable) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); if (enable) glDepthMask(GL_TRUE); else @@ -149,10 +149,10 @@ namespace nexo::renderer { glStencilFunc(func, ref, mask); } - void OpenGlRendererApi::setStencilOp(unsigned int sfail, unsigned int dpfail, unsigned int dppass) + void NxOpenGlRendererApi::setStencilOp(unsigned int sfail, unsigned int dpfail, unsigned int dppass) { if (!m_initialized) - THROW_EXCEPTION(GraphicsApiNotInitialized, "OPENGL"); + THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glStencilOp(sfail, dpfail, dppass); } } diff --git a/engine/src/renderer/primitives/Billboard.cpp b/engine/src/renderer/primitives/Billboard.cpp index 75c372730..638f0bd57 100644 --- a/engine/src/renderer/primitives/Billboard.cpp +++ b/engine/src/renderer/primitives/Billboard.cpp @@ -119,7 +119,7 @@ namespace nexo::renderer { } } - void Renderer3D::drawBillboard( + void NxRenderer3D::drawBillboard( const glm::vec3& position, const glm::vec2& size, const glm::vec4& color, @@ -127,7 +127,7 @@ namespace nexo::renderer { { if (!m_renderingScene) { - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); } @@ -141,7 +141,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::Material mat; + renderer::NxIndexedMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -168,7 +168,7 @@ namespace nexo::renderer { }); } - void Renderer3D::drawBillboard( + void NxRenderer3D::drawBillboard( const glm::vec3& position, const glm::vec2& size, const components::Material& material, @@ -176,7 +176,7 @@ namespace nexo::renderer { { if (!m_renderingScene) { - THROW_EXCEPTION(RendererSceneLifeCycleFailure, RendererType::RENDERER_3D, + THROW_EXCEPTION(NxRendererSceneLifeCycleFailure, NxRendererType::RENDERER_3D, "Renderer not rendering a scene, make sure to call beginScene first"); } @@ -190,11 +190,16 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::Material mat; + auto albedoTextureAsset = material.albedoTexture.lock(); + auto metallicTextureAsset = material.metallicMap.lock(); + auto albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->data->texture : nullptr; + auto metallicTexture = metallicTextureAsset && metallicTextureAsset->isLoaded() ? metallicTextureAsset->data->texture : nullptr; + + renderer::NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; - mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; + mat.albedoTexIndex = getTextureIndex(albedoTexture); mat.specularColor = material.specularColor; - mat.specularTexIndex = material.metallicMap ? getTextureIndex(material.metallicMap) : 0; + mat.specularTexIndex = getTextureIndex(metallicTexture); setMaterialUniforms(mat); std::array verts{}; diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index c01275f3b..d980e0979 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -60,9 +60,9 @@ namespace nexo::system { auto quadVB = renderer::createVertexBuffer(sizeof(quadVertices)); quadVB->setData(quadVertices, sizeof(quadVertices)); - renderer::BufferLayout quadLayout = { - {renderer::ShaderDataType::FLOAT3, "aPosition"}, - {renderer::ShaderDataType::FLOAT2, "aTexCoord"} + renderer::NxBufferLayout quadLayout = { + {renderer::NxShaderDataType::FLOAT3, "aPosition"}, + {renderer::NxShaderDataType::FLOAT2, "aTexCoord"} }; quadVB->setLayout(quadLayout); @@ -84,9 +84,9 @@ namespace nexo::system { * - pointLights (and pointLightCount) * - spotLights (and spotLightCount) */ - void RenderSystem::setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext) + void RenderSystem::setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext) { - static std::shared_ptr lastShader = nullptr; + static std::shared_ptr lastShader = nullptr; if (lastShader == shader) return; lastShader = shader; @@ -216,11 +216,11 @@ namespace nexo::system { gridShader->setUniformFloat("uTime", static_cast(glfwGetTime())); // Render the grid - renderer::RenderCommand::setDepthMask(false); + renderer::NxRenderCommand::setDepthMask(false); glDisable(GL_CULL_FACE); - renderer::RenderCommand::drawUnIndexed(6); + renderer::NxRenderCommand::drawUnIndexed(6); gridShader->unbind(); - renderer::RenderCommand::setDepthMask(true); + renderer::NxRenderCommand::setDepthMask(true); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); } @@ -241,8 +241,8 @@ namespace nexo::system { // Step 2: Render selected object to mask texture m_maskFramebuffer->bind(); - renderer::RenderCommand::setClearColor({0.0f, 0.0f, 0.0f, 0.0f}); - renderer::RenderCommand::clear(); + renderer::NxRenderCommand::setClearColor({0.0f, 0.0f, 0.0f, 0.0f}); + renderer::NxRenderCommand::clear(); const auto &material = std::dynamic_pointer_cast(renderComponent.renderable)->material; @@ -251,7 +251,7 @@ namespace nexo::system { maskShaderName = "Albedo unshaded transparent"; renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, maskShaderName); - auto context = std::make_shared(); + auto context = std::make_shared(); context->renderer3D = renderContext.renderer3D; renderComponent.draw(context, transformComponent); renderContext.renderer3D.endScene(); @@ -262,7 +262,7 @@ namespace nexo::system { camera.renderTarget->bind(); // Step 3: Draw full-screen quad with outline post-process shader - renderer::RenderCommand::setDepthMask(false); + renderer::NxRenderCommand::setDepthMask(false); renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, "Outline pulse flat"); auto outlineShader = renderContext.renderer3D.getShader(); outlineShader->bind(); @@ -275,12 +275,12 @@ namespace nexo::system { outlineShader->setUniformFloat("uOutlineWidth", 10.0f); m_fullscreenQuad->bind(); - renderer::RenderCommand::drawUnIndexed(6); + renderer::NxRenderCommand::drawUnIndexed(6); m_fullscreenQuad->unbind(); renderContext.renderer3D.endScene(); outlineShader->unbind(); - renderer::RenderCommand::setDepthMask(true); + renderer::NxRenderCommand::setDepthMask(true); } void RenderSystem::update() diff --git a/engine/src/systems/RenderSystem.hpp b/engine/src/systems/RenderSystem.hpp index 42497c11e..26c843d9c 100644 --- a/engine/src/systems/RenderSystem.hpp +++ b/engine/src/systems/RenderSystem.hpp @@ -52,7 +52,7 @@ namespace nexo::system { void update(); private: - void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext); + void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext); void renderGrid(const components::CameraContext &camera, components::RenderContext &renderContext); void renderOutline( components::RenderContext &renderContext, @@ -60,7 +60,7 @@ namespace nexo::system { const components::RenderComponent &renderComponent, const components::TransformComponent &transformComponent ); - std::shared_ptr m_fullscreenQuad; - std::shared_ptr m_maskFramebuffer; + std::shared_ptr m_fullscreenQuad; + std::shared_ptr m_maskFramebuffer; }; } diff --git a/vcpkg b/vcpkg index cd124b84f..96d5fb3de 160000 --- a/vcpkg +++ b/vcpkg @@ -1 +1 @@ -Subproject commit cd124b84feb0c02a24a2d90981e8358fdee0e077 +Subproject commit 96d5fb3de135b86d7222c53f2352ca92827a156b From b1ac5964215adfccdf3ef84470be088d1ef026f0 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 25 Apr 2025 15:52:49 +0900 Subject: [PATCH 259/450] feat(asset-ecs): finish migrating to new AssetRef after rebase --- editor/src/utils/EditorProps.cpp | 27 +++++++++++++------- engine/src/assets/Asset.hpp | 1 + engine/src/assets/AssetCatalog.hpp | 23 +++++++++++++---- engine/src/assets/Assets/Texture/Texture.hpp | 14 ++++++++++ engine/src/components/Shapes3D.hpp | 26 ++++++++++++++++++- engine/src/renderer/Renderer3D.hpp | 2 +- engine/src/renderer/primitives/Billboard.cpp | 11 +++----- 7 files changed, 80 insertions(+), 24 deletions(-) diff --git a/editor/src/utils/EditorProps.cpp b/editor/src/utils/EditorProps.cpp index db54e20c4..8bf3832c7 100644 --- a/editor/src/utils/EditorProps.cpp +++ b/editor/src/utils/EditorProps.cpp @@ -24,11 +24,14 @@ namespace nexo::editor::utils { static void addCameraProps(ecs::Entity entity) { - auto &app = getApp(); + auto& app = getApp(); + auto& catalog = assets::AssetCatalog::getInstance(); nexo::components::Material billboardMat{}; billboardMat.isOpaque = false; - static const std::shared_ptr cameraIconTexture = - nexo::renderer::NxTexture2D::create(nexo::Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png").string()); + static const assets::AssetRef cameraIconTexture = catalog.createAsset( + assets::AssetLocation("_internal::cameraIcon@_internal"), + Path::resolvePathRelativeToExe("../resources/textures/cameraIcon.png") + ); billboardMat.albedoTexture = cameraIconTexture; billboardMat.shader = "Albedo unshaded transparent"; auto billboard = std::make_shared(); @@ -39,11 +42,14 @@ namespace nexo::editor::utils { static void addPointLightProps(ecs::Entity entity) { - auto &app = getApp(); + auto& app = getApp(); + auto& catalog = assets::AssetCatalog::getInstance(); nexo::components::Material billboardMat{}; billboardMat.isOpaque = false; - static const std::shared_ptr pointLightIconTexture = - renderer::NxTexture2D::create(Path::resolvePathRelativeToExe("../resources/textures/pointLightIcon.png").string()); + static const assets::AssetRef pointLightIconTexture = catalog.createAsset( + assets::AssetLocation("_internal::pointLightIcon@_internal"), + Path::resolvePathRelativeToExe("../resources/textures/pointLightIcon.png") + ); billboardMat.albedoTexture = pointLightIconTexture; billboardMat.shader = "Albedo unshaded transparent"; auto billboard = std::make_shared(); @@ -54,11 +60,14 @@ namespace nexo::editor::utils { static void addSpotLightProps(ecs::Entity entity) { - auto &app = getApp(); + auto& app = getApp(); + auto& catalog = assets::AssetCatalog::getInstance(); nexo::components::Material billboardMat{}; billboardMat.isOpaque = false; - static const std::shared_ptr spotLightIconTexture = - renderer::NxTexture2D::create(Path::resolvePathRelativeToExe("../resources/textures/spotLightIcon.png").string()); + static const assets::AssetRef spotLightIconTexture = catalog.createAsset( + assets::AssetLocation("_internal::spotLightIcon@_internal"), + Path::resolvePathRelativeToExe("../resources/textures/spotLightIcon.png") + ); billboardMat.albedoTexture = spotLightIconTexture; billboardMat.shader = "Albedo unshaded transparent"; auto billboard = std::make_shared(); diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index 1224a0120..0a3bc66c0 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -197,6 +197,7 @@ namespace nexo::assets { friend class AssetRef; public: + using AssetDataType = TAssetData; static constexpr AssetType TYPE = TAssetType; /** diff --git a/engine/src/assets/AssetCatalog.hpp b/engine/src/assets/AssetCatalog.hpp index 6f2f73c48..996b2d45c 100644 --- a/engine/src/assets/AssetCatalog.hpp +++ b/engine/src/assets/AssetCatalog.hpp @@ -14,6 +14,8 @@ #pragma once +#include + #include "AssetImporter.hpp" #include "AssetLocation.hpp" #include "Asset.hpp" @@ -21,6 +23,8 @@ #include #include +#include "Assets/Texture/Texture.hpp" + namespace nexo::assets { /** @@ -133,7 +137,7 @@ namespace nexo::assets { */ template requires std::derived_from - [[nodiscard]] std::ranges::view auto getAssetsOfTypeView() const; + [[nodiscard]] decltype(auto) getAssetsOfTypeView() const; /** * @brief Registers an asset in the catalog. @@ -158,15 +162,24 @@ namespace nexo::assets { * * @tparam AssetType The type of asset to create. Must be derived from IAsset. * @param location The asset's location metadata. - * @param data Optional data to load the asset. Default is nullptr. + * @param args Constructor arguments for the asset. * @return AssetRef A reference to the created and registered asset. */ + template + requires std::derived_from + AssetRef createAsset(const AssetLocation& location, Args&& ...args) + { + auto asset = new AssetType(std::forward(args)...); + auto assetRef = registerAsset(location, asset); + return assetRef.template as(); + } + template requires std::derived_from - AssetRef createAsset(const AssetLocation& location, void *data = nullptr) + AssetRef createAsset(const AssetLocation& location, typename AssetType::AssetDataType* assetData) { auto asset = new AssetType(); - asset->setRawData(data); + asset->setData(assetData); auto assetRef = registerAsset(location, asset); return assetRef.template as(); } @@ -190,7 +203,7 @@ namespace nexo::assets { } template requires std::derived_from - std::ranges::view auto AssetCatalog::getAssetsOfTypeView() const + decltype(auto) AssetCatalog::getAssetsOfTypeView() const { // TODO: AssetType::TYPE is not a thing, need to find a way to get the type of the asset static_assert(true, "Filtering not implemented yet"); diff --git a/engine/src/assets/Assets/Texture/Texture.hpp b/engine/src/assets/Assets/Texture/Texture.hpp index aff4a1277..2ba5cd886 100644 --- a/engine/src/assets/Assets/Texture/Texture.hpp +++ b/engine/src/assets/Assets/Texture/Texture.hpp @@ -31,6 +31,20 @@ namespace nexo::assets { public: Texture() = default; + /** + * @brief Constructs a Texture object from a file path. + * + * @param path The path to the texture file. + */ + explicit Texture(const std::filesystem::path &path) + : Asset() + { + auto texture = renderer::NxTexture2D::create(path.string()); + auto textureData = new TextureData(); + textureData->texture = texture; + setData(textureData); + } + ~Texture() override = default; }; diff --git a/engine/src/components/Shapes3D.hpp b/engine/src/components/Shapes3D.hpp index acf1271d3..216ab576b 100644 --- a/engine/src/components/Shapes3D.hpp +++ b/engine/src/components/Shapes3D.hpp @@ -148,7 +148,31 @@ namespace nexo::components { void draw(std::shared_ptr &context, const TransformComponent &transf, const Material &material, const int entityID) override { const auto renderer3D = context->renderer3D; - renderer3D.drawBillboard(transf.pos, transf.size, material, entityID); + + // lock all textures + auto albedoTextureAsset = material.albedoTexture.lock(); + auto normalMapAsset = material.normalMap.lock(); + auto metallicMapAsset = material.metallicMap.lock(); + auto roughnessMapAsset = material.roughnessMap.lock(); + auto emissiveMapAsset = material.emissiveMap.lock(); + + + renderer::NxMaterial inputMaterial = { + .albedoColor = material.albedoColor, + .specularColor = material.specularColor, + .emissiveColor = material.emissiveColor, + .roughness = material.roughness, + .metallic = material.metallic, + .opacity = material.opacity, + .albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->data->texture : nullptr, + .normalMap = normalMapAsset && normalMapAsset->isLoaded() ? normalMapAsset->data->texture : nullptr, + .metallicMap = metallicMapAsset && metallicMapAsset->isLoaded() ? metallicMapAsset->data->texture : nullptr, + .roughnessMap = roughnessMapAsset && roughnessMapAsset->isLoaded() ? roughnessMapAsset->data->texture : nullptr, + .emissiveMap = emissiveMapAsset && emissiveMapAsset->isLoaded() ? emissiveMapAsset->data->texture : nullptr, + .shader = material.shader + }; + + renderer3D.drawBillboard(transf.pos, transf.size, inputMaterial, entityID); } [[nodiscard]] std::shared_ptr clone() const override diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index 4189cabcb..c7579d925 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -330,7 +330,7 @@ namespace nexo::renderer { void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::mat4& transform, const renderer::NxMaterial& material, int entityID = -1) const; void drawBillboard(const glm::vec3& position, const glm::vec2& size, const glm::vec4& color, int entityID) const; - void drawBillboard(const glm::vec3& position, const glm::vec2& size, const components::Material& material, int entityID) const; + void drawBillboard(const glm::vec3& position, const glm::vec2& size, const NxMaterial& material, int entityID) const; /** * @brief Resets rendering statistics. diff --git a/engine/src/renderer/primitives/Billboard.cpp b/engine/src/renderer/primitives/Billboard.cpp index 638f0bd57..04fcb2ed2 100644 --- a/engine/src/renderer/primitives/Billboard.cpp +++ b/engine/src/renderer/primitives/Billboard.cpp @@ -171,7 +171,7 @@ namespace nexo::renderer { void NxRenderer3D::drawBillboard( const glm::vec3& position, const glm::vec2& size, - const components::Material& material, + const NxMaterial& material, int entityID) const { if (!m_renderingScene) @@ -190,16 +190,11 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - auto albedoTextureAsset = material.albedoTexture.lock(); - auto metallicTextureAsset = material.metallicMap.lock(); - auto albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->data->texture : nullptr; - auto metallicTexture = metallicTextureAsset && metallicTextureAsset->isLoaded() ? metallicTextureAsset->data->texture : nullptr; - renderer::NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; - mat.albedoTexIndex = getTextureIndex(albedoTexture); + mat.albedoTexIndex = getTextureIndex(material.albedoTexture); mat.specularColor = material.specularColor; - mat.specularTexIndex = getTextureIndex(metallicTexture); + mat.specularTexIndex = getTextureIndex(material.metallicMap); setMaterialUniforms(mat); std::array verts{}; From 74f965f3af8ba147758a4cacec6cc46b01dfa4a1 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 25 Apr 2025 16:36:49 +0900 Subject: [PATCH 260/450] fix(asset-ecs): invalid image format on load --- engine/src/renderer/Renderer3D.cpp | 6 +-- engine/src/renderer/Shader.hpp | 40 +++++++++---------- engine/src/renderer/opengl/OpenGlShader.cpp | 12 +++--- engine/src/renderer/opengl/OpenGlShader.hpp | 12 +++--- .../src/renderer/opengl/OpenGlTexture2D.cpp | 8 +--- 5 files changed, 36 insertions(+), 42 deletions(-) diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index 107847fe5..cd14ebf13 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -71,13 +71,13 @@ namespace nexo::renderer { m_storage->shaderLibrary.load("Flat color", Path::resolvePathRelativeToExe( "../resources/shaders/flat_color.glsl").string()); phong->bind(); - phong->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); + phong->setUniformIntArray(NxShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); phong->unbind(); outlinePulseTransparentFlat->bind(); - outlinePulseTransparentFlat->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); + outlinePulseTransparentFlat->setUniformIntArray(NxShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); outlinePulseTransparentFlat->unbind(); albedoUnshadedTransparent->bind(); - albedoUnshadedTransparent->setUniformIntArray(ShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); + albedoUnshadedTransparent->setUniformIntArray(NxShaderUniforms::TEXTURE_SAMPLER, samplers.data(), NxRenderer3DStorage::maxTextureSlots); albedoUnshadedTransparent->unbind(); m_storage->textureSlots[0] = m_storage->whiteTexture; diff --git a/engine/src/renderer/Shader.hpp b/engine/src/renderer/Shader.hpp index 42e04c9ff..bf2272a82 100644 --- a/engine/src/renderer/Shader.hpp +++ b/engine/src/renderer/Shader.hpp @@ -23,7 +23,7 @@ namespace nexo::renderer { - enum class ShaderUniforms { + enum class NxShaderUniforms { VIEW_PROJECTION, MODEL_MATRIX, CAMERA_POSITION, @@ -40,18 +40,18 @@ namespace nexo::renderer { MATERIAL }; - inline const std::unordered_map ShaderUniformsName = { - {ShaderUniforms::VIEW_PROJECTION, "uViewProjection"}, - {ShaderUniforms::MODEL_MATRIX, "uMatModel"}, - {ShaderUniforms::CAMERA_POSITION, "uCamPos"}, - {ShaderUniforms::TEXTURE_SAMPLER, "uTexture"}, - {ShaderUniforms::DIR_LIGHT, "uDirLight"}, - {ShaderUniforms::AMBIENT_LIGHT, "uAmbientLight"}, - {ShaderUniforms::POINT_LIGHT_ARRAY, "uPointLights"}, - {ShaderUniforms::NB_POINT_LIGHT, "uNbPointLights"}, - {ShaderUniforms::SPOT_LIGHT_ARRAY, "uSpotLights"}, - {ShaderUniforms::NB_SPOT_LIGHT, "uNbSpotLights"}, - {ShaderUniforms::MATERIAL, "uMaterial"} + inline const std::unordered_map ShaderUniformsName = { + {NxShaderUniforms::VIEW_PROJECTION, "uViewProjection"}, + {NxShaderUniforms::MODEL_MATRIX, "uMatModel"}, + {NxShaderUniforms::CAMERA_POSITION, "uCamPos"}, + {NxShaderUniforms::TEXTURE_SAMPLER, "uTexture"}, + {NxShaderUniforms::DIR_LIGHT, "uDirLight"}, + {NxShaderUniforms::AMBIENT_LIGHT, "uAmbientLight"}, + {NxShaderUniforms::POINT_LIGHT_ARRAY, "uPointLights"}, + {NxShaderUniforms::NB_POINT_LIGHT, "uNbPointLights"}, + {NxShaderUniforms::SPOT_LIGHT_ARRAY, "uSpotLights"}, + {NxShaderUniforms::NB_SPOT_LIGHT, "uNbSpotLights"}, + {NxShaderUniforms::MATERIAL, "uMaterial"} }; /** @@ -138,12 +138,12 @@ namespace nexo::renderer { virtual bool setUniformInt(const std::string &name, int value) const = 0; virtual bool setUniformIntArray(const std::string &name, const int *values, unsigned int count) const = 0; - virtual bool setUniformFloat(const ShaderUniforms uniform, const float value) const = 0; - virtual bool setUniformFloat3(const ShaderUniforms uniform, const glm::vec3 &values) const = 0; - virtual bool setUniformFloat4(const ShaderUniforms uniform, const glm::vec4 &values) const = 0; - virtual bool setUniformMatrix(const ShaderUniforms uniform, const glm::mat4 &matrix) const = 0; - virtual bool setUniformInt(const ShaderUniforms uniform, int value) const = 0; - virtual bool setUniformIntArray(const ShaderUniforms uniform, const int *values, unsigned int count) const = 0; + virtual bool setUniformFloat(const NxShaderUniforms uniform, const float value) const = 0; + virtual bool setUniformFloat3(const NxShaderUniforms uniform, const glm::vec3 &values) const = 0; + virtual bool setUniformFloat4(const NxShaderUniforms uniform, const glm::vec4 &values) const = 0; + virtual bool setUniformMatrix(const NxShaderUniforms uniform, const glm::mat4 &matrix) const = 0; + virtual bool setUniformInt(const NxShaderUniforms uniform, int value) const = 0; + virtual bool setUniformIntArray(const NxShaderUniforms uniform, const int *values, unsigned int count) const = 0; void addStorageBuffer(const std::shared_ptr &buffer); void setStorageBufferData(unsigned int index, void *data, unsigned int size); @@ -156,7 +156,7 @@ namespace nexo::renderer { protected: static std::string readFile(const std::string &filepath); std::vector> m_storageBuffers; - std::unordered_map m_uniformLocations; + std::unordered_map m_uniformLocations; }; diff --git a/engine/src/renderer/opengl/OpenGlShader.cpp b/engine/src/renderer/opengl/OpenGlShader.cpp index 8c00a1e0b..4d66fb686 100644 --- a/engine/src/renderer/opengl/OpenGlShader.cpp +++ b/engine/src/renderer/opengl/OpenGlShader.cpp @@ -210,7 +210,7 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformFloat(const ShaderUniforms uniform, const float value) const + bool NxOpenGlShader::setUniformFloat(const NxShaderUniforms uniform, const float value) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -240,7 +240,7 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformFloat3(const ShaderUniforms uniform, const glm::vec3 &values) const + bool NxOpenGlShader::setUniformFloat3(const NxShaderUniforms uniform, const glm::vec3 &values) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -260,7 +260,7 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformFloat4(const ShaderUniforms uniform, const glm::vec4 &values) const + bool NxOpenGlShader::setUniformFloat4(const NxShaderUniforms uniform, const glm::vec4 &values) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -280,7 +280,7 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformMatrix(const ShaderUniforms uniform, const glm::mat4 &matrix) const + bool NxOpenGlShader::setUniformMatrix(const NxShaderUniforms uniform, const glm::mat4 &matrix) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -310,7 +310,7 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformInt(const ShaderUniforms uniform, const int value) const + bool NxOpenGlShader::setUniformInt(const NxShaderUniforms uniform, const int value) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) @@ -330,7 +330,7 @@ namespace nexo::renderer { return true; } - bool NxOpenGlShader::setUniformIntArray(const ShaderUniforms uniform, const int *values, const unsigned int count) const + bool NxOpenGlShader::setUniformIntArray(const NxShaderUniforms uniform, const int *values, const unsigned int count) const { const int loc = m_uniformLocations.at(uniform); if (loc == -1) diff --git a/engine/src/renderer/opengl/OpenGlShader.hpp b/engine/src/renderer/opengl/OpenGlShader.hpp index 39f17e81b..9996a6e11 100644 --- a/engine/src/renderer/opengl/OpenGlShader.hpp +++ b/engine/src/renderer/opengl/OpenGlShader.hpp @@ -66,12 +66,12 @@ namespace nexo::renderer { 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(const ShaderUniforms uniform, const float value) const override; - bool setUniformFloat3(const ShaderUniforms uniform, const glm::vec3 &values) const override; - bool setUniformFloat4(const ShaderUniforms uniform, const glm::vec4 &values) const override; - bool setUniformMatrix(const ShaderUniforms uniform, const glm::mat4 &matrix) const override; - bool setUniformInt(const ShaderUniforms uniform, int value) const override; - bool setUniformIntArray(const ShaderUniforms uniform, const int *values, unsigned int count) const override; + bool setUniformFloat(const NxShaderUniforms uniform, const float value) const override; + bool setUniformFloat3(const NxShaderUniforms uniform, const glm::vec3 &values) const override; + bool setUniformFloat4(const NxShaderUniforms uniform, const glm::vec4 &values) const override; + bool setUniformMatrix(const NxShaderUniforms uniform, const glm::mat4 &matrix) const override; + bool setUniformInt(const NxShaderUniforms uniform, int value) const override; + bool setUniformIntArray(const 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; diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index de1e86cfa..c3b4ecc6b 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -94,9 +94,6 @@ namespace nexo::renderer { m_width = width; m_height = height; - GLenum internalFormat = 0; - GLenum dataFormat = 0; - switch (channels) { [[likely]] case 4: m_internalFormat = GL_RGBA8; @@ -119,12 +116,9 @@ namespace nexo::renderer { THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", channels, debugPath); } - m_internalFormat = internalFormat; - m_dataFormat = dataFormat; - glGenTextures(1, &m_id); glBindTexture(GL_TEXTURE_2D, m_id); - glTexImage2D(GL_TEXTURE_2D, 0, static_cast(internalFormat), width, height, 0, dataFormat, GL_UNSIGNED_BYTE, data); + glTexImage2D(GL_TEXTURE_2D, 0, static_cast(m_internalFormat), width, height, 0, m_dataFormat, GL_UNSIGNED_BYTE, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); From a84746720110a360f6aec077a7d3e581660b68b8 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 25 Apr 2025 16:51:51 +0900 Subject: [PATCH 261/450] refactor(asset-ecs): remove unused Render3D component include, better split renderer and rest of the engine --- engine/src/renderer/Renderer3D.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index c7579d925..30a051fd2 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -17,7 +17,6 @@ #include "VertexArray.hpp" #include "Texture.hpp" #include "ShaderLibrary.hpp" -#include "components/Render3D.hpp" #include #include From 3fb158318f468433b3c3bc7ff985445d1ce7d086 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sat, 26 Apr 2025 19:48:00 +0900 Subject: [PATCH 262/450] refactor(asset-ecs): add new constructors to Texture asset, improve Texture2D creation on backend --- common/String.hpp | 38 ++++++ editor/src/ImNexo/Panels.cpp | 2 +- .../src/assets/Assets/Model/ModelImporter.cpp | 117 ++++++++++++++---- .../src/assets/Assets/Model/ModelImporter.hpp | 6 +- engine/src/assets/Assets/Texture/Texture.hpp | 89 +++++++++++++ .../assets/Assets/Texture/TextureImporter.cpp | 2 - engine/src/renderer/Texture.cpp | 20 +++ engine/src/renderer/Texture.hpp | 74 ++++++++++- .../src/renderer/opengl/OpenGlTexture2D.cpp | 115 +++++++++++------ .../src/renderer/opengl/OpenGlTexture2D.hpp | 58 ++++++++- engine/src/systems/CameraSystem.cpp | 1 + 11 files changed, 458 insertions(+), 64 deletions(-) create mode 100644 common/String.hpp diff --git a/common/String.hpp b/common/String.hpp new file mode 100644 index 000000000..810010dab --- /dev/null +++ b/common/String.hpp @@ -0,0 +1,38 @@ +//// String.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: Guillaume HEIN +// Date: 23/11/2024 +// Description: Utils for strings +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +namespace nexo { + + /** + * @brief Compare two strings case-insensitively. + * + * @param a The first string. + * @param b The second string. + * @return true if the strings are equal (case-insensitive), false otherwise. + */ + [[nodiscard]] constexpr bool iequals(const std::string_view& a, const std::string_view& b) { + return a.size() == b.size() && + std::equal(a.begin(), a.end(), b.begin(), [](const char _a, const char _b) { + return std::tolower(static_cast(_a)) == + std::tolower(static_cast(_b)); + }); + } + + +} // namespace nexo diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index c00c27256..f9b5c4dc0 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -80,8 +80,8 @@ namespace ImNexo { modified = TextureButton("Specular texture", metallicTexture) || modified; ImGui::SameLine(); modified = ColorEditor("##ColorEditor Specular texture", &material->specularColor, &colorPickerModeSpecular, &showColorPickerSpecular) || modified; - return modified; } + return modified; } /** diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index 283c56302..a2d4c5a8a 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -75,8 +75,8 @@ namespace nexo::assets { throw core::LoadModelException(ctx.location.getFullLocation(), m_importer.GetErrorString()); } - loadEmbeddedTextures(ctx, scene); - loadMaterials(ctx, scene); + loadSceneEmbeddedTextures(ctx, scene); + loadSceneMaterials(ctx, scene); auto meshNode = processNode(ctx, scene->mRootNode, scene); if (!meshNode) { @@ -87,38 +87,113 @@ namespace nexo::assets { return model; } - void ModelImporter::loadEmbeddedTextures(AssetImporterContext& ctx, const aiScene* scene) + void ModelImporter::loadSceneEmbeddedTextures(AssetImporterContext& ctx, const aiScene* scene) { m_textures.reserve(scene->mNumTextures); // Load embedded textures for (int i = 0; scene->mNumTextures; ++i) { aiTexture *texture = scene->mTextures[i]; + auto loadedTexture = loadEmbeddedTexture(ctx, texture); + m_textures.emplace(texture->mFilename.C_Str(), loadedTexture); + } + + } + + AssetRef ModelImporter::loadEmbeddedTexture(AssetImporterContext& ctx, aiTexture* texture) + { + + if (texture->mHeight == 0) { // Compressed texture AssetImporter assetImporter; - ImporterInputVariant inputVariant; - if (texture->mHeight == 0) { // Compressed texture - 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) - }; - } else { // Uncompressed texture - // TODO: implement RGBA8888 format - /*inputVariant = ImporterMemoryInput{ - .memoryData = std::vector(texture->pcData, texture->pcData + (texture->mWidth * texture->mHeight * 4)), - .formatHint = std::string(texture->achFormatHint) - };*/ - return; - } + 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) + }; - auto assetTexture = assetImporter.importAsset( + return assetImporter.importAsset( ctx.genUniqueDependencyLocation(), inputVariant); - m_textures.emplace(texture->mFilename.C_Str(), assetTexture); + } else { // Uncompressed texture + auto& catalog = AssetCatalog::getInstance(); + + renderer::NxTextureFormat 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); + return nullptr; + } + + return catalog.createAsset(ctx.genUniqueDependencyLocation(), + reinterpret_cast(texture->pcData), texture->mWidth, texture->mHeight, format); + } + + + } + + renderer::NxTextureFormat ModelImporter::convertAssimpHintToNxTextureFormat(const char achFormatHint[9]) + { + // Split into channels (first 4 chars) and bit depths (next 4 chars) + std::string_view channels(achFormatHint, 4); + std::string_view bits_str(achFormatHint + 4, 4); + + // Parse active channels and their bit depths + struct ChannelInfo { char code; int bits; }; + std::vector active_channels; + + for (int i = 0; i < 4; ++i) { + const char ch = static_cast(std::tolower(channels[i])); + if (not (ch == 'r' || ch == 'g' || ch == 'b')) { + return renderer::NxTextureFormat::INVALID; + } + if (not std::isdigit(bits_str[i])) { + return renderer::NxTextureFormat::INVALID; + } + const int bits = bits_str[i] - '0'; + + if (ch != '\0' && bits > 0) { + active_channels.push_back({ch, bits}); + } + } + + // Check all active channels have exactly 8 bits + for (const auto& ci : active_channels) { + if (ci.bits != 8) return renderer::NxTextureFormat::INVALID; + } + + // Match channel patterns + switch (active_channels.size()) { + case 1: + if (active_channels[0].code == 'r') + return renderer::NxTextureFormat::R8; + break; + + case 2: + 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') + 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') + return renderer::NxTextureFormat::RGBA8; + break; + default: + break; } + return renderer::NxTextureFormat::INVALID; } - void ModelImporter::loadMaterials(AssetImporterContext& ctx, const aiScene* scene) + void ModelImporter::loadSceneMaterials(AssetImporterContext& ctx, const aiScene* scene) { m_materials.assign(scene->mNumMaterials, nullptr); diff --git a/engine/src/assets/Assets/Model/ModelImporter.hpp b/engine/src/assets/Assets/Model/ModelImporter.hpp index 31ebcd21d..649bdabb2 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.hpp +++ b/engine/src/assets/Assets/Model/ModelImporter.hpp @@ -35,8 +35,10 @@ namespace nexo::assets { protected: Model* loadModel(AssetImporterContext& ctx); - void loadEmbeddedTextures(AssetImporterContext& ctx, const aiScene* scene); - void loadMaterials(AssetImporterContext& ctx, const aiScene* scene); + void loadSceneEmbeddedTextures(AssetImporterContext& ctx, const aiScene* scene); + AssetRef loadEmbeddedTexture(AssetImporterContext& ctx, aiTexture *texture); + renderer::NxTextureFormat convertAssimpHintToNxTextureFormat(const char achFormatHint[9]); + void loadSceneMaterials(AssetImporterContext& ctx, const aiScene* scene); std::shared_ptr processNode(AssetImporterContext& ctx, aiNode const *node, const aiScene* scene); components::Mesh processMesh(AssetImporterContext& ctx, aiMesh* mesh, const aiScene* scene); diff --git a/engine/src/assets/Assets/Texture/Texture.hpp b/engine/src/assets/Assets/Texture/Texture.hpp index 2ba5cd886..bfe1ad8b3 100644 --- a/engine/src/assets/Assets/Texture/Texture.hpp +++ b/engine/src/assets/Assets/Texture/Texture.hpp @@ -29,12 +29,27 @@ namespace nexo::assets { */ 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; /** * @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() @@ -45,6 +60,80 @@ namespace nexo::assets { setData(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(unsigned int width, unsigned int height) + : Asset() + { + auto texture = renderer::NxTexture2D::create(width, height); + auto textureData = new TextureData(); + textureData->texture = texture; + setData(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 top-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(uint8_t* buffer, unsigned int width, unsigned int height, renderer::NxTextureFormat format) + : Asset() + { + auto texture = renderer::NxTexture2D::create(buffer, width, height, format); + auto textureData = new TextureData(); + textureData->texture = texture; + setData(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(uint8_t* buffer, unsigned int size) + : Asset() + { + auto texture = renderer::NxTexture2D::create(buffer, size); + auto textureData = new TextureData(); + textureData->texture = texture; + setData(textureData); + } + ~Texture() override = default; }; diff --git a/engine/src/assets/Assets/Texture/TextureImporter.cpp b/engine/src/assets/Assets/Texture/TextureImporter.cpp index fd02e6a00..074d57a7a 100644 --- a/engine/src/assets/Assets/Texture/TextureImporter.cpp +++ b/engine/src/assets/Assets/Texture/TextureImporter.cpp @@ -12,8 +12,6 @@ // /////////////////////////////////////////////////////////////////////////////// -#pragma once - #include "TextureImporter.hpp" #include diff --git a/engine/src/renderer/Texture.cpp b/engine/src/renderer/Texture.cpp index 740b588fe..69ede3924 100644 --- a/engine/src/renderer/Texture.cpp +++ b/engine/src/renderer/Texture.cpp @@ -15,12 +15,23 @@ #include "Texture.hpp" #include "Renderer.hpp" #include "renderer/RendererExceptions.hpp" +#include "String.hpp" + #ifdef NX_GRAPHICS_API_OPENGL #include "opengl/OpenGlTexture2D.hpp" #endif namespace nexo::renderer { + 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, "RGBA8")) return NxTextureFormat::RGBA8; + return NxTextureFormat::INVALID; + } + std::shared_ptr NxTexture2D::create(unsigned int width, unsigned int height) { #ifdef NX_GRAPHICS_API_OPENGL @@ -29,6 +40,15 @@ namespace nexo::renderer { THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } + std::shared_ptr NxTexture2D::create(uint8_t* buffer, unsigned int width, unsigned int height, + NxTextureFormat format) + { + #ifdef NX_GRAPHICS_API_OPENGL + return std::make_shared(buffer, width, height, format); + #endif + THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); + } + std::shared_ptr NxTexture2D::create(uint8_t* buffer, unsigned int len) { #ifdef NX_GRAPHICS_API_OPENGL diff --git a/engine/src/renderer/Texture.hpp b/engine/src/renderer/Texture.hpp index 209417ac5..ab1c2a78d 100644 --- a/engine/src/renderer/Texture.hpp +++ b/engine/src/renderer/Texture.hpp @@ -13,8 +13,11 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include +#include #include +#include + +#include "String.hpp" namespace nexo::renderer { @@ -52,6 +55,45 @@ namespace nexo::renderer { bool operator==(const NxTexture &other) const { return this->getId() == other.getId(); }; }; + /** + * @enum NxTextureFormat + * @brief Enumeration of texture formats. + * + * This enum defines various texture formats that can be used in the rendering system. + * Each format corresponds to a specific pixel layout and color depth. + * + * For example: + * - `R8` represents a single-channel texture with 8 bits per channel. + * - `RG8` represents a two-channel texture with 8 bits per channel. + * - `RGB8` represents a three-channel texture with 8 bits per channel. + * - `RGBA8` represents a four-channel texture with 8 bits per channel. + * + * @note If a texture format is invalid, it is represented by `INVALID`, which value is 0. + */ + 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 + + _NB_FORMATS_ // Number of texture formats, used for array sizing + }; + + constexpr const std::string& 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"; + } + } + + NxTextureFormat NxTextureFormatFromString(const std::string_view &format); + class NxTexture2D : public NxTexture { public: /** @@ -71,6 +113,36 @@ namespace nexo::renderer { */ 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 top-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(uint8_t *buffer, unsigned int width, unsigned int height, NxTextureFormat format); + /** * @brief Creates a 2D texture from file in memory. * diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index c3b4ecc6b..5af4a7c9d 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -24,20 +24,40 @@ namespace nexo::renderer { NxOpenGlTexture2D::NxOpenGlTexture2D(const unsigned int width, const unsigned int height) : m_width(width), m_height(height) { - const unsigned int maxTextureSize = getMaxTextureSize(); - if (width > maxTextureSize || height > maxTextureSize) - THROW_EXCEPTION(NxTextureInvalidSize, "OPENGL", width, height, maxTextureSize); - m_internalFormat = GL_RGBA8; - m_dataFormat = GL_RGBA; + createOpenGLTexture(nullptr, width, height, GL_RGBA8, GL_RGBA); + } - glGenTextures(1, &m_id); - glBindTexture(GL_TEXTURE_2D, m_id); - glTexImage2D(GL_TEXTURE_2D, 0, static_cast(m_internalFormat), static_cast(width), static_cast(height), 0, m_dataFormat, GL_UNSIGNED_BYTE, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glBindTexture(GL_TEXTURE_2D, 0); + NxOpenGlTexture2D::NxOpenGlTexture2D(const uint8_t* buffer, const unsigned int width, const unsigned int height, + const NxTextureFormat format) : m_width(width), m_height(height) + { + if (!buffer) + THROW_EXCEPTION(NxInvalidValue, "OPENGL", "Buffer is null"); + + GLint internalFormat = 0; + GLenum dataFormat = 0; + switch (format) { + [[likely]] case NxTextureFormat::RGBA8: + internalFormat = GL_RGBA8; + dataFormat = GL_RGBA; + break; + [[likely]] case NxTextureFormat::RGB8: + internalFormat = GL_RGB8; + dataFormat = GL_RGB; + break; + case NxTextureFormat::RG8: + internalFormat = GL_RG8; + dataFormat = GL_RG; + break; + case NxTextureFormat::R8: + internalFormat = GL_R8; + dataFormat = GL_RED; + break; + + default: + THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", static_cast(format), ""); + } + + createOpenGLTexture(buffer, width, height, internalFormat, dataFormat); } NxOpenGlTexture2D::NxOpenGlTexture2D(const std::string &path) @@ -50,7 +70,14 @@ namespace nexo::renderer { stbi_uc *data = stbi_load(path.c_str(), &width, &height, &channels, 0); if (!data) THROW_EXCEPTION(NxFileNotFoundException, path); - ingestDataFromStb(data, width, height, channels, path); + + try { + ingestDataFromStb(data, width, height, channels, path); + } catch (const Exception& e) { + stbi_image_free(data); + throw; + } + stbi_image_free(data); } NxOpenGlTexture2D::~NxOpenGlTexture2D() @@ -68,7 +95,14 @@ namespace nexo::renderer { stbi_uc *data = stbi_load_from_memory(buffer, len, &width, &height, &channels, 0); if (!data) THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", channels, "(buffer)"); - ingestDataFromStb(data, width, height, channels, "(buffer)"); + + try { + ingestDataFromStb(data, width, height, channels, "(buffer)"); + } catch (const Exception& e) { + stbi_image_free(data); + throw; + } + stbi_image_free(data); } unsigned int NxOpenGlTexture2D::getMaxTextureSize() const @@ -91,44 +125,55 @@ namespace nexo::renderer { void NxOpenGlTexture2D::ingestDataFromStb(uint8_t* data, int width, int height, int channels, const std::string& debugPath) { - m_width = width; - m_height = height; - + GLint internalFormat = 0; + GLenum dataFormat = 0; switch (channels) { - [[likely]] case 4: - m_internalFormat = GL_RGBA8; - m_dataFormat = GL_RGBA; + [[likely]] case 4: // red, green, blue, alpha + internalFormat = GL_RGBA8; + dataFormat = GL_RGBA; break; - [[likely]] case 3: - m_internalFormat = GL_RGB8; - m_dataFormat = GL_RGB; + [[likely]] case 3: // red, green, blue + internalFormat = GL_RGB8; + dataFormat = GL_RGB; break; - case 2: - m_internalFormat = GL_RG8; - m_dataFormat = GL_RG; + case 2: // grey, alpha + internalFormat = GL_RG8; + dataFormat = GL_RG; break; - case 1: - m_internalFormat = GL_R8; - m_dataFormat = GL_RED; + case 1: // grey + internalFormat = GL_R8; + dataFormat = GL_RED; break; default: - stbi_image_free(data); THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", channels, debugPath); } + 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) + { + 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 glHeight = static_cast(height); + + m_internalFormat = internalFormat; + m_dataFormat = dataFormat; + glGenTextures(1, &m_id); glBindTexture(GL_TEXTURE_2D, m_id); - glTexImage2D(GL_TEXTURE_2D, 0, static_cast(m_internalFormat), width, height, 0, m_dataFormat, GL_UNSIGNED_BYTE, data); + glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, glWidth, glHeight, 0, m_dataFormat, GL_UNSIGNED_BYTE, buffer); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glBindTexture(GL_TEXTURE_2D, 0); - - stbi_image_free(data); } - void NxOpenGlTexture2D::bind(const unsigned int slot) const { glActiveTexture(GL_TEXTURE0 + slot); diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.hpp b/engine/src/renderer/opengl/OpenGlTexture2D.hpp index e957850c2..59dbbe1d9 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.hpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.hpp @@ -69,7 +69,45 @@ namespace nexo::renderer { NxOpenGlTexture2D(unsigned int width, unsigned int height); /** - * @brief Creates a OpenGL 2D texture from file in memory. + * @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 top-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 @@ -168,11 +206,27 @@ namespace nexo::renderer { */ void ingestDataFromStb(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{}; - GLenum m_internalFormat; + GLint m_internalFormat; GLenum m_dataFormat; }; } diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index a7bd9086c..e092f657d 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -25,6 +25,7 @@ #include namespace nexo::system { + void CameraContextSystem::update() { auto &renderContext = getSingleton(); From 3b82ed392787f160c3fdf2c2ff83bfb3f38e755e Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sat, 26 Apr 2025 23:35:56 +0900 Subject: [PATCH 263/450] feat(asset-ecs): add ModelImporter tests and improve texture format conversion --- .../src/assets/Assets/Model/ModelImporter.cpp | 9 +- .../src/assets/Assets/Model/ModelImporter.hpp | 4 +- tests/engine/CMakeLists.txt | 1 + .../Assets/Model/ModelImporter.test.cpp | 183 ++++++++++++++++++ .../assets/Assets/Model/checkerboard.png | Bin 0 -> 1823 bytes tests/engine/assets/Assets/Model/cube.mtl | 15 ++ tests/engine/assets/Assets/Model/cube.obj | 40 ++++ 7 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 tests/engine/assets/Assets/Model/ModelImporter.test.cpp create mode 100644 tests/engine/assets/Assets/Model/checkerboard.png create mode 100644 tests/engine/assets/Assets/Model/cube.mtl create mode 100644 tests/engine/assets/Assets/Model/cube.obj diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index a2d4c5a8a..c917ea54a 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -131,6 +131,10 @@ namespace nexo::assets { renderer::NxTextureFormat ModelImporter::convertAssimpHintToNxTextureFormat(const char achFormatHint[9]) { + if (std::strlen(achFormatHint) != 8) { + return renderer::NxTextureFormat::INVALID; + } + // Split into channels (first 4 chars) and bit depths (next 4 chars) std::string_view channels(achFormatHint, 4); std::string_view bits_str(achFormatHint + 4, 4); @@ -141,7 +145,7 @@ namespace nexo::assets { for (int i = 0; i < 4; ++i) { const char ch = static_cast(std::tolower(channels[i])); - if (not (ch == 'r' || ch == 'g' || ch == 'b')) { + if (not (ch == 'r' || ch == 'g' || ch == 'b' || ch == 'a')) { return renderer::NxTextureFormat::INVALID; } if (not std::isdigit(bits_str[i])) { @@ -284,10 +288,11 @@ namespace nexo::assets { materialComponent->metallicMap ? "Yes" : "No", materialComponent->roughnessMap ? "Yes" : "No"); - AssetCatalog::getInstance().createAsset( + auto materialRef = AssetCatalog::getInstance().createAsset( ctx.genUniqueDependencyLocation(), materialComponent ); + m_materials[matIdx] = materialRef; } // end for (int matIdx = 0; matIdx < scene->mNumMaterials; ++matIdx) } diff --git a/engine/src/assets/Assets/Model/ModelImporter.hpp b/engine/src/assets/Assets/Model/ModelImporter.hpp index 649bdabb2..37d79ce35 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.hpp +++ b/engine/src/assets/Assets/Model/ModelImporter.hpp @@ -24,7 +24,7 @@ namespace nexo::assets { - class ModelImporter final : public AssetImporterBase { + class ModelImporter : public AssetImporterBase { public: ModelImporter() = default; ~ModelImporter() override = default; @@ -37,12 +37,12 @@ namespace nexo::assets { Model* loadModel(AssetImporterContext& ctx); void loadSceneEmbeddedTextures(AssetImporterContext& ctx, const aiScene* scene); AssetRef loadEmbeddedTexture(AssetImporterContext& ctx, aiTexture *texture); - renderer::NxTextureFormat convertAssimpHintToNxTextureFormat(const char achFormatHint[9]); void loadSceneMaterials(AssetImporterContext& ctx, const aiScene* scene); std::shared_ptr processNode(AssetImporterContext& ctx, aiNode const *node, const aiScene* scene); components::Mesh processMesh(AssetImporterContext& ctx, aiMesh* mesh, const aiScene* scene); + static renderer::NxTextureFormat convertAssimpHintToNxTextureFormat(const char achFormatHint[9]); static glm::mat4 convertAssimpMatrixToGLM(const aiMatrix4x4& matrix); Model *m_model = nullptr; //< Model being imported diff --git a/tests/engine/CMakeLists.txt b/tests/engine/CMakeLists.txt index 5b9bac150..d94e01f9b 100644 --- a/tests/engine/CMakeLists.txt +++ b/tests/engine/CMakeLists.txt @@ -37,6 +37,7 @@ add_executable(engine_tests ${BASEDIR}/assets/AssetRef.test.cpp ${BASEDIR}/assets/AssetImporterContext.test.cpp ${BASEDIR}/assets/AssetImporter.test.cpp + ${BASEDIR}/assets/Assets/Model/ModelImporter.test.cpp # Add other engine test files here ) diff --git a/tests/engine/assets/Assets/Model/ModelImporter.test.cpp b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp new file mode 100644 index 000000000..c80938b65 --- /dev/null +++ b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp @@ -0,0 +1,183 @@ +//// ModelImporter.test.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: Guillaume HEIN +// Date: 24/04/2025 +// Description: Test file for the ModelImporter class +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "assets/Assets/Model/ModelImporter.hpp" +#include "assets/AssetImporterContext.hpp" +#include "assets/AssetRef.hpp" +#include "assets/Assets/Model/Model.hpp" +#include "assets/Assets/Model/ModelParameters.hpp" + +#include +#include +#include +#include +#include + +#include "Path.hpp" + +using namespace testing; +using namespace nexo::assets; + +class TestModelImporter : public ModelImporter { + FRIEND_TEST(ModelImporterTestFixture, CanReadSupportsValidExtensions); + FRIEND_TEST(ModelImporterTestFixture, ConvertAssimpMatrixToGLM); + FRIEND_TEST(ModelImporterTestFixture, ConvertAssimpHintToNxTextureFormat); + FRIEND_TEST(ModelImporterTestFixture, ImportImplSetsMainAsset); + FRIEND_TEST(ModelImporterTestFixture, ProcessMeshHandlesEmptyMesh); + +}; + +// Test fixture for ModelImporter tests +class ModelImporterTestFixture : public Test { +protected: + void SetUp() override { + // Clean up the catalog before each test + auto& catalog = AssetCatalog::getInstance(); + for (auto& asset : catalog.getAssets()) { + catalog.deleteAsset(asset); + } + } + + void TearDown() override + { + // Clean up the catalog after each test + auto& catalog = AssetCatalog::getInstance(); + for (auto& asset : catalog.getAssets()) { + catalog.deleteAsset(asset); + } + } + + TestModelImporter importer; +}; + +// Test canRead method +TEST_F(ModelImporterTestFixture, CanReadSupportsValidExtensions) { + // Test with valid extension + ImporterFileInput fileInput{std::filesystem::path("model.fbx")}; + ImporterInputVariant inputVariant = fileInput; + EXPECT_TRUE(importer.canRead(inputVariant)); + + // Test with another valid extension + fileInput = ImporterFileInput{std::filesystem::path("model.obj")}; + inputVariant = fileInput; + EXPECT_TRUE(importer.canRead(inputVariant)); + + // Test with invalid extension + fileInput = ImporterFileInput{std::filesystem::path("model.invalid")}; + inputVariant = fileInput; + EXPECT_FALSE(importer.canRead(inputVariant)); +} + +// Test convertAssimpMatrixToGLM method +TEST_F(ModelImporterTestFixture, ConvertAssimpMatrixToGLM) { + // Create a test aiMatrix4x4 + aiMatrix4x4 aiMat; + aiMat.a1 = 1.0f; aiMat.a2 = 2.0f; aiMat.a3 = 3.0f; aiMat.a4 = 4.0f; + aiMat.b1 = 5.0f; aiMat.b2 = 6.0f; aiMat.b3 = 7.0f; aiMat.b4 = 8.0f; + aiMat.c1 = 9.0f; aiMat.c2 = 10.0f; aiMat.c3 = 11.0f; aiMat.c4 = 12.0f; + aiMat.d1 = 13.0f; aiMat.d2 = 14.0f; aiMat.d3 = 15.0f; aiMat.d4 = 16.0f; + + // Use the static method to convert the matrix + glm::mat4 glmMat = TestModelImporter::convertAssimpMatrixToGLM(aiMat); + + // Check if the conversion is correct + EXPECT_FLOAT_EQ(glmMat[0][0], 1.0f); + EXPECT_FLOAT_EQ(glmMat[0][1], 5.0f); + EXPECT_FLOAT_EQ(glmMat[0][2], 9.0f); + EXPECT_FLOAT_EQ(glmMat[0][3], 13.0f); + + EXPECT_FLOAT_EQ(glmMat[1][0], 2.0f); + EXPECT_FLOAT_EQ(glmMat[1][1], 6.0f); + EXPECT_FLOAT_EQ(glmMat[1][2], 10.0f); + EXPECT_FLOAT_EQ(glmMat[1][3], 14.0f); + + EXPECT_FLOAT_EQ(glmMat[2][0], 3.0f); + EXPECT_FLOAT_EQ(glmMat[2][1], 7.0f); + EXPECT_FLOAT_EQ(glmMat[2][2], 11.0f); + EXPECT_FLOAT_EQ(glmMat[2][3], 15.0f); + + EXPECT_FLOAT_EQ(glmMat[3][0], 4.0f); + EXPECT_FLOAT_EQ(glmMat[3][1], 8.0f); + EXPECT_FLOAT_EQ(glmMat[3][2], 12.0f); + EXPECT_FLOAT_EQ(glmMat[3][3], 16.0f); +} + +// Test convertAssimpHintToNxTextureFormat method +TEST_F(ModelImporterTestFixture, ConvertAssimpHintToNxTextureFormat) { + // Test various format hints + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat("rgba8888"), nexo::renderer::NxTextureFormat::RGBA8); + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat("rgba8880"), nexo::renderer::NxTextureFormat::RGB8); + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat("rgba8800"), nexo::renderer::NxTextureFormat::RG8); + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat("rgba8000"), nexo::renderer::NxTextureFormat::R8); + + // Tests invalid format because of length + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat("rgba88888"), nexo::renderer::NxTextureFormat::INVALID); + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat("rgba888"), nexo::renderer::NxTextureFormat::INVALID); + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat("rgba88"), nexo::renderer::NxTextureFormat::INVALID); + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat(""), nexo::renderer::NxTextureFormat::INVALID); + + // Test invalid format hints + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat("invalid0"), nexo::renderer::NxTextureFormat::INVALID); + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat("rgba7777"), nexo::renderer::NxTextureFormat::INVALID); + + // Test all permutations of rgba + std::string hint = "abgr0000"; + do { + hint.replace(hint.begin() + 4, hint.begin() + 8, "0000"); + GTEST_LOG_(INFO) << "HINT: " << hint; + + const auto rPos = std::ranges::find(hint, 'r'); + const auto gPos = std::ranges::find(hint, 'g'); + const auto bPos = std::ranges::find(hint, 'b'); + const auto aPos = std::ranges::find(hint, 'a'); + + hint[rPos - hint.begin() + 4] = '8'; + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat(hint.c_str()), nexo::renderer::NxTextureFormat::R8); + + if (rPos >= gPos) + continue; + hint[gPos - hint.begin() + 4] = '8'; + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat(hint.c_str()), nexo::renderer::NxTextureFormat::RG8); + + if (gPos >= bPos) + continue; + hint[bPos - hint.begin() + 4] = '8'; + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat(hint.c_str()), nexo::renderer::NxTextureFormat::RGB8); + + if (bPos >= aPos) + continue; + hint[aPos - hint.begin() + 4] = '8'; + EXPECT_EQ(importer.convertAssimpHintToNxTextureFormat(hint.c_str()), nexo::renderer::NxTextureFormat::RGBA8); + GTEST_LOG_(INFO) << "HINT: " << hint; + } while (std::next_permutation(hint.begin(), hint.begin() + 4)); +} + +// Additional test for processing empty mesh +TEST_F(ModelImporterTestFixture, ImportCubeModel) { + AssetImporterContext ctx = {}; + ctx.location = AssetLocation("test::cube_model@test_folder"); + ctx.input = ImporterFileInput{std::filesystem::path(nexo::Path::resolvePathRelativeToExe("../tests/engine/assets/Assets/Model/cube.obj"))}; + importer.import(ctx); + + /* + EXPECT_NE(ctx.getMainAsset(), nullptr); + EXPECT_EQ(ctx.getMainAsset()->getType(), Model::TYPE); + EXPECT_EQ(ctx.getMainAsset()->isLoaded(), true); + */ + + +} diff --git a/tests/engine/assets/Assets/Model/checkerboard.png b/tests/engine/assets/Assets/Model/checkerboard.png new file mode 100644 index 0000000000000000000000000000000000000000..a384354d47ac0589dafdb12b4fd591b0a7754690 GIT binary patch literal 1823 zcmbVNO^n+_6gDis+ZI$1qT)1ig;WHN$9C-4R!!L^%Wf*NU2P+Efdie)j1y~PkFlMf zU2!NJkjkA4;sAHV6$y|ih!Z!&0WL-2!VSRzA#vkP;<#C~TUJDJ-q`ca_kQM^H@CL8 zHqNX*v#Kb{nf7LD2kcAodE|chfA-X`x4=A_ZoZl;%Bd&ibD#3ThvyXKpmd>Gy^hP;nGnyoH=Jy=R+Rzq{snlRM>PN3Xu=QG1S%cAqP;}PN(v5WGr!9xyXz4 zF{DuzFjD|3g?`&K~?e(T(c#fl)HDeZTr{lMi zydVkX?N*>deQHs}eBN+PZaJam+1S>IS+_J7GopEv^15w0ooM$Twp*u;W11#*T&et-nEcHshi%?3&(byiW;q&0Xr>>ZP{er)_A8S`u@gOp?hIug(FKn*hyBa)`W zh!UBuK2BJ9(5nqXg+O<8b*!J9jPG5glc{E&{*hH$J|O?IO64Zd0ZIBCTDq>ve016T z<`U5Vt&Z8;QlnhJq5H~V)%M|~x;S_O)EQh0x8LtQ2v-Vix7NCYpMU%D2y-mJ9`^o2j`@f8)pWS&{UOu;1|M+g>hhR2*?4xg`{p;ovD`0+FxpErJ ljmIw-U~UG7Cf`~)_wYBX@4j~RN(&~H_WD-q)5|yB`3EfoGmHQL literal 0 HcmV?d00001 diff --git a/tests/engine/assets/Assets/Model/cube.mtl b/tests/engine/assets/Assets/Model/cube.mtl new file mode 100644 index 000000000..160ccf845 --- /dev/null +++ b/tests/engine/assets/Assets/Model/cube.mtl @@ -0,0 +1,15 @@ +# Blender 4.4.1 MTL File: 'cube.blend' +# www.blender.org + +newmtl Material +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.500000 +d 1.000000 +illum 3 +Pr 0.500000 +Pm 0.700000 +Ps 0.000000 +Pc 0.000000 +Pcr 0.030000 +map_Kd checkerboard.png diff --git a/tests/engine/assets/Assets/Model/cube.obj b/tests/engine/assets/Assets/Model/cube.obj new file mode 100644 index 000000000..19a5f3663 --- /dev/null +++ b/tests/engine/assets/Assets/Model/cube.obj @@ -0,0 +1,40 @@ +# Blender 4.4.1 +# www.blender.org +mtllib cube.mtl +o Cube +v 1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 1.000000 +vn -0.0000 1.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vt 0.625000 0.500000 +vt 0.875000 0.500000 +vt 0.875000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.625000 1.000000 +vt 0.375000 1.000000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.125000 0.500000 +vt 0.375000 0.500000 +vt 0.125000 0.750000 +s 0 +usemtl Material +f 1/1/1 5/2/1 7/3/1 3/4/1 +f 4/5/2 3/4/2 7/6/2 8/7/2 +f 8/8/3 7/9/3 5/10/3 6/11/3 +f 6/12/4 2/13/4 4/5/4 8/14/4 +f 2/13/5 1/1/5 3/4/5 4/5/5 +f 6/11/6 5/10/6 1/1/6 2/13/6 From 03a68d5e409f805f1843d231c9f468ee36c446d3 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 12:16:26 +0900 Subject: [PATCH 264/450] feat(asset-ecs): add comparison operators for GenericAssetRef --- engine/src/assets/AssetRef.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/engine/src/assets/AssetRef.hpp b/engine/src/assets/AssetRef.hpp index a02985d08..3ed52b8ce 100644 --- a/engine/src/assets/AssetRef.hpp +++ b/engine/src/assets/AssetRef.hpp @@ -56,6 +56,22 @@ namespace nexo::assets { return !m_weakPtr.expired(); } + [[nodiscard]] bool operator==(const GenericAssetRef& other) const noexcept { + return m_weakPtr.lock() == other.m_weakPtr.lock(); + } + + [[nodiscard]] bool operator!=(const GenericAssetRef& other) const noexcept { + return !(*this == other); + } + + [[nodiscard]] bool operator==(const std::nullptr_t) const noexcept { + return !isValid(); + } + + [[nodiscard]] bool operator!=(const std::nullptr_t) const noexcept { + return isValid(); + } + /** * @brief Get a shared_ptr to the referenced asset * @return A shared_ptr to the asset, or nullptr if expired From 22fce715123aca9768c3f41832b7f66939556187 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 12:17:01 +0900 Subject: [PATCH 265/450] refactor(asset-ecs): fix warning for if indentation --- engine/src/ecs/Group.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/engine/src/ecs/Group.hpp b/engine/src/ecs/Group.hpp index b8da34f21..b9bfa4964 100644 --- a/engine/src/ecs/Group.hpp +++ b/engine/src/ecs/Group.hpp @@ -503,14 +503,15 @@ namespace nexo::ecs { 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"); + 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"); From f897795578aa47b060364a00cd8b88f26913b82b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 12:18:04 +0900 Subject: [PATCH 266/450] refactor(asset-ecs): describe texel buffer as bottom-left starting instead of top-left --- engine/src/assets/Assets/Texture/Texture.hpp | 6 +++--- engine/src/renderer/Texture.cpp | 4 ++-- engine/src/renderer/Texture.hpp | 9 +++++---- engine/src/renderer/opengl/OpenGlTexture2D.cpp | 5 ++++- engine/src/renderer/opengl/OpenGlTexture2D.hpp | 4 ++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/engine/src/assets/Assets/Texture/Texture.hpp b/engine/src/assets/Assets/Texture/Texture.hpp index bfe1ad8b3..71df91c45 100644 --- a/engine/src/assets/Assets/Texture/Texture.hpp +++ b/engine/src/assets/Assets/Texture/Texture.hpp @@ -92,7 +92,7 @@ namespace nexo::assets { * @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 top-left-most + * (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. @@ -103,7 +103,7 @@ namespace nexo::assets { * @throws NxTextureUnsupportedFormat If the specified format is not supported. * @throws NxTextureInvalidSize If the dimensions exceed the maximum texture size. */ - Texture(uint8_t* buffer, unsigned int width, unsigned int height, renderer::NxTextureFormat format) + Texture(const uint8_t *buffer, const unsigned int width, const unsigned int height, const renderer::NxTextureFormat format) : Asset() { auto texture = renderer::NxTexture2D::create(buffer, width, height, format); @@ -125,7 +125,7 @@ namespace nexo::assets { * @throws NxTextureUnsupportedFormat If the image format is not supported. * @throws NxTextureInvalidSize If the image dimensions exceed the maximum texture size. */ - Texture(uint8_t* buffer, unsigned int size) + Texture(const uint8_t* buffer, const unsigned int size) : Asset() { auto texture = renderer::NxTexture2D::create(buffer, size); diff --git a/engine/src/renderer/Texture.cpp b/engine/src/renderer/Texture.cpp index 69ede3924..bf240c97e 100644 --- a/engine/src/renderer/Texture.cpp +++ b/engine/src/renderer/Texture.cpp @@ -40,7 +40,7 @@ namespace nexo::renderer { THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } - std::shared_ptr NxTexture2D::create(uint8_t* buffer, unsigned int width, unsigned int height, + std::shared_ptr NxTexture2D::create(const uint8_t *buffer, unsigned int width, unsigned int height, NxTextureFormat format) { #ifdef NX_GRAPHICS_API_OPENGL @@ -49,7 +49,7 @@ namespace nexo::renderer { THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); } - std::shared_ptr NxTexture2D::create(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); diff --git a/engine/src/renderer/Texture.hpp b/engine/src/renderer/Texture.hpp index ab1c2a78d..2b7bcb74f 100644 --- a/engine/src/renderer/Texture.hpp +++ b/engine/src/renderer/Texture.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "String.hpp" @@ -81,7 +82,7 @@ namespace nexo::renderer { _NB_FORMATS_ // Number of texture formats, used for array sizing }; - constexpr const std::string& NxTextureFormatToString(const NxTextureFormat format) + constexpr const std::string_view NxTextureFormatToString(const NxTextureFormat format) { switch (format) { case NxTextureFormat::R8: return "R8"; @@ -124,7 +125,7 @@ namespace nexo::renderer { * @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 top-left-most + * (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. @@ -141,7 +142,7 @@ namespace nexo::renderer { * auto texture = NxTexture2D::create(pixelData.data(), 128, 128, NxTextureFormat::RGBA8); * ``` */ - static std::shared_ptr create(uint8_t *buffer, unsigned int width, unsigned int height, NxTextureFormat format); + 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. @@ -160,7 +161,7 @@ namespace nexo::renderer { * auto texture = NxTexture2D::create(imageData.data(), imageData.size()); * ``` */ - static std::shared_ptr create(uint8_t *buffer, unsigned int len); + static std::shared_ptr create(const uint8_t* buffer, unsigned int len); /** * @brief Creates a 2D texture from an image file. diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index 5af4a7c9d..0f6c7b34c 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -27,7 +27,7 @@ namespace nexo::renderer { createOpenGLTexture(nullptr, width, height, GL_RGBA8, GL_RGBA); } - NxOpenGlTexture2D::NxOpenGlTexture2D(const uint8_t* buffer, const unsigned int width, const unsigned int height, + NxOpenGlTexture2D::NxOpenGlTexture2D(const uint8_t *buffer, const unsigned int width, const unsigned int height, const NxTextureFormat format) : m_width(width), m_height(height) { if (!buffer) @@ -61,6 +61,7 @@ namespace nexo::renderer { } NxOpenGlTexture2D::NxOpenGlTexture2D(const std::string &path) + : m_path(path) { int width = 0; int height = 0; @@ -163,6 +164,8 @@ namespace nexo::renderer { m_internalFormat = internalFormat; m_dataFormat = dataFormat; + m_width = width; + m_height = height; glGenTextures(1, &m_id); glBindTexture(GL_TEXTURE_2D, m_id); diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.hpp b/engine/src/renderer/opengl/OpenGlTexture2D.hpp index 59dbbe1d9..1bd99a52b 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.hpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.hpp @@ -78,7 +78,7 @@ namespace nexo::renderer { * @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 top-left-most + * (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 @@ -104,7 +104,7 @@ namespace nexo::renderer { * auto texture = std::make_shared(pixelData.data(), 256, 256, NxTextureFormat::RGBA8); * ``` */ - NxOpenGlTexture2D(const uint8_t* buffer, unsigned int width, unsigned int height, NxTextureFormat format); + 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). From 75b65ac775426462482c4006ecd7de6a554bee1e Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 12:19:00 +0900 Subject: [PATCH 267/450] feat(asset-ecs): fix assimp hint handling, fix roughness --- engine/src/assets/Assets/Model/ModelImporter.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index c917ea54a..16870ae2b 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -116,7 +116,13 @@ namespace nexo::assets { } else { // Uncompressed texture auto& catalog = AssetCatalog::getInstance(); - renderer::NxTextureFormat format = convertAssimpHintToNxTextureFormat(texture->achFormatHint); + renderer::NxTextureFormat format; + if (texture->achFormatHint[0] == '\0') { // if empty, then ARGB888 + 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); return nullptr; @@ -228,8 +234,8 @@ namespace nexo::assets { materialComponent->emissiveColor = { color.r, color.g, color.b }; } - if (float roughness = 0.0f; material->Get(AI_MATKEY_SHININESS, roughness) == AI_SUCCESS) { - materialComponent->roughness = 1.0f - (roughness / 100.0f); // Convert glossiness to roughness + if (float roughness = 0.0f; material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness) == AI_SUCCESS) { + materialComponent->roughness = roughness; } // Load Metallic From 16942c141b85a111e1a7944c8a37a64cbd23eaa6 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 12:19:16 +0900 Subject: [PATCH 268/450] feat(asset-ecs): enhance ModelImporter tests with OpenGL context and detailed asset validation --- .../Assets/Model/ModelImporter.test.cpp | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/tests/engine/assets/Assets/Model/ModelImporter.test.cpp b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp index c80938b65..d23753676 100644 --- a/tests/engine/assets/Assets/Model/ModelImporter.test.cpp +++ b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp @@ -28,6 +28,7 @@ #include #include "Path.hpp" +#include "../tests/renderer/contexts/opengl.hpp" using namespace testing; using namespace nexo::assets; @@ -42,9 +43,10 @@ class TestModelImporter : public ModelImporter { }; // Test fixture for ModelImporter tests -class ModelImporterTestFixture : public Test { +class ModelImporterTestFixture : public nexo::renderer::OpenGLTest { protected: void SetUp() override { + OpenGLTest::SetUp(); // Clean up the catalog before each test auto& catalog = AssetCatalog::getInstance(); for (auto& asset : catalog.getAssets()) { @@ -52,8 +54,9 @@ class ModelImporterTestFixture : public Test { } } - void TearDown() override + void TearDown() override { + OpenGLTest::TearDown(); // Clean up the catalog after each test auto& catalog = AssetCatalog::getInstance(); for (auto& asset : catalog.getAssets()) { @@ -168,16 +171,69 @@ TEST_F(ModelImporterTestFixture, ConvertAssimpHintToNxTextureFormat) { // Additional test for processing empty mesh TEST_F(ModelImporterTestFixture, ImportCubeModel) { + auto& catalog = AssetCatalog::getInstance(); + AssetImporterContext ctx = {}; ctx.location = AssetLocation("test::cube_model@test_folder"); ctx.input = ImporterFileInput{std::filesystem::path(nexo::Path::resolvePathRelativeToExe("../tests/engine/assets/Assets/Model/cube.obj"))}; importer.import(ctx); - /* EXPECT_NE(ctx.getMainAsset(), nullptr); EXPECT_EQ(ctx.getMainAsset()->getType(), Model::TYPE); EXPECT_EQ(ctx.getMainAsset()->isLoaded(), true); - */ + const auto allAssets = catalog.getAssets(); + EXPECT_EQ(allAssets.size(), 3); // 2 materials + 1 texture + + auto model = static_cast(ctx.getMainAsset()); + // Now verify the model data + const auto& modelData = model->getData(); + ASSERT_NE(modelData, nullptr); + + const auto root = modelData->root; + + // Root mesh in this case has zero mesh but 1 child + ASSERT_EQ(modelData->root->meshes.size(), 0); + ASSERT_EQ(root->children.size(), 1); + const auto child = root->children[0]; + EXPECT_EQ(child->children.size(), 0); + EXPECT_EQ(child->meshes.size(), 1); + const auto childMesh = child->meshes[0]; + + // Get the mesh and verify its properties + EXPECT_EQ(childMesh.name, "Cube"); + + // A cube should have 8 vertices and 12 triangles (36 indices) + EXPECT_EQ(childMesh.vertices.size(), 24); // 24 because each vertex is duplicated for different face normals/UVs + EXPECT_EQ(childMesh.indices.size(), 36); // 6 faces × 2 triangles × 3 vertices + + // Check the Material reference + const auto material = childMesh.material.lock(); + EXPECT_NE(material, nullptr); + + const auto materialData = material->getData(); + EXPECT_NE(materialData, nullptr); + + EXPECT_NE(materialData->albedoTexture, nullptr); + const auto albedoTextureAsset = materialData->albedoTexture.lock(); + EXPECT_NE(albedoTextureAsset, nullptr); + EXPECT_EQ(albedoTextureAsset->getType(), Texture::TYPE); + + const auto albedoTexture = albedoTextureAsset->getData(); + EXPECT_NE(albedoTexture->texture, nullptr); + EXPECT_EQ(albedoTexture->texture->getWidth(), 64); + EXPECT_EQ(albedoTexture->texture->getHeight(), 64); + + // Check material properties from the MTL file + EXPECT_FLOAT_EQ(materialData->specularColor.r, 0.5f); + EXPECT_FLOAT_EQ(materialData->specularColor.g, 0.5f); + EXPECT_FLOAT_EQ(materialData->specularColor.b, 0.5f); + + EXPECT_FLOAT_EQ(materialData->emissiveColor.r, 0.0f); + EXPECT_FLOAT_EQ(materialData->emissiveColor.g, 0.0f); + EXPECT_FLOAT_EQ(materialData->emissiveColor.b, 0.0f); + // Check roughness and metallic properties if supported + EXPECT_FLOAT_EQ(materialData->roughness, 0.5f); // Pr in MTL + EXPECT_FLOAT_EQ(materialData->metallic, 0.7f); // Pm in MTL } From bf6c95d54d83ed17e8c600ecf661c31b3f547f0b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 15:39:49 +0900 Subject: [PATCH 269/450] test(asset-ecs): change GLFW skip to fail on initialization errors --- tests/renderer/contexts/opengl.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/renderer/contexts/opengl.hpp b/tests/renderer/contexts/opengl.hpp index 96baf717f..d35e06c0d 100644 --- a/tests/renderer/contexts/opengl.hpp +++ b/tests/renderer/contexts/opengl.hpp @@ -28,7 +28,7 @@ namespace nexo::renderer { void SetUp() override { if (!glfwInit()) { - GTEST_SKIP() << "GLFW initialization failed. Skipping OpenGL tests."; + GTEST_FAIL() << "GLFW initialization failed. Failing OpenGL tests."; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); @@ -38,7 +38,7 @@ namespace nexo::renderer { window = glfwCreateWindow(800, 600, "Test Window", nullptr, nullptr); if (!window) { glfwTerminate(); - GTEST_SKIP() << "Failed to create GLFW window. Skipping OpenGL tests."; + GTEST_FAIL() << "Failed to create GLFW window. Failing OpenGL tests."; } glfwMakeContextCurrent(window); @@ -46,7 +46,7 @@ namespace nexo::renderer { if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { glfwDestroyWindow(window); glfwTerminate(); - GTEST_SKIP() << "Failed to initialize GLAD. Skipping OpenGL tests."; + GTEST_FAIL() << "Failed to initialize GLAD. Failing OpenGL tests."; } GLint major = 0, minor = 0; @@ -55,7 +55,7 @@ namespace nexo::renderer { if (major < 4 || (major == 4 && minor < 5)) { glfwDestroyWindow(window); glfwTerminate(); - GTEST_SKIP() << "OpenGL 4.5 is required. Skipping OpenGL tests."; + GTEST_FAIL() << "OpenGL 4.5 is required. Failing OpenGL tests."; } } @@ -85,4 +85,4 @@ namespace nexo::renderer { MOCK_METHOD(unsigned int, getCount, (), (const, override)); MOCK_METHOD(unsigned int, getId, (), (const, override)); }; -} \ No newline at end of file +} From 37096798eb87ea01fb73e5d2755f654fb0f9ff15 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 16:21:51 +0900 Subject: [PATCH 270/450] ci(asset-ecs): add workflow_dispatch support with optional debug input for SSH debugging --- .github/workflows/build.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f327509b..3793fa2a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,14 @@ name: Build, test and Package -on: [push] +on: + push: + workflow_dispatch: + inputs: + debug_enabled: + type: boolean + description: 'Enable remote SSH connection. Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + required: false + default: false jobs: build: @@ -42,6 +50,14 @@ jobs: submodules: recursive fetch-depth: 0 # Fetch all history for all tags and branches (for SonarCloud) + # DEBUGGING ONLY, to run this trigger with even + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + with: + limit-access-to-actor: true # Only the person who triggered the workflow can access + detached: true # Run in the background and wait for connection + - name: Add Ubuntu toolchain repository if: ${{ matrix.os == 'ubuntu-latest' && matrix.compiler == 'gcc'}} run: | From 77f4255a2078de0943aa9e7271dd2b026654929c Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 17:24:23 +0900 Subject: [PATCH 271/450] test(asset-ecs): fix segv reorder TearDown to ensure OpenGL cleanup occurs after asset deletion --- tests/engine/assets/Assets/Model/ModelImporter.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/engine/assets/Assets/Model/ModelImporter.test.cpp b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp index d23753676..ba88de4ea 100644 --- a/tests/engine/assets/Assets/Model/ModelImporter.test.cpp +++ b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp @@ -56,12 +56,12 @@ class ModelImporterTestFixture : public nexo::renderer::OpenGLTest { void TearDown() override { - OpenGLTest::TearDown(); // Clean up the catalog after each test auto& catalog = AssetCatalog::getInstance(); for (auto& asset : catalog.getAssets()) { catalog.deleteAsset(asset); } + OpenGLTest::TearDown(); } TestModelImporter importer; From a3deb1cdaec0cc99f5484c61edcd61d857413599 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 19:30:58 +0900 Subject: [PATCH 272/450] feat(asset-ecs): add ARGB8 to RGBA8 conversion and enhance texture format handling --- .../src/assets/Assets/Model/ModelImporter.cpp | 6 +++- engine/src/renderer/Texture.cpp | 10 ++++++ engine/src/renderer/Texture.hpp | 36 ++++++++++++++++--- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index 16870ae2b..77eeee6be 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -118,6 +118,10 @@ namespace nexo::assets { renderer::NxTextureFormat format; if (texture->achFormatHint[0] == '\0') { // if empty, then ARGB888 + renderer::NxTextureFormatConvertArgb8ToRgba8( + reinterpret_cast(texture->pcData), + texture->mWidth * texture->mHeight * sizeof(aiTexel) + ); format = renderer::NxTextureFormat::RGBA8; } else { format = convertAssimpHintToNxTextureFormat(texture->achFormatHint); @@ -258,7 +262,7 @@ namespace nexo::assets { aiString aiStr; if (material->GetTexture(type, 0, &aiStr) == AI_SUCCESS) { const char* cStr = aiStr.C_Str(); - if (cStr[0] == '*') { + if (cStr[0] == '*' || scene->GetEmbeddedTexture(cStr)) { // Embedded texture if (const auto it = m_textures.find(cStr) ; it != m_textures.end()) { return it->second; diff --git a/engine/src/renderer/Texture.cpp b/engine/src/renderer/Texture.cpp index bf240c97e..d718f2c68 100644 --- a/engine/src/renderer/Texture.cpp +++ b/engine/src/renderer/Texture.cpp @@ -32,6 +32,16 @@ namespace nexo::renderer { return NxTextureFormat::INVALID; } + void NxTextureFormatConvertArgb8ToRgba8(uint8_t *bytes, size_t size) + { + uint32_t *pixels = reinterpret_cast(bytes); + const size_t width = size / 4; + + for (size_t i = 0; i < width; ++i) { + pixels[i] = (pixels[i] << 8) | (pixels[i] >> 24); + } + } + std::shared_ptr NxTexture2D::create(unsigned int width, unsigned int height) { #ifdef NX_GRAPHICS_API_OPENGL diff --git a/engine/src/renderer/Texture.hpp b/engine/src/renderer/Texture.hpp index 2b7bcb74f..a62297e83 100644 --- a/engine/src/renderer/Texture.hpp +++ b/engine/src/renderer/Texture.hpp @@ -13,13 +13,10 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include #include #include #include -#include "String.hpp" - namespace nexo::renderer { /** @@ -82,7 +79,16 @@ namespace nexo::renderer { _NB_FORMATS_ // Number of texture formats, used for array sizing }; - constexpr const std::string_view NxTextureFormatToString(const NxTextureFormat format) + /** + * @brief Converts a NxTextureFormat enum value to its string representation. + * + * This function takes a NxTextureFormat enum value and returns its corresponding + * string representation. The string is a human-readable format name (e.g., "R8", "RGBA8"). + * + * @param format The NxTextureFormat enum value to convert. + * @return A string_view representing the format name. + */ + [[nodiscard]] constexpr std::string_view NxTextureFormatToString(const NxTextureFormat format) { switch (format) { case NxTextureFormat::R8: return "R8"; @@ -93,8 +99,30 @@ namespace nexo::renderer { } } + /** + * @brief Converts a string representation of a texture format to its NxTextureFormat enum value. + * + * This function takes a string representation of a texture format (e.g., "R8", "RGBA8") and + * returns the corresponding NxTextureFormat enum value. If the string does not match any + * known format, it returns NxTextureFormat::INVALID. + * + * @param format The string representation of the texture format. + * @return The corresponding NxTextureFormat enum value. + */ NxTextureFormat NxTextureFormatFromString(const std::string_view &format); + /** + * @brief Converts ARGB8 format to RGBA8 format in place. + * + * This function takes a pointer to a byte array and its size, and converts the + * pixel data from ARGB8 format to RGBA8 format. The conversion is done in place, + * meaning the original data will be modified. + * + * @param bytes Pointer to the byte array containing ARGB8 pixel data. + * @param size Size of the byte array in bytes. + */ + void NxTextureFormatConvertArgb8ToRgba8(uint8_t *bytes, size_t size); + class NxTexture2D : public NxTexture { public: /** From a2a3b15df8b2b585813bd59be29f34b142df746d Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 27 Apr 2025 19:51:42 +0900 Subject: [PATCH 273/450] refactor(asset-ecs): remove redundant const qualifiers from getter methods -> suppress warning --- editor/src/inputs/Command.cpp | 4 +++- editor/src/inputs/Command.hpp | 2 +- editor/src/inputs/WindowState.cpp | 2 +- editor/src/inputs/WindowState.hpp | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/editor/src/inputs/Command.cpp b/editor/src/inputs/Command.cpp index 5074048bb..77b439d46 100644 --- a/editor/src/inputs/Command.cpp +++ b/editor/src/inputs/Command.cpp @@ -20,6 +20,8 @@ #include #include +#include "String.hpp" + namespace nexo::editor { Command::Command( const std::string &description, @@ -166,7 +168,7 @@ namespace nexo::editor { return m_key; } - const bool Command::isModifier() const + bool Command::isModifier() const { return m_isModifier; } diff --git a/editor/src/inputs/Command.hpp b/editor/src/inputs/Command.hpp index db6fa0ead..5f1308918 100644 --- a/editor/src/inputs/Command.hpp +++ b/editor/src/inputs/Command.hpp @@ -42,7 +42,7 @@ namespace nexo::editor { const std::bitset &getSignature() const; const std::string &getKey() const; const std::string &getDescription() const; - const bool isModifier() const; + bool isModifier() const; class Builder { public: diff --git a/editor/src/inputs/WindowState.cpp b/editor/src/inputs/WindowState.cpp index 0349dc144..47bc1b859 100644 --- a/editor/src/inputs/WindowState.cpp +++ b/editor/src/inputs/WindowState.cpp @@ -15,7 +15,7 @@ #include "WindowState.hpp" namespace nexo::editor { - const unsigned int WindowState::getId() const + unsigned int WindowState::getId() const { return m_id; } diff --git a/editor/src/inputs/WindowState.hpp b/editor/src/inputs/WindowState.hpp index 82431ce33..9b0c768a5 100644 --- a/editor/src/inputs/WindowState.hpp +++ b/editor/src/inputs/WindowState.hpp @@ -24,7 +24,7 @@ namespace nexo::editor { WindowState(unsigned int id) : m_id(id) {} ~WindowState() = default; - const unsigned int getId() const; + unsigned int getId() const; void registerCommand(const Command &command); const std::span getCommands() const; private: From 12906a959ed7c3df1a9d6387d384ee24695372c2 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 28 Apr 2025 00:12:54 +0900 Subject: [PATCH 274/450] feat(asset-ecs): enhance logging callbacks to include source location information --- common/Logger.hpp | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/common/Logger.hpp b/common/Logger.hpp index 6f7d1f85f..58ec1b26c 100644 --- a/common/Logger.hpp +++ b/common/Logger.hpp @@ -71,12 +71,13 @@ namespace nexo { return path; } - inline void defaultCallback(const LogLevel level, const std::string &message) + inline void defaultCallback(const LogLevel level, const std::source_location& loc, const std::string &message) { - if (level == LogLevel::FATAL || level == LogLevel::ERR) - std::cerr << "[" << toString(level) << "] " << message << std::endl; - else - std::cout << "[" << toString(level) << "] " << message << std::endl; + std::ostream& outputStream = level == LogLevel::FATAL || level == LogLevel::ERR + ? std::cerr + : std::cout; + + outputStream << "[" << toString(level) << "] " << getFileName(loc.file_name()) << ":" << loc.line() << " - " << message << std::endl; } /** @@ -136,7 +137,7 @@ namespace nexo { class Logger { public: - static void setCallback(std::function callback) + static void setCallback(std::function callback) { logCallback = std::move(callback); } @@ -152,14 +153,7 @@ namespace nexo { }, transformed); - if (level == LogLevel::INFO) - logString(level, message); - else - { - std::stringstream ss; - ss << getFileName(loc.file_name()) << ":" << loc.line() << " - " << message; - logString(level, ss.str()); - } + logCallback(level, loc, message); } /** @@ -221,14 +215,7 @@ namespace nexo { } private: - static void logString(const LogLevel level, const std::string &message) - { - if (logCallback) - logCallback(level, message); - else - defaultCallback(level, message); - } - static inline std::function logCallback = nullptr; + static inline std::function logCallback = defaultCallback; }; } From 9157415209107330fdb713cae8c5c3e9bd2b185b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 28 Apr 2025 00:17:17 +0900 Subject: [PATCH 275/450] refactor(asset-ecs): convert IAsset methods to pure virtual functions for better interface definition --- engine/src/assets/Asset.hpp | 58 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index 0a3bc66c0..874f377b6 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -139,27 +139,29 @@ namespace nexo::assets { AssetLocation location; //< Location of the asset }; + /** + * @brief Pure virtual interface for all assets + */ class IAsset { friend class AssetCatalog; friend class AssetImporter; public: - //IAsset() = delete; virtual ~IAsset() = default; - - [[nodiscard]] virtual const AssetMetadata& getMetadata() const { return m_metadata; } - [[nodiscard]] virtual AssetType getType() const { return getMetadata().type; } - [[nodiscard]] virtual AssetID getID() const { return getMetadata().id; } - [[nodiscard]] virtual AssetStatus getStatus() const { return getMetadata().status; } - - [[nodiscard]] virtual bool isLoaded() const { return getStatus() == AssetStatus::LOADED; } - [[nodiscard]] virtual bool isErrored() const { return getStatus() == AssetStatus::ERROR; } - + + [[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; + /** * @brief Get the asset data pointer * @return Raw pointer to the asset data */ [[nodiscard]] virtual void* getRawData() const = 0; - + /** * @brief Set the asset data pointer * @param rawData Raw pointer to the asset data @@ -177,29 +179,24 @@ namespace nexo::assets { }) { } - + public: AssetMetadata m_metadata; - + /** * @brief Get the metadata of the asset (for modification) */ //[[nodiscard]] AssetMetadata& getMetadata() { return m_metadata; } - - /*virtual AssetStatus load() = 0; - virtual AssetStatus unload() = 0;*/ - }; - + template class Asset : public IAsset { friend class AssetCatalog; - friend class AssetRef; public: using AssetDataType = TAssetData; static constexpr AssetType TYPE = TAssetType; - + /** * @brief Destructor that releases the allocated asset data. * @@ -209,26 +206,33 @@ namespace nexo::assets { { delete data; } - + TAssetData *data; + [[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; } + // Implementation of IAsset virtual methods [[nodiscard]] void* getRawData() const override { return data; } - + IAsset& setRawData(void* rawData) override { delete data; // Clean up existing data if (rawData == nullptr) { m_metadata.status = AssetStatus::UNLOADED; } else { - m_metadata.status = AssetStatus::LOADED; } data = static_cast(rawData); return *this; } - + [[nodiscard]] TAssetData* getData() const { return data; } @@ -243,7 +247,7 @@ namespace nexo::assets { data = newData; return *this; } - + protected: explicit Asset() : data(nullptr) { @@ -255,12 +259,10 @@ namespace nexo::assets { m_metadata.type = TAssetType; m_metadata.status = AssetStatus::LOADED; } - + private: - /*virtual AssetStatus load() = 0; virtual AssetStatus unload() = 0;*/ - }; } // namespace nexo::editor From 523c413d494ac77ed18f98cd21eeb01fe00c20a2 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 28 Apr 2025 00:20:59 +0900 Subject: [PATCH 276/450] feat(asset-ecs): update TextureButton to return modified status and output new texture path --- editor/src/ImNexo/Components.cpp | 21 ++++++------------ editor/src/ImNexo/Components.hpp | 14 +++++++----- editor/src/ImNexo/Panels.cpp | 38 ++++++++++++++++++++++++++------ 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp index 4184ecb54..2306a0c55 100644 --- a/editor/src/ImNexo/Components.cpp +++ b/editor/src/ImNexo/Components.cpp @@ -99,17 +99,16 @@ namespace ImNexo { } - bool TextureButton(const std::string &label, std::shared_ptr &texture) + bool TextureButton(const std::string &label, const std::shared_ptr& texture, std::filesystem::path& outPath) { - bool textureModified = false; + 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 (ImGui::ImageButton(textureButton.c_str(), textureId, previewSize)) - { + if (ImGui::ImageButton(textureButton.c_str(), textureId, previewSize)) { const char* filePath = tinyfd_openFileDialog( "Open Texture", "", @@ -119,22 +118,16 @@ namespace ImNexo { 0 ); - if (filePath) - { - const std::string path(filePath); - std::shared_ptr newTexture = nexo::renderer::NxTexture2D::create(path); - if (newTexture) - { - texture = newTexture; - textureModified = true; - } + if (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 textureModified; + return modified; } bool IconGradientButton( diff --git a/editor/src/ImNexo/Components.hpp b/editor/src/ImNexo/Components.hpp index 518dc3af8..19755e277 100644 --- a/editor/src/ImNexo/Components.hpp +++ b/editor/src/ImNexo/Components.hpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once +#include #include #include #include @@ -54,14 +55,15 @@ namespace ImNexo { /** * @brief Draws a texture button that displays a texture preview. * - * When clicked, opens a file dialog to select a new texture. If a new texture is loaded, - * the passed texture pointer is updated and the function returns true. + * 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 label A unique label identifier for the button. - * @param texture A shared pointer to the renderer::NxTexture2D that holds the texture. - * @return true if the texture was modified; false otherwise. + * @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, std::shared_ptr &texture); + 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. diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index f9b5c4dc0..a68963b6e 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -20,6 +20,7 @@ #include "CameraFactory.hpp" #include "Path.hpp" #include "IconsFontAwesome.h" +#include "assets/AssetCatalog.hpp" #include "components/Uuid.hpp" #include "context/Selector.hpp" #include "utils/EditorProps.hpp" @@ -60,13 +61,25 @@ namespace ImNexo { //TODO: implement rendering mode } + auto& catalog = nexo::assets::AssetCatalog::getInstance(); // --- Albedo texture --- { static ImGuiColorEditFlags colorPickerModeAlbedo = ImGuiColorEditFlags_PickerHueBar; static bool showColorPickerAlbedo = false; - const auto albedoTextureAsset = material->albedoTexture.lock(); - auto albedoTexture = albedoTextureAsset ? albedoTextureAsset->data->texture : nullptr; - modified = TextureButton("Albedo texture", albedoTexture) || modified; + const auto asset = material->albedoTexture.lock(); + auto albedoTexture = asset && asset->isLoaded() ? asset->data->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; } @@ -75,12 +88,23 @@ namespace ImNexo { { static ImGuiColorEditFlags colorPickerModeSpecular = ImGuiColorEditFlags_PickerHueBar; static bool showColorPickerSpecular = false; - const auto metallicTextureAsset = material->metallicMap.lock(); - auto metallicTexture = metallicTextureAsset ? metallicTextureAsset->data->texture : nullptr; - modified = TextureButton("Specular texture", metallicTexture) || modified; + const auto asset = material->metallicMap.lock(); + auto metallicTexture = asset && asset->isLoaded() ? asset->data->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; - } + } return modified; } From 1b3416cdaf7a4ee2a0a90b79ce4ce1167c373da1 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 28 Apr 2025 00:21:18 +0900 Subject: [PATCH 277/450] feat(asset-ecs): enhance error handling with message box for fatal exceptions --- editor/main.cpp | 66 ++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/editor/main.cpp b/editor/main.cpp index 824448303..923662908 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -21,41 +21,41 @@ #include "src/DocumentWindows/MaterialInspector/MaterialInspector.hpp" #include +#include #include int main(int argc, char **argv) -{ - try { - loguru::init(argc, argv); - loguru::g_stderr_verbosity = loguru::Verbosity_3; - nexo::editor::Editor &editor = nexo::editor::Editor::getInstance(); - - editor.registerWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)); - editor.registerWindow(NEXO_WND_USTRID_SCENE_TREE); - editor.registerWindow(NEXO_WND_USTRID_INSPECTOR); - editor.registerWindow(NEXO_WND_USTRID_CONSOLE); - editor.registerWindow(NEXO_WND_USTRID_MATERIAL_INSPECTOR); - editor.registerWindow(NEXO_WND_USTRID_ASSET_MANAGER); - - if (auto defaultScene = editor.getWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)).lock()) - defaultScene->setDefault(); - - editor.init(); - - while (editor.isOpen()) - { - auto start = std::chrono::high_resolution_clock::now(); - editor.render(); - editor.update(); - - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = end - start; - std::this_thread::sleep_for(std::chrono::milliseconds(16) - elapsed); - } - editor.shutdown(); - return 0; - } catch (const nexo::Exception &e) { - LOG_EXCEPTION(e); - return 1; +try { + loguru::init(argc, argv); + loguru::g_stderr_verbosity = loguru::Verbosity_3; + nexo::editor::Editor &editor = nexo::editor::Editor::getInstance(); + + editor.registerWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)); + editor.registerWindow(NEXO_WND_USTRID_SCENE_TREE); + editor.registerWindow(NEXO_WND_USTRID_INSPECTOR); + editor.registerWindow(NEXO_WND_USTRID_CONSOLE); + editor.registerWindow(NEXO_WND_USTRID_MATERIAL_INSPECTOR); + editor.registerWindow(NEXO_WND_USTRID_ASSET_MANAGER); + + if (auto defaultScene = editor.getWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)).lock()) + defaultScene->setDefault(); + + editor.init(); + + while (editor.isOpen()) + { + auto start = std::chrono::high_resolution_clock::now(); + editor.render(); + editor.update(); + + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + std::this_thread::sleep_for(std::chrono::milliseconds(16) - elapsed); } + editor.shutdown(); + return 0; +} catch (const nexo::Exception &e) { + LOG_EXCEPTION(e); + tinyfd_messageBox("NEXO had a fatal error", e.what(), "ok", "error", 1); + return 1; } From 02a0b1d480e0dafcc6a4b2b8b02faf3de1f2b84a Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 28 Apr 2025 11:49:29 +0900 Subject: [PATCH 278/450] refactor(asset-ecs): add memchr with strlen for safer validation --- engine/src/assets/Assets/Model/ModelImporter.cpp | 3 ++- tests/engine/assets/Assets/Model/ModelImporter.test.cpp | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index 77eeee6be..76252d234 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -141,7 +141,8 @@ namespace nexo::assets { renderer::NxTextureFormat ModelImporter::convertAssimpHintToNxTextureFormat(const char achFormatHint[9]) { - if (std::strlen(achFormatHint) != 8) { + if (std::memchr(achFormatHint, '\0', 9) == nullptr + || std::strlen(achFormatHint) != 8) { return renderer::NxTextureFormat::INVALID; } diff --git a/tests/engine/assets/Assets/Model/ModelImporter.test.cpp b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp index ba88de4ea..46787461f 100644 --- a/tests/engine/assets/Assets/Model/ModelImporter.test.cpp +++ b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp @@ -141,7 +141,6 @@ TEST_F(ModelImporterTestFixture, ConvertAssimpHintToNxTextureFormat) { std::string hint = "abgr0000"; do { hint.replace(hint.begin() + 4, hint.begin() + 8, "0000"); - GTEST_LOG_(INFO) << "HINT: " << hint; const auto rPos = std::ranges::find(hint, 'r'); const auto gPos = std::ranges::find(hint, 'g'); From d4d5827ee1bbf1ed91ee96ce671c66b7c6132a1b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 29 Apr 2025 16:10:30 +0900 Subject: [PATCH 279/450] fix(asset-ecs): images and textures flipped UVs #237 --- .../EntityProperties/RenderProperty.cpp | 6 +-- .../MaterialInspector/Show.cpp | 2 +- editor/src/ImNexo/Components.cpp | 2 +- editor/src/ImNexo/Elements.cpp | 12 +++++ editor/src/ImNexo/Elements.hpp | 49 +++++++++++++++++++ editor/src/ImNexo/Panels.cpp | 4 +- .../src/assets/Assets/Model/ModelImporter.cpp | 1 - .../src/renderer/opengl/OpenGlTexture2D.cpp | 2 +- engine/src/renderer/primitives/Billboard.cpp | 8 +-- 9 files changed, 73 insertions(+), 13 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index 3aade16cd..11fc5c62e 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -84,8 +84,8 @@ namespace nexo::editor { const float displayWidth = displayHeight * aspectRatio; ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 4, ImGui::GetCursorPosY() + 4)); - ImGui::Image(static_cast(static_cast(textureId)), - ImVec2(displayWidth, displayHeight), ImVec2(0, 1), ImVec2(1, 0)); + ImNexo::Image(static_cast(static_cast(textureId)), + ImVec2(displayWidth, displayHeight)); ImGui::EndChild(); } @@ -178,7 +178,7 @@ namespace nexo::editor { // --- Material Preview --- if (framebuffer && framebuffer->getColorAttachmentId(0) != 0) - ImGui::Image(static_cast(static_cast(framebuffer->getColorAttachmentId(0))), ImVec2(64, 64), ImVec2(0, 1), ImVec2(1, 0)); + ImNexo::Image(static_cast(static_cast(framebuffer->getColorAttachmentId(0))), ImVec2(64, 64)); ImGui::SameLine(); ImGui::BeginGroup(); diff --git a/editor/src/DocumentWindows/MaterialInspector/Show.cpp b/editor/src/DocumentWindows/MaterialInspector/Show.cpp index a4f74d312..779665949 100644 --- a/editor/src/DocumentWindows/MaterialInspector/Show.cpp +++ b/editor/src/DocumentWindows/MaterialInspector/Show.cpp @@ -58,7 +58,7 @@ namespace nexo::editor { THROW_EXCEPTION(BackendRendererApiFatalFailure, "OPENGL", "Failed to initialize framebuffer in Material Inspector window"); // --- Material preview --- if (m_framebuffer->getColorAttachmentId(0) != 0) - ImGui::Image(static_cast(static_cast(m_framebuffer->getColorAttachmentId(0))), {64, 64}, ImVec2(0, 1), ImVec2(1, 0)); + ImNexo::Image(static_cast(static_cast(m_framebuffer->getColorAttachmentId(0))), {64, 64}); ImGui::SameLine(); auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp index 2306a0c55..1cd21c7ae 100644 --- a/editor/src/ImNexo/Components.cpp +++ b/editor/src/ImNexo/Components.cpp @@ -108,7 +108,7 @@ namespace ImNexo { const ImTextureID textureId = texture ? static_cast(static_cast(texture->getId())) : 0; const std::string textureButton = std::string("##TextureButton") + label; - if (ImGui::ImageButton(textureButton.c_str(), textureId, previewSize)) { + if (ImNexo::ImageButton(textureButton.c_str(), textureId, previewSize)) { const char* filePath = tinyfd_openFileDialog( "Open Texture", "", diff --git a/editor/src/ImNexo/Elements.cpp b/editor/src/ImNexo/Elements.cpp index 967aae448..9f217c2d5 100644 --- a/editor/src/ImNexo/Elements.cpp +++ b/editor/src/ImNexo/Elements.cpp @@ -398,4 +398,16 @@ namespace ImNexo { ImGui::Dummy(ImVec2(0, ImGui::GetTextLineHeight())); } + + 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); + } + + 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) + { + return ImGui::ImageButton(str_id, user_texture_id, image_size, uv0, uv1, bg_col, tint_col); + } } diff --git a/editor/src/ImNexo/Elements.hpp b/editor/src/ImNexo/Elements.hpp index 342fad593..901165182 100644 --- a/editor/src/ImNexo/Elements.hpp +++ b/editor/src/ImNexo/Elements.hpp @@ -284,4 +284,53 @@ namespace ImNexo { * @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::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) + ); + } diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index a68963b6e..00dfd06c9 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -343,8 +343,8 @@ namespace ImNexo { const float displayWidth = displayHeight; ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 4, ImGui::GetCursorPosY() + 4)); - ImGui::Image(static_cast(static_cast(textureId)), - ImVec2(displayWidth, displayHeight), ImVec2(0, 1), ImVec2(1, 0)); + ImNexo::Image(static_cast(static_cast(textureId)), + ImVec2(displayWidth, displayHeight)); ImGui::EndChild(); } diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index 76252d234..9c95f6e71 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -58,7 +58,6 @@ namespace nexo::assets { //m_model->setData(new components::Model()); const auto param = ctx.getParameters(); int flags = aiProcess_Triangulate - | aiProcess_FlipUVs | aiProcess_GenNormals; const aiScene* scene = nullptr; if (std::holds_alternative(ctx.input)) diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index 0f6c7b34c..4425844d9 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -67,7 +67,7 @@ namespace nexo::renderer { int height = 0; int channels = 0; //TODO: Set this conditionnaly based on the type of texture - //stbi_set_flip_vertically_on_load(1); + 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); diff --git a/engine/src/renderer/primitives/Billboard.cpp b/engine/src/renderer/primitives/Billboard.cpp index 04fcb2ed2..dee9a4029 100644 --- a/engine/src/renderer/primitives/Billboard.cpp +++ b/engine/src/renderer/primitives/Billboard.cpp @@ -37,10 +37,10 @@ namespace nexo::renderer { }; constexpr glm::vec2 billboardTexCoords[4] = { - {0.0f, 1.0f}, // Bottom left (flipped Y) - {1.0f, 1.0f}, // Bottom right (flipped Y) - {1.0f, 0.0f}, // Top right (flipped Y) - {0.0f, 0.0f} // Top left (flipped Y) + {0.0f, 0.0f}, // Bottom left + {1.0f, 0.0f}, // Bottom right + {1.0f, 1.0f}, // Top right + {0.0f, 1.0f}, // Top left }; /** From 95c763e2d74c3fd3bf03cf920f2ca7982baf52d3 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 29 Apr 2025 16:12:34 +0900 Subject: [PATCH 280/450] refactor(asset-ecs): replace raw pointers with unique_ptr for asset data management --- editor/main.cpp | 1 - editor/src/ImNexo/Panels.cpp | 4 +- engine/src/assets/Asset.hpp | 89 +++++++------------ engine/src/assets/AssetCatalog.cpp | 4 +- engine/src/assets/AssetCatalog.hpp | 38 ++++---- engine/src/assets/AssetImporter.cpp | 4 +- engine/src/assets/AssetImporterContext.cpp | 12 ++- engine/src/assets/AssetImporterContext.hpp | 14 ++- .../src/assets/Assets/Model/ModelImporter.cpp | 19 ++-- .../src/assets/Assets/Model/ModelImporter.hpp | 3 +- engine/src/assets/Assets/Texture/Texture.hpp | 24 ++--- .../assets/Assets/Texture/TextureImporter.cpp | 9 +- engine/src/components/Shapes3D.hpp | 24 ++--- tests/engine/assets/AssetCatalog.test.cpp | 52 +++++------ tests/engine/assets/AssetImporter.test.cpp | 10 +-- .../assets/AssetImporterContext.test.cpp | 29 +++--- .../Assets/Model/ModelImporter.test.cpp | 6 +- 17 files changed, 161 insertions(+), 181 deletions(-) diff --git a/editor/main.cpp b/editor/main.cpp index 923662908..1f119adb9 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -56,6 +56,5 @@ try { return 0; } catch (const nexo::Exception &e) { LOG_EXCEPTION(e); - tinyfd_messageBox("NEXO had a fatal error", e.what(), "ok", "error", 1); return 1; } diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index 00dfd06c9..d49f97d39 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -67,7 +67,7 @@ namespace ImNexo { static ImGuiColorEditFlags colorPickerModeAlbedo = ImGuiColorEditFlags_PickerHueBar; static bool showColorPickerAlbedo = false; const auto asset = material->albedoTexture.lock(); - auto albedoTexture = asset && asset->isLoaded() ? asset->data->texture : nullptr; + auto albedoTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; std::filesystem::path newTexturePath; if (TextureButton("Albedo texture", albedoTexture, newTexturePath) @@ -89,7 +89,7 @@ namespace ImNexo { static ImGuiColorEditFlags colorPickerModeSpecular = ImGuiColorEditFlags_PickerHueBar; static bool showColorPickerSpecular = false; const auto asset = material->metallicMap.lock(); - auto metallicTexture = asset && asset->isLoaded() ? asset->data->texture : nullptr; + auto metallicTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; std::filesystem::path newTexturePath; if (TextureButton("Specular texture", metallicTexture, newTexturePath) diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index 874f377b6..60fc4de1b 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -155,19 +155,7 @@ namespace nexo::assets { [[nodiscard]] virtual bool isLoaded() const = 0; [[nodiscard]] virtual bool isErrored() const = 0; - - /** - * @brief Get the asset data pointer - * @return Raw pointer to the asset data - */ - [[nodiscard]] virtual void* getRawData() const = 0; - - /** - * @brief Set the asset data pointer - * @param rawData Raw pointer to the asset data - * @note This transfers ownership of the data to the asset, which will delete it in its destructor - */ - virtual IAsset& setRawData(void* rawData) = 0; + protected: explicit IAsset() : m_metadata({ @@ -193,21 +181,13 @@ namespace nexo::assets { class Asset : public IAsset { friend class AssetCatalog; friend class AssetRef; + public: + // SFINAE definitions using AssetDataType = TAssetData; static constexpr AssetType TYPE = TAssetType; - - /** - * @brief Destructor that releases the allocated asset data. - * - * Deletes the dynamically allocated asset data to ensure proper memory cleanup when the asset is destroyed. - */ - virtual ~Asset() override - { - delete data; - } - - TAssetData *data; + + ~Asset() override = default; [[nodiscard]] const AssetMetadata& getMetadata() const override { return m_metadata; } [[nodiscard]] AssetType getType() const override { return getMetadata().type; } @@ -216,38 +196,10 @@ namespace nexo::assets { [[nodiscard]] bool isLoaded() const override { return getStatus() == AssetStatus::LOADED; } [[nodiscard]] bool isErrored() const override { return getStatus() == AssetStatus::ERROR; } - - // Implementation of IAsset virtual methods - [[nodiscard]] void* getRawData() const override { - return data; - } - - IAsset& setRawData(void* rawData) override { - delete data; // Clean up existing data - if (rawData == nullptr) { - m_metadata.status = AssetStatus::UNLOADED; - } else { - m_metadata.status = AssetStatus::LOADED; - } - data = static_cast(rawData); - return *this; - } - - [[nodiscard]] TAssetData* getData() const { - return data; - } - Asset& setData(TAssetData* newData) { - delete data; - if (newData == nullptr) { - m_metadata.status = AssetStatus::UNLOADED; - } else { - m_metadata.status = AssetStatus::LOADED; - } - data = newData; - return *this; - } - + [[nodiscard]] const std::unique_ptr& getData() const { return data; } + Asset& setData(std::unique_ptr newData); + protected: explicit Asset() : data(nullptr) { @@ -259,10 +211,35 @@ namespace nexo::assets { m_metadata.type = TAssetType; m_metadata.status = AssetStatus::LOADED; } + + std::unique_ptr data; 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; + + + template + Asset& Asset::setData(std::unique_ptr newData) + { + data.reset(); // Clean up existing data + if (newData == nullptr) { + m_metadata.status = AssetStatus::UNLOADED; + } else { + m_metadata.status = AssetStatus::LOADED; + } + data = std::move(newData); + return *this; + } } // namespace nexo::editor diff --git a/engine/src/assets/AssetCatalog.cpp b/engine/src/assets/AssetCatalog.cpp index 62b861e6d..402dfea73 100644 --- a/engine/src/assets/AssetCatalog.cpp +++ b/engine/src/assets/AssetCatalog.cpp @@ -58,12 +58,12 @@ namespace nexo::assets { return assets; } - GenericAssetRef AssetCatalog::registerAsset(const AssetLocation& location, IAsset* asset) + GenericAssetRef AssetCatalog::registerAsset(const AssetLocation& location, std::unique_ptr asset) { if (!asset) return GenericAssetRef::null(); // TODO: implement error handling if already exists (once we have the folder tree) - auto shared_ptr = std::shared_ptr(asset); + std::shared_ptr shared_ptr = std::move(asset); shared_ptr->m_metadata.location = location; if (shared_ptr->m_metadata.id.is_nil()) shared_ptr->m_metadata.id = boost::uuids::random_generator()(); diff --git a/engine/src/assets/AssetCatalog.hpp b/engine/src/assets/AssetCatalog.hpp index 996b2d45c..e3c83b834 100644 --- a/engine/src/assets/AssetCatalog.hpp +++ b/engine/src/assets/AssetCatalog.hpp @@ -15,14 +15,11 @@ #pragma once #include +#include +#include "Asset.hpp" #include "AssetImporter.hpp" #include "AssetLocation.hpp" -#include "Asset.hpp" - -#include -#include - #include "Assets/Texture/Texture.hpp" namespace nexo::assets { @@ -126,8 +123,7 @@ namespace nexo::assets { * @tparam AssetType The type of asset to get. (e.g. Model, Texture) * @return A vector of all assets of the specified type in the catalog. */ - template - requires std::derived_from + template [[nodiscard]] std::vector> getAssetsOfType() const; /** @@ -135,8 +131,7 @@ namespace nexo::assets { * @tparam AssetType The type of asset to get. (e.g. Model, Texture) * @return A view of all assets of the specified type in the catalog. */ - template - requires std::derived_from + template [[nodiscard]] decltype(auto) getAssetsOfTypeView() const; /** @@ -153,7 +148,7 @@ namespace nexo::assets { * @param asset Pointer to the asset to be registered. * @return GenericAssetRef A reference to the registered asset, or a null reference if the asset pointer was null. */ - GenericAssetRef registerAsset(const AssetLocation& location, IAsset *asset); + GenericAssetRef registerAsset(const AssetLocation& location, std::unique_ptr asset); /** * @brief Creates and registers a new asset in the catalog. @@ -165,22 +160,20 @@ namespace nexo::assets { * @param args Constructor arguments for the asset. * @return AssetRef A reference to the created and registered asset. */ - template - requires std::derived_from + template AssetRef createAsset(const AssetLocation& location, Args&& ...args) { - auto asset = new AssetType(std::forward(args)...); - auto assetRef = registerAsset(location, asset); + auto asset = std::make_unique(std::forward(args)...); + auto assetRef = registerAsset(location, std::move(asset)); return assetRef.template as(); } - template - requires std::derived_from - AssetRef createAsset(const AssetLocation& location, typename AssetType::AssetDataType* assetData) + template + AssetRef createAsset(const AssetLocation& location, std::unique_ptr assetData) { - auto asset = new AssetType(); - asset->setData(assetData); - auto assetRef = registerAsset(location, asset); + auto asset = std::make_unique(); + asset->setData(std::move(assetData)); + auto assetRef = registerAsset(location, std::move(asset)); return assetRef.template as(); } @@ -188,8 +181,7 @@ namespace nexo::assets { std::unordered_map> m_assets; }; - template - requires std::derived_from + template std::vector> AssetCatalog::getAssetsOfType() const { // TODO: AssetType::TYPE is not a thing, need to find a way to get the type of the asset @@ -202,7 +194,7 @@ namespace nexo::assets { return assets; } - template requires std::derived_from + template decltype(auto) AssetCatalog::getAssetsOfTypeView() const { // TODO: AssetType::TYPE is not a thing, need to find a way to get the type of the asset diff --git a/engine/src/assets/AssetImporter.cpp b/engine/src/assets/AssetImporter.cpp index c05fb4840..a6b4d05e8 100644 --- a/engine/src/assets/AssetImporter.cpp +++ b/engine/src/assets/AssetImporter.cpp @@ -61,7 +61,7 @@ namespace nexo::assets { importer->import(*ctx); - const auto asset = ctx->getMainAsset(); + auto asset = ctx->releaseMainAsset(); if (!asset) return GenericAssetRef::null(); if (asset->getID().is_nil()) @@ -69,7 +69,7 @@ namespace nexo::assets { if (asset->m_metadata.location == AssetLocation("default")) asset->m_metadata.location = location; - return AssetCatalog::getInstance().registerAsset(location, asset); + return AssetCatalog::getInstance().registerAsset(location, std::move(asset)); } GenericAssetRef AssetImporter::importAssetTryImporters(const AssetLocation& location, diff --git a/engine/src/assets/AssetImporterContext.cpp b/engine/src/assets/AssetImporterContext.cpp index 7cfce359e..aa1c6581a 100644 --- a/engine/src/assets/AssetImporterContext.cpp +++ b/engine/src/assets/AssetImporterContext.cpp @@ -16,16 +16,22 @@ namespace nexo::assets { - void AssetImporterContext::setMainAsset(IAsset* asset) + void AssetImporterContext::setMainAsset(std::unique_ptr asset) { - m_mainAsset = asset; + m_mainAsset = std::move(asset); } - IAsset* AssetImporterContext::getMainAsset() const + const std::unique_ptr& AssetImporterContext::getMainAsset() const { return m_mainAsset; } + std::unique_ptr AssetImporterContext::releaseMainAsset() + { + 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 a2ce7683f..045e9f97b 100644 --- a/engine/src/assets/AssetImporterContext.hpp +++ b/engine/src/assets/AssetImporterContext.hpp @@ -48,13 +48,21 @@ namespace nexo::assets { * @param asset The main asset * @note This method must be called by the importer to set the main asset data */ - void setMainAsset(IAsset* asset); + void setMainAsset(std::unique_ptr asset); /** * @brief Get the main asset data for this context * @return The main asset data */ - [[nodiscard]] IAsset* getMainAsset() const; + [[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. @@ -117,7 +125,7 @@ namespace nexo::assets { } private: - IAsset *m_mainAsset = nullptr; //< Main asset being imported, resulting asset (MUST be set by importer) + 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 diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index 9c95f6e71..c81efcd0c 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -31,7 +31,7 @@ #include "core/exceptions/Exceptions.hpp" namespace nexo::assets { - + bool ModelImporter::canRead(const ImporterInputVariant& inputVariant) { std::string extension; @@ -47,15 +47,14 @@ namespace nexo::assets { void ModelImporter::importImpl(AssetImporterContext& ctx) { - m_model = loadModel(ctx); - ctx.setMainAsset(m_model); + std::unique_ptr model = loadModel(ctx); + ctx.setMainAsset(std::move(model)); } - Model* ModelImporter::loadModel(AssetImporterContext& ctx) + std::unique_ptr ModelImporter::loadModel(AssetImporterContext& ctx) { - Model* model = new Model(); + auto model = std::make_unique(); - //m_model->setData(new components::Model()); const auto param = ctx.getParameters(); int flags = aiProcess_Triangulate | aiProcess_GenNormals; @@ -68,7 +67,6 @@ namespace nexo::assets { } if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { //log error TODO: improve error handling in importers - delete model; if (scene) m_importer.FreeScene(); throw core::LoadModelException(ctx.location.getFullLocation(), m_importer.GetErrorString()); @@ -79,10 +77,9 @@ namespace nexo::assets { auto meshNode = processNode(ctx, scene->mRootNode, scene); if (!meshNode) { - delete model; throw core::LoadModelException(ctx.location.getFullLocation(), "Failed to process model node"); } - model->setData(new components::Model(meshNode)); + model->setData(std::make_unique(meshNode)); return model; } @@ -223,7 +220,7 @@ namespace nexo::assets { for (int matIdx = 0; matIdx < scene->mNumMaterials; ++matIdx) { aiMaterial const *material = scene->mMaterials[matIdx]; - const auto materialComponent = new components::Material(); + auto materialComponent = std::make_unique(); aiColor4D color; if (material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { @@ -300,7 +297,7 @@ namespace nexo::assets { auto materialRef = AssetCatalog::getInstance().createAsset( ctx.genUniqueDependencyLocation(), - materialComponent + std::move(materialComponent) ); m_materials[matIdx] = materialRef; } // end for (int matIdx = 0; matIdx < scene->mNumMaterials; ++matIdx) diff --git a/engine/src/assets/Assets/Model/ModelImporter.hpp b/engine/src/assets/Assets/Model/ModelImporter.hpp index 37d79ce35..f42c839f5 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.hpp +++ b/engine/src/assets/Assets/Model/ModelImporter.hpp @@ -34,7 +34,7 @@ namespace nexo::assets { void importImpl(AssetImporterContext& ctx) override; protected: - Model* loadModel(AssetImporterContext& ctx); + 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); @@ -45,7 +45,6 @@ namespace nexo::assets { static renderer::NxTextureFormat convertAssimpHintToNxTextureFormat(const char achFormatHint[9]); static glm::mat4 convertAssimpMatrixToGLM(const aiMatrix4x4& matrix); - Model *m_model = nullptr; //< Model being imported 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 diff --git a/engine/src/assets/Assets/Texture/Texture.hpp b/engine/src/assets/Assets/Texture/Texture.hpp index 71df91c45..fd8e67245 100644 --- a/engine/src/assets/Assets/Texture/Texture.hpp +++ b/engine/src/assets/Assets/Texture/Texture.hpp @@ -54,10 +54,10 @@ namespace nexo::assets { explicit Texture(const std::filesystem::path &path) : Asset() { - auto texture = renderer::NxTexture2D::create(path.string()); - auto textureData = new TextureData(); + const auto texture = renderer::NxTexture2D::create(path.string()); + auto textureData = std::make_unique(); textureData->texture = texture; - setData(textureData); + setData(std::move(textureData)); } /** @@ -76,10 +76,10 @@ namespace nexo::assets { Texture(unsigned int width, unsigned int height) : Asset() { - auto texture = renderer::NxTexture2D::create(width, height); - auto textureData = new TextureData(); + const auto texture = renderer::NxTexture2D::create(width, height); + auto textureData = std::make_unique(); textureData->texture = texture; - setData(textureData); + setData(std::move(textureData)); } /** @@ -106,10 +106,10 @@ namespace nexo::assets { Texture(const uint8_t *buffer, const unsigned int width, const unsigned int height, const renderer::NxTextureFormat format) : Asset() { - auto texture = renderer::NxTexture2D::create(buffer, width, height, format); - auto textureData = new TextureData(); + const auto texture = renderer::NxTexture2D::create(buffer, width, height, format); + auto textureData = std::make_unique(); textureData->texture = texture; - setData(textureData); + setData(std::move(textureData)); } /** @@ -128,10 +128,10 @@ namespace nexo::assets { Texture(const uint8_t* buffer, const unsigned int size) : Asset() { - auto texture = renderer::NxTexture2D::create(buffer, size); - auto textureData = new TextureData(); + const auto texture = renderer::NxTexture2D::create(buffer, size); + auto textureData = std::unique_ptr(); textureData->texture = texture; - setData(textureData); + setData(std::move(textureData)); } ~Texture() override = default; diff --git a/engine/src/assets/Assets/Texture/TextureImporter.cpp b/engine/src/assets/Assets/Texture/TextureImporter.cpp index 074d57a7a..ef855522d 100644 --- a/engine/src/assets/Assets/Texture/TextureImporter.cpp +++ b/engine/src/assets/Assets/Texture/TextureImporter.cpp @@ -35,7 +35,7 @@ namespace nexo::assets { void TextureImporter::importImpl(AssetImporterContext& ctx) { // TODO: we need to import textures independently from graphics API back end renderer::NxTexture2D::create implementation - auto asset = new Texture(); + auto asset = std::make_unique(); std::shared_ptr rendererTexture; if (std::holds_alternative(ctx.input)) rendererTexture = renderer::NxTexture2D::create(std::get(ctx.input).filePath.string()); @@ -43,12 +43,13 @@ namespace nexo::assets { auto data = std::get(ctx.input).memoryData; rendererTexture = renderer::NxTexture2D::create(data.data(), data.size()); } - auto assetData = new TextureData(); + auto assetData = std::make_unique(); assetData->texture = rendererTexture; - asset->setData(assetData); + asset->m_metadata.id = boost::uuids::random_generator()(); - ctx.setMainAsset(asset); + asset->setData(std::move(assetData)); + ctx.setMainAsset(std::move(asset)); } bool TextureImporter::canReadMemory(const ImporterMemoryInput& input) diff --git a/engine/src/components/Shapes3D.hpp b/engine/src/components/Shapes3D.hpp index 216ab576b..fd185f597 100644 --- a/engine/src/components/Shapes3D.hpp +++ b/engine/src/components/Shapes3D.hpp @@ -52,11 +52,11 @@ namespace nexo::components { .roughness = material.roughness, .metallic = material.metallic, .opacity = material.opacity, - .albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->data->texture : nullptr, - .normalMap = normalMapAsset && normalMapAsset->isLoaded() ? normalMapAsset->data->texture : nullptr, - .metallicMap = metallicMapAsset && metallicMapAsset->isLoaded() ? metallicMapAsset->data->texture : nullptr, - .roughnessMap = roughnessMapAsset && roughnessMapAsset->isLoaded() ? roughnessMapAsset->data->texture : nullptr, - .emissiveMap = emissiveMapAsset && emissiveMapAsset->isLoaded() ? emissiveMapAsset->data->texture : nullptr, + .albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr, + .normalMap = normalMapAsset && normalMapAsset->isLoaded() ? normalMapAsset->getData()->texture : nullptr, + .metallicMap = metallicMapAsset && metallicMapAsset->isLoaded() ? metallicMapAsset->getData()->texture : nullptr, + .roughnessMap = roughnessMapAsset && roughnessMapAsset->isLoaded() ? roughnessMapAsset->getData()->texture : nullptr, + .emissiveMap = emissiveMapAsset && emissiveMapAsset->isLoaded() ? emissiveMapAsset->getData()->texture : nullptr, .shader = material.shader }; @@ -92,9 +92,9 @@ namespace nexo::components { { const auto meshMaterialAsset = mesh.material.lock(); - const auto albedoTextureAsset = meshMaterialAsset && meshMaterialAsset->isLoaded() ? meshMaterialAsset->data->albedoTexture.lock() : nullptr; + const auto albedoTextureAsset = meshMaterialAsset && meshMaterialAsset->isLoaded() ? meshMaterialAsset->getData()->albedoTexture.lock() : nullptr; - const auto albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->data->texture : nullptr; + const auto albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr; renderer3D.drawMesh(transformedVertices, mesh.indices, albedoTexture, entityID); } } @@ -164,11 +164,11 @@ namespace nexo::components { .roughness = material.roughness, .metallic = material.metallic, .opacity = material.opacity, - .albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->data->texture : nullptr, - .normalMap = normalMapAsset && normalMapAsset->isLoaded() ? normalMapAsset->data->texture : nullptr, - .metallicMap = metallicMapAsset && metallicMapAsset->isLoaded() ? metallicMapAsset->data->texture : nullptr, - .roughnessMap = roughnessMapAsset && roughnessMapAsset->isLoaded() ? roughnessMapAsset->data->texture : nullptr, - .emissiveMap = emissiveMapAsset && emissiveMapAsset->isLoaded() ? emissiveMapAsset->data->texture : nullptr, + .albedoTexture = albedoTextureAsset && albedoTextureAsset->isLoaded() ? albedoTextureAsset->getData()->texture : nullptr, + .normalMap = normalMapAsset && normalMapAsset->isLoaded() ? normalMapAsset->getData()->texture : nullptr, + .metallicMap = metallicMapAsset && metallicMapAsset->isLoaded() ? metallicMapAsset->getData()->texture : nullptr, + .roughnessMap = roughnessMapAsset && roughnessMapAsset->isLoaded() ? roughnessMapAsset->getData()->texture : nullptr, + .emissiveMap = emissiveMapAsset && emissiveMapAsset->isLoaded() ? emissiveMapAsset->getData()->texture : nullptr, .shader = material.shader }; diff --git a/tests/engine/assets/AssetCatalog.test.cpp b/tests/engine/assets/AssetCatalog.test.cpp index 51df76063..e0a6816fb 100644 --- a/tests/engine/assets/AssetCatalog.test.cpp +++ b/tests/engine/assets/AssetCatalog.test.cpp @@ -48,8 +48,8 @@ namespace nexo::assets { TEST_F(AssetCatalogTest, RegisterAndRetrieveAssetById) { // Register an asset const AssetLocation location("text@test/texture"); - const auto textureAsset = new Texture(); - const auto ref = assetCatalog.registerAsset(location, textureAsset); + auto textureAsset = std::make_unique(); + const auto ref = assetCatalog.registerAsset(location, std::move(textureAsset)); ASSERT_TRUE(ref.isValid()); const auto id = ref.lock()->getID(); @@ -64,8 +64,8 @@ namespace nexo::assets { TEST_F(AssetCatalogTest, RegisterAndRetrieveAssetByLocation) { // Register an asset const AssetLocation location("text@test/texture"); - const auto textureAsset = new Texture(); - const auto ref = assetCatalog.registerAsset(location, textureAsset); + auto textureAsset = std::make_unique(); + const auto ref = assetCatalog.registerAsset(location, std::move(textureAsset)); ASSERT_TRUE(ref.isValid()); ASSERT_TRUE(ref); @@ -79,9 +79,9 @@ namespace nexo::assets { TEST_F(AssetCatalogTest, DeleteAssetById) { AssetLocation location("text@test/texture"); - const auto textureAsset = new Texture(); - const auto ref = assetCatalog.registerAsset(location, textureAsset); - const auto id = ref.lock()->getID(); + auto textureAsset = std::make_unique(); + const auto ref = assetCatalog.registerAsset(location, std::move(textureAsset)); + const auto id = ref.lock()->getID(); // Delete by ID assetCatalog.deleteAsset(id); @@ -99,8 +99,8 @@ namespace nexo::assets { TEST_F(AssetCatalogTest, DeleteAssetByReference) { AssetLocation location("text@test/texture"); - const auto textureAsset = new Texture(); - const auto ref = assetCatalog.registerAsset(location, textureAsset); + auto textureAsset = std::make_unique(); + const auto ref = assetCatalog.registerAsset(location, std::move(textureAsset)); // Delete by reference assetCatalog.deleteAsset(ref); @@ -117,12 +117,12 @@ namespace nexo::assets { } TEST_F(AssetCatalogTest, GetAssetsReturnsAllAssets) { - const auto textureAsset = new Texture(); - const auto modelAsset = new Model(); + auto textureAsset = std::make_unique(); + auto modelAsset = std::make_unique(); // Register multiple assets - assetCatalog.registerAsset(AssetLocation("text@test/texture"), textureAsset); - assetCatalog.registerAsset(AssetLocation("model@test/model"), modelAsset); + assetCatalog.registerAsset(AssetLocation("text@test/texture"), std::move(textureAsset)); + assetCatalog.registerAsset(AssetLocation("model@test/model"), std::move(modelAsset)); // Get all assets auto assets = assetCatalog.getAssets(); @@ -137,12 +137,12 @@ namespace nexo::assets { } TEST_F(AssetCatalogTest, GetAssetsReturnsAllAssetsViews) { - const auto textureAsset = new Texture(); - const auto modelAsset = new Model(); + auto textureAsset = std::make_unique(); + auto modelAsset = std::make_unique(); // Register multiple assets - const auto textRef = assetCatalog.registerAsset(AssetLocation("text@test/texture"), textureAsset); - const auto modelRef = assetCatalog.registerAsset(AssetLocation("model@test/model"), modelAsset); + const auto textRef = assetCatalog.registerAsset(AssetLocation("text@test/texture"), std::move(textureAsset)); + const auto modelRef = assetCatalog.registerAsset(AssetLocation("model@test/model"), std::move(modelAsset)); // Get all assets as a view auto assetsView = assetCatalog.getAssetsView(); @@ -165,13 +165,13 @@ namespace nexo::assets { } TEST_F(AssetCatalogTest, MultipleAssetsDeleteOne) { - const auto textureAsset = new Texture(); - const auto modelAsset = new Model(); + auto textureAsset = std::make_unique(); + auto modelAsset = std::make_unique(); // Register multiple assets - const auto textRef = assetCatalog.registerAsset(AssetLocation("text@test/texture"), textureAsset); - const auto modelRef = assetCatalog.registerAsset(AssetLocation("model@test/model"), modelAsset); - const auto modelId = modelRef.lock()->getID(); + const auto textRef = assetCatalog.registerAsset(AssetLocation("text@test/texture"), std::move(textureAsset)); + const auto modelRef = assetCatalog.registerAsset(AssetLocation("model@test/model"), std::move(modelAsset)); + const auto modelId = modelRef.lock()->getID(); // Get all assets auto assets = assetCatalog.getAssets(); @@ -303,8 +303,8 @@ namespace nexo::assets { auto& instance = AssetCatalog::getInstance(); const AssetLocation location("text@test/texture"); - const auto textureAsset = new Texture(); - const auto ref = instance.registerAsset(location, textureAsset); + auto textureAsset = std::make_unique(); + const auto ref = instance.registerAsset(location, std::move(textureAsset)); ASSERT_TRUE(ref.isValid()); const auto id = ref.lock()->getID(); @@ -326,8 +326,8 @@ namespace nexo::assets { auto& instance = AssetCatalog::getInstance(); const AssetLocation location("text@test/texture"); - const auto textureAsset = new Texture(); - const auto ref = instance.registerAsset(location, textureAsset); + auto textureAsset = std::make_unique(); + const auto ref = instance.registerAsset(location, std::move(textureAsset)); ASSERT_TRUE(ref.isValid()); const auto id = ref.lock()->getID(); diff --git a/tests/engine/assets/AssetImporter.test.cpp b/tests/engine/assets/AssetImporter.test.cpp index 41b3fa0c3..7c79bb684 100644 --- a/tests/engine/assets/AssetImporter.test.cpp +++ b/tests/engine/assets/AssetImporter.test.cpp @@ -87,7 +87,7 @@ namespace nexo::assets { EXPECT_CALL(*mockImporter, canRead(testing::_)).Times(0); // Never called EXPECT_CALL(*mockImporter, importImpl(testing::_)) .WillOnce(Invoke([&](AssetImporterContext& ctx) { - ctx.setMainAsset(expectedAsset); + ctx.setMainAsset(std::unique_ptr(expectedAsset)); })); importer.registerImporter(mockImporter, 100); @@ -114,7 +114,7 @@ namespace nexo::assets { EXPECT_CALL(*mockImporter, importImpl(testing::_)) .After(canReadCall) .WillOnce(Invoke([&](AssetImporterContext& ctx) { - ctx.setMainAsset(expectedAsset); + ctx.setMainAsset(std::unique_ptr(expectedAsset)); })); // Register the mock importer @@ -161,7 +161,7 @@ namespace nexo::assets { EXPECT_CALL(*bestImporter, importImpl(testing::_)) .After(canReadCall) .WillOnce(Invoke([&](AssetImporterContext& ctx) { - ctx.setMainAsset(expectedAsset); + ctx.setMainAsset(std::unique_ptr(expectedAsset)); })); EXPECT_CALL(*wrongImporter, canRead(testing::_)).Times(0); @@ -253,7 +253,7 @@ namespace nexo::assets { EXPECT_CALL(*validModelImporter, importImpl(testing::_)) .After(validCanReadCall) .WillOnce(Invoke([](AssetImporterContext& ctx) { - ctx.setMainAsset(new Model()); + ctx.setMainAsset(std::make_unique()); })); EXPECT_CALL(*cannotReadModelImporter, importImpl(testing::_)).Times(0); @@ -298,7 +298,7 @@ namespace nexo::assets { EXPECT_CALL(*bestImporter, importImpl(testing::_)) .After(wrongCanReadCall) .WillOnce(Invoke([&](AssetImporterContext& ctx) { - ctx.setMainAsset(expectedAsset); + ctx.setMainAsset(std::unique_ptr(expectedAsset)); })); EXPECT_CALL(*wrongImporter, importImpl(testing::_)).Times(0); diff --git a/tests/engine/assets/AssetImporterContext.test.cpp b/tests/engine/assets/AssetImporterContext.test.cpp index da2d80111..36b3cc186 100644 --- a/tests/engine/assets/AssetImporterContext.test.cpp +++ b/tests/engine/assets/AssetImporterContext.test.cpp @@ -64,9 +64,10 @@ namespace nexo::assets { TEST_F(AssetImporterContextTest, SetAndGetMainAsset) { - Texture asset; - context.setMainAsset(&asset); - EXPECT_EQ(context.getMainAsset(), &asset); + auto asset = std::make_unique(); + Texture* texturePtr = asset.get(); + context.setMainAsset(std::move(asset)); + EXPECT_EQ(context.getMainAsset().get(), texturePtr); } TEST_F(AssetImporterContextTest, GetDependenciesEmptyOnCreation) @@ -78,8 +79,8 @@ namespace nexo::assets { { // Register an asset first to get a valid reference auto& catalog = AssetCatalog::getInstance(); - auto* asset = new Texture(); - const auto ref = catalog.registerAsset(AssetLocation("test@texture/dependency"), asset); + auto asset = std::make_unique(); + const auto ref = catalog.registerAsset(AssetLocation("test@texture/dependency"), std::move(asset)); EXPECT_TRUE(ref); // Add as dependency @@ -96,10 +97,10 @@ namespace nexo::assets { auto& catalog = AssetCatalog::getInstance(); // Create and register multiple assets - auto* texture = new Texture(); - auto* model = new Model(); - const auto textureRef = catalog.registerAsset(AssetLocation("text@path"), texture); - const auto modelRef = catalog.registerAsset(AssetLocation("model@path"), model); + auto* texture = new Texture(); + auto* model = new Model(); + const auto textureRef = catalog.registerAsset(AssetLocation("text@path"), std::unique_ptr(texture)); + const auto modelRef = catalog.registerAsset(AssetLocation("model@path"), std::unique_ptr(model)); EXPECT_TRUE(textureRef); EXPECT_TRUE(modelRef); @@ -173,7 +174,7 @@ namespace nexo::assets { // Register an asset with that name auto& catalog = AssetCatalog::getInstance(); auto* asset = new Texture(); - EXPECT_TRUE(catalog.registerAsset(depName1, asset)); + EXPECT_TRUE(catalog.registerAsset(depName1, std::unique_ptr(asset))); // Generate another name - should be different const auto depName2 = context.genUniqueDependencyLocation(); @@ -193,13 +194,13 @@ namespace nexo::assets { // Register an asset with that name auto& catalog = AssetCatalog::getInstance(); - auto* asset = new Model(); - EXPECT_TRUE(catalog.registerAsset(depName1, asset)); + auto asset = std::make_unique(); + EXPECT_TRUE(catalog.registerAsset(depName1, std::move(asset))); EXPECT_EQ(depName1.getFullLocation(), "test_MODEL1@folder/main"); // Let's register an asset with the same name as a future dependency - auto* asset2 = new Model(); - EXPECT_TRUE(catalog.registerAsset(AssetLocation("test_MODEL2@folder/main"), asset2)); + auto asset2 = std::make_unique(); + EXPECT_TRUE(catalog.registerAsset(AssetLocation("test_MODEL2@folder/main"), std::move(asset2))); // Generate another dep name: should prevent collision const auto depName2 = context.genUniqueDependencyLocation(); diff --git a/tests/engine/assets/Assets/Model/ModelImporter.test.cpp b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp index 46787461f..67ee0ca33 100644 --- a/tests/engine/assets/Assets/Model/ModelImporter.test.cpp +++ b/tests/engine/assets/Assets/Model/ModelImporter.test.cpp @@ -184,7 +184,7 @@ TEST_F(ModelImporterTestFixture, ImportCubeModel) { const auto allAssets = catalog.getAssets(); EXPECT_EQ(allAssets.size(), 3); // 2 materials + 1 texture - auto model = static_cast(ctx.getMainAsset()); + auto model = static_cast(ctx.getMainAsset().get()); // Now verify the model data const auto& modelData = model->getData(); ASSERT_NE(modelData, nullptr); @@ -210,7 +210,7 @@ TEST_F(ModelImporterTestFixture, ImportCubeModel) { const auto material = childMesh.material.lock(); EXPECT_NE(material, nullptr); - const auto materialData = material->getData(); + const auto materialData = material->getData().get(); EXPECT_NE(materialData, nullptr); EXPECT_NE(materialData->albedoTexture, nullptr); @@ -218,7 +218,7 @@ TEST_F(ModelImporterTestFixture, ImportCubeModel) { EXPECT_NE(albedoTextureAsset, nullptr); EXPECT_EQ(albedoTextureAsset->getType(), Texture::TYPE); - const auto albedoTexture = albedoTextureAsset->getData(); + const auto albedoTexture = albedoTextureAsset->getData().get(); EXPECT_NE(albedoTexture->texture, nullptr); EXPECT_EQ(albedoTexture->texture->getWidth(), 64); EXPECT_EQ(albedoTexture->texture->getHeight(), 64); From 279ed32a4f4e2e4a0d4bf0488bd4ae890ffa2afb Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 29 Apr 2025 16:18:04 +0900 Subject: [PATCH 281/450] feat(asset-ecs): bump vcpkg submodule and baseline + bump tinyfd --- vcpkg.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vcpkg.json b/vcpkg.json index 3b856270b..352d4e9ec 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,7 +3,7 @@ "version-string": "0.1.0", "description": "Nexo Engine", "documentation": "https://nexoengine.github.io/game-engine/", - "builtin-baseline": "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c", + "builtin-baseline": "96d5fb3de135b86d7222c53f2352ca92827a156b", "dependencies": [ { "name": "imgui", @@ -23,7 +23,7 @@ { "name": "glfw3", "version>=": "3.4#1" }, { "name": "glad", "version>=": "0.1.36#0" }, { "name": "stb", "version>=": "2024-07-29#1" }, - { "name": "tinyfiledialogs", "version>=": "3.8.8#4" }, + { "name": "tinyfiledialogs", "version>=": "3.19.1#0" }, { "name": "gtest", "version>=": "1.15.2#0" }, { "name": "assimp", "version>=": "5.4.3#0" }, {"name": "nlohmann-json", "version>=": "3.11.3#1"} From df8242b123146216ef75b3f6cfa00e7968e17fbc Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 29 Apr 2025 17:01:10 +0900 Subject: [PATCH 282/450] feat(asset-ecs): change factory for Models to take AssetRef --- engine/src/EntityFactory3D.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/src/EntityFactory3D.hpp b/engine/src/EntityFactory3D.hpp index eb7ac3cbf..9a9fe6a70 100644 --- a/engine/src/EntityFactory3D.hpp +++ b/engine/src/EntityFactory3D.hpp @@ -18,6 +18,7 @@ #include #include +#include "assets/Assets/Model/Model.hpp" #include "ecs/ECSExceptions.hpp" #include "components/Components.hpp" #include "renderer/Framebuffer.hpp" @@ -60,7 +61,7 @@ namespace nexo */ static ecs::Entity createCube(glm::vec3 pos, glm::vec3 size, glm::vec3 rotation, const components::Material &material); - static ecs::Entity createModel(const std::string& path, glm::vec3 pos, glm::vec3 size, glm::vec3 rotation); + static ecs::Entity createModel(assets::AssetRef modelAsset, glm::vec3 pos, glm::vec3 size, glm::vec3 rotation); static ecs::Entity createBillboard(const glm::vec3 &pos, const glm::vec3 &size, const glm::vec4 &color); static ecs::Entity createBillboard(const glm::vec3 &pos, const glm::vec3 &size, const components::Material &material); From 3f68fa1cb48f457fc41a8a0784d29bdef8d9f230 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 30 Apr 2025 15:30:38 +0900 Subject: [PATCH 283/450] fix(asset-ecs): renderer names from rebase --- engine/src/components/Camera.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 47b6fdcaf..d06a4a5a8 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -117,7 +117,7 @@ namespace nexo::components { bool render; bool main; bool resizing; - std::shared_ptr renderTarget; + std::shared_ptr renderTarget; CameraComponent restore() const { From bf485337fa1b28d15963424c27bc2e4896d2f99a Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 30 Apr 2025 18:32:01 +0900 Subject: [PATCH 284/450] fix(asset-ecs): reapply changes in DocumentWindows after rebase Files were split on the base branch, so changes done in asset-ecs needed to be reapplied to the split files one by one. --- editor/src/DocumentWindows/AssetManager/Init.cpp | 4 ++-- editor/src/DocumentWindows/ConsoleWindow/Init.cpp | 8 +++++--- editor/src/DocumentWindows/EditorScene/Init.cpp | 6 +++--- editor/src/DocumentWindows/EditorScene/Show.cpp | 2 +- editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp | 5 ++--- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp index 38e0611ed..2b53aa736 100644 --- a/editor/src/DocumentWindows/AssetManager/Init.cpp +++ b/editor/src/DocumentWindows/AssetManager/Init.cpp @@ -22,8 +22,8 @@ namespace nexo::editor { void AssetManagerWindow::setup() { auto& catalog = assets::AssetCatalog::getInstance(); - const auto asset = new assets::Model(); - catalog.registerAsset(assets::AssetLocation("my_package::My_Model@foo/bar/"), asset); + auto asset = std::make_unique(); + catalog.registerAsset(assets::AssetLocation("my_package::My_Model@foo/bar/"), std::move(asset)); { assets::AssetImporter importer; diff --git a/editor/src/DocumentWindows/ConsoleWindow/Init.cpp b/editor/src/DocumentWindows/ConsoleWindow/Init.cpp index acc11aa19..f1aec186e 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Init.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Init.cpp @@ -33,9 +33,11 @@ namespace nexo::editor { loguru::add_callback(LOGURU_CALLBACK_NAME, &ConsoleWindow::loguruCallback, this, loguru::Verbosity_MAX); - auto engineLogCallback = [](const LogLevel level, const std::string &message) { - const auto loguruLevel = nexoLevelToLoguruLevel(level); - VLOG_F(loguruLevel, "%s", message.c_str()); + 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); m_logFilePath = Path::resolvePathRelativeToExe(generateLogFilePath()).string(); diff --git a/editor/src/DocumentWindows/EditorScene/Init.cpp b/editor/src/DocumentWindows/EditorScene/Init.cpp index 245c0e99d..2e08fbae2 100644 --- a/editor/src/DocumentWindows/EditorScene/Init.cpp +++ b/editor/src/DocumentWindows/EditorScene/Init.cpp @@ -32,13 +32,13 @@ namespace nexo::editor { auto &app = getApp(); m_sceneId = static_cast(app.getSceneManager().createScene(m_windowName)); - renderer::FramebufferSpecs framebufferSpecs; + renderer::NxFramebufferSpecs framebufferSpecs; framebufferSpecs.attachments = { - renderer::FrameBufferTextureFormats::RGBA8, renderer::FrameBufferTextureFormats::RED_INTEGER, renderer::FrameBufferTextureFormats::Depth + 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::Framebuffer::create(framebufferSpecs); + const auto renderTarget = renderer::NxFramebuffer::create(framebufferSpecs); m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast(m_contentSize.x), static_cast(m_contentSize.y), renderTarget); auto &cameraComponent = app.m_coordinator->getComponent(m_editorCamera); cameraComponent.render = true; diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index 4e40cdbc4..796536662 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -100,7 +100,7 @@ namespace nexo::editor { // Render framebuffer const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); - ImGui::Image(static_cast(static_cast(textureId)), m_contentSize, ImVec2(0, 1), ImVec2(1, 0)); + ImNexo::Image(static_cast(static_cast(textureId)), m_contentSize); ImVec2 viewportMin = ImGui::GetItemRectMin(); ImVec2 viewportMax = ImGui::GetItemRectMax(); diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp index 86e02b329..2335d54ea 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp @@ -28,10 +28,9 @@ namespace nexo::editor { cameraComponent.render = true; const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); - ImGui::Image( + ImNexo::Image( static_cast(static_cast(textureId)), - ImVec2(previewSize, previewSize), - ImVec2(0, 1), ImVec2(1, 0) // Flip Y coordinates for OpenGL texture + ImVec2(previewSize, previewSize) ); ImGui::EndTooltip(); From f399a91978ef845dd06f9006643f55dc4c24335b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Wed, 30 Apr 2025 18:56:47 +0900 Subject: [PATCH 285/450] fix(asset-ecs): missing include --- editor/src/DocumentWindows/MaterialInspector/Show.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/DocumentWindows/MaterialInspector/Show.cpp b/editor/src/DocumentWindows/MaterialInspector/Show.cpp index 779665949..2e80ed43b 100644 --- a/editor/src/DocumentWindows/MaterialInspector/Show.cpp +++ b/editor/src/DocumentWindows/MaterialInspector/Show.cpp @@ -15,6 +15,7 @@ #include "MaterialInspector.hpp" #include "utils/ScenePreview.hpp" #include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" +#include "ImNexo/Elements.hpp" #include "ImNexo/Panels.hpp" #include "context/Selector.hpp" From 057884b87773dcd8430995ac13803e214adfe05f Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 15 Apr 2025 12:45:53 +0900 Subject: [PATCH 286/450] chore(license): add copyright and license files for various dependencies --- .gitignore | 3 + COPYRIGHT | 188 ++++++++++++++++++ external/licenses/assimp | 78 ++++++++ external/licenses/boost | 23 +++ external/licenses/draco | 252 +++++++++++++++++++++++++ external/licenses/egl-registry | 28 +++ external/licenses/glad | 63 +++++++ external/licenses/glfw3 | 23 +++ external/licenses/glm | 54 ++++++ external/licenses/gtest | 28 +++ external/licenses/imgui | 21 +++ external/licenses/imguizmo | 21 +++ external/licenses/jhasse-poly2tri | 26 +++ external/licenses/joltphysics | 7 + external/licenses/kubazip | 26 +++ external/licenses/loguru | 3 + external/licenses/minizip | 74 ++++++++ external/licenses/nlohmann-json | 21 +++ external/licenses/opengl-registry | 2 + external/licenses/polyclipping | 24 +++ external/licenses/pugixml | 24 +++ external/licenses/rapidjson | 57 ++++++ external/licenses/stb | 37 ++++ external/licenses/tinyfiledialogs | 14 ++ external/licenses/utfcpp | 23 +++ external/licenses/vcpkg-cmake | 20 ++ external/licenses/vcpkg-cmake-config | 23 +++ external/licenses/vcpkg-cmake-get-vars | 20 ++ external/licenses/zlib | 22 +++ scripts/COPYRIGHT.in | 27 +++ scripts/copyright.cmake | 61 ++++++ scripts/install.cmake | 26 +++ 32 files changed, 1319 insertions(+) create mode 100644 COPYRIGHT create mode 100644 external/licenses/assimp create mode 100644 external/licenses/boost create mode 100644 external/licenses/draco create mode 100644 external/licenses/egl-registry create mode 100644 external/licenses/glad create mode 100644 external/licenses/glfw3 create mode 100644 external/licenses/glm create mode 100644 external/licenses/gtest create mode 100644 external/licenses/imgui create mode 100644 external/licenses/imguizmo create mode 100644 external/licenses/jhasse-poly2tri create mode 100644 external/licenses/joltphysics create mode 100644 external/licenses/kubazip create mode 100644 external/licenses/loguru create mode 100644 external/licenses/minizip create mode 100644 external/licenses/nlohmann-json create mode 100644 external/licenses/opengl-registry create mode 100644 external/licenses/polyclipping create mode 100644 external/licenses/pugixml create mode 100644 external/licenses/rapidjson create mode 100644 external/licenses/stb create mode 100644 external/licenses/tinyfiledialogs create mode 100644 external/licenses/utfcpp create mode 100644 external/licenses/vcpkg-cmake create mode 100644 external/licenses/vcpkg-cmake-config create mode 100644 external/licenses/vcpkg-cmake-get-vars create mode 100644 external/licenses/zlib create mode 100644 scripts/COPYRIGHT.in create mode 100644 scripts/copyright.cmake diff --git a/.gitignore b/.gitignore index 400e3d902..f8b9d9755 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Prevent pushing window layouts ./config/default-layout.ini +# Do not push auto generated COPYRIGHT +COPYRIGHT_generated + # Prerequisites *.d diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 000000000..b567d7dae --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,188 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Comment: + NEXO Engine Exhaustive list of all licenses used in the project + --------------------------------------------------------------- + This file is a template that is processed by CMake to generate the + final copyright file. See scripts/copyright.cmake for more information. + When generated some information might be missing and should be + verified and updated manually. + . + This file lists copyright holders and licenses for the NEXO project, its + source code, dependencies and libraries. + . + The format is based on the Debian copyright format 1.0, which is a + human and machine readable format for copyright information. + For more information, see: + https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Upstream-Name: NEXO Engine +Upstream-Contact: NEXO Engine Team +Source: https://github.com/NexoEngine/game-engine + +Files: * +Comment: NEXO Engine +Copyright: 2025 NEXO Engine contributors +License: MIT + +Files: * +Copyright: 2006-2021, assimp team +License: BSD-3-clause +Comment: + assimp full license in /external/licenses/assimp + +Files: * +Copyright: Boost Software License - Version 1.0 - August 17th, 2003 +License: BSL-1.0 +Comment: + boost full license in /external/licenses/boost + +Files: * +Copyright: Google Inc. and other contributors +License: Apache-2.0 +Comment: + draco full license in /external/licenses/draco + +Files: * +Copyright: 2008-2018 The Khronos Group Inc. +License: Apache-2.0 +Comment: + egl-registry full license in /external/licenses/egl-registry + +Files: * +Copyright: 2013-2021 David Herberth +License: MIT +Comment: + glad full license in /external/licenses/glad + +Files: * +Copyright: 2002-2006 Marcus Geelnard + 2006-2019 Camilla Löwy +License: zlib/libpng +Comment: + glfw3 full license in /external/licenses/glfw3 + +Files: * +Copyright: 2005 - G-Truc Creation +License: MIT +Comment: + glm full license in /external/licenses/glm + +Files: * +Copyright: 2008, Google Inc. +License: BSD-3-clause +Comment: + gtest full license in /external/licenses/gtest + +Files: * +Copyright: 2014-2025 Omar Cornut +License: MIT +Comment: + imgui full license in /external/licenses/imgui + +Files: * +Copyright: 2016 Cedric Guillemet +License: MIT +Comment: + imguizmo full license in /external/licenses/imguizmo + +Files: * +Copyright: 2009-2018, Poly2Tri Contributors +License: BSD-3-clause +Comment: + jhasse-poly2tri full license in /external/licenses/jhasse-poly2tri + +Files: * +Copyright: 2021 Jorrit Rouwe +License: MIT +Comment: + joltphysics full license in /external/licenses/joltphysics + +Files: * +Copyright: Kuba Podgórski +License: public-domain +Comment: + kubazip full license in /external/licenses/kubazip + +Files: * +Copyright: Emil Ernerfeldt +License: public-domain +Comment: + loguru full license in /external/licenses/loguru + +Files: * +Copyright: 1998-2010 - by Gilles Vollant - version 1.1 64 bits from Mathias Svensson +License: zlib +Comment: + minizip full license in /external/licenses/minizip + +Files: * +Copyright: 2013-2022 Niels Lohmann +License: MIT +Comment: + nlohmann-json full license in /external/licenses/nlohmann-json + +Files: * +Copyright: 2008-2018 The Khronos Group Inc. +License: MIT +Comment: + opengl-registry full license in /external/licenses/opengl-registry + +Files: * +Copyright: Angus Johnson and other contributors +License: BSL-1.0 +Comment: + polyclipping full license in /external/licenses/polyclipping + +Files: * +Copyright: 2006-2023 Arseny Kapoulkine +License: MIT +Comment: + pugixml full license in /external/licenses/pugixml + +Files: * +Copyright: 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +License: MIT +Comment: + rapidjson full license in /external/licenses/rapidjson + +Files: * +Copyright: 2017 Sean Barrett +License: MIT or public-domain +Comment: + stb full license in /external/licenses/stb + +Files: * +Copyright: Pascal Costanza +License: zlib/libpng +Comment: + tinyfiledialogs full license in /external/licenses/tinyfiledialogs + +Files: * +Copyright: Nemanja Trifunovic +License: BSL-1.0 +Comment: + utfcpp full license in /external/licenses/utfcpp + +Files: * +Copyright: Microsoft Corporation +License: MIT +Comment: + vcpkg-cmake full license in /external/licenses/vcpkg-cmake + +Files: * +Copyright: Microsoft Corporation +License: MIT +Comment: + vcpkg-cmake-config full license in /external/licenses/vcpkg-cmake-config + +Files: * +Copyright: Microsoft Corporation +License: MIT +Comment: + vcpkg-cmake-get-vars full license in /external/licenses/vcpkg-cmake-get-vars + +Files: * +Copyright: 1995-2022 Jean-loup Gailly and Mark Adler +License: zlib +Comment: + zlib full license in /external/licenses/zlib diff --git a/external/licenses/assimp b/external/licenses/assimp new file mode 100644 index 000000000..acaaf016e --- /dev/null +++ b/external/licenses/assimp @@ -0,0 +1,78 @@ +Open Asset Import Library (assimp) + +Copyright (c) 2006-2021, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +****************************************************************************** + +AN EXCEPTION applies to all files in the ./test/models-nonbsd folder. +These are 3d models for testing purposes, from various free sources +on the internet. They are - unless otherwise stated - copyright of +their respective creators, which may impose additional requirements +on the use of their work. For any of these models, see +.source.txt for more legal information. Contact us if you +are a copyright holder and believe that we credited you inproperly or +if you don't want your files to appear in the repository. + + +****************************************************************************** + +Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors +http://code.google.com/p/poly2tri/ + +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Poly2Tri nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/external/licenses/boost b/external/licenses/boost new file mode 100644 index 000000000..36b7cd93c --- /dev/null +++ b/external/licenses/boost @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/external/licenses/draco b/external/licenses/draco new file mode 100644 index 000000000..301095454 --- /dev/null +++ b/external/licenses/draco @@ -0,0 +1,252 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- +Files: docs/assets/js/ASCIIMathML.js + +Copyright (c) 2014 Peter Jipsen and other ASCIIMathML.js contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +-------------------------------------------------------------------------------- +Files: docs/assets/css/pygments/* + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/external/licenses/egl-registry b/external/licenses/egl-registry new file mode 100644 index 000000000..8db79b8e2 --- /dev/null +++ b/external/licenses/egl-registry @@ -0,0 +1,28 @@ +## include/KHR/khrplatform.h + +Copyright (c) 2008-2018 The Khronos Group Inc. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and/or associated documentation files (the +"Materials"), to deal in the Materials without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Materials, and to +permit persons to whom the Materials are furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Materials. + +THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + +## include/EGL/* +## share/opengl/egl.xml + +Copyright 2013-2020 The Khronos Group Inc. +SPDX-License-Identifier: Apache-2.0 diff --git a/external/licenses/glad b/external/licenses/glad new file mode 100644 index 000000000..ac5d8041b --- /dev/null +++ b/external/licenses/glad @@ -0,0 +1,63 @@ +The glad source code: + + The MIT License (MIT) + + Copyright (c) 2013-2021 David Herberth + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +The Khronos Specifications: + + Copyright (c) 2013-2020 The Khronos Group Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +The EGL Specification and various headers: + + Copyright (c) 2007-2016 The Khronos Group Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and/or associated documentation files (the + "Materials"), to deal in the Materials without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Materials, and to + permit persons to whom the Materials are furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Materials. + + THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. diff --git a/external/licenses/glfw3 b/external/licenses/glfw3 new file mode 100644 index 000000000..7494a3f68 --- /dev/null +++ b/external/licenses/glfw3 @@ -0,0 +1,23 @@ +Copyright (c) 2002-2006 Marcus Geelnard + +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + diff --git a/external/licenses/glm b/external/licenses/glm new file mode 100644 index 000000000..779c32fb9 --- /dev/null +++ b/external/licenses/glm @@ -0,0 +1,54 @@ +================================================================================ +OpenGL Mathematics (GLM) +-------------------------------------------------------------------------------- +GLM is licensed under The Happy Bunny License or MIT License + +================================================================================ +The Happy Bunny License (Modified MIT License) +-------------------------------------------------------------------------------- +Copyright (c) 2005 - G-Truc Creation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Restrictions: + By making use of the Software for military purposes, you choose to make a + Bunny unhappy. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +================================================================================ +The MIT License +-------------------------------------------------------------------------------- +Copyright (c) 2005 - G-Truc Creation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/external/licenses/gtest b/external/licenses/gtest new file mode 100644 index 000000000..1941a11f8 --- /dev/null +++ b/external/licenses/gtest @@ -0,0 +1,28 @@ +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/external/licenses/imgui b/external/licenses/imgui new file mode 100644 index 000000000..00ae473ab --- /dev/null +++ b/external/licenses/imgui @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2025 Omar Cornut + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external/licenses/imguizmo b/external/licenses/imguizmo new file mode 100644 index 000000000..dcbee451c --- /dev/null +++ b/external/licenses/imguizmo @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Cedric Guillemet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external/licenses/jhasse-poly2tri b/external/licenses/jhasse-poly2tri new file mode 100644 index 000000000..dddc3ccc3 --- /dev/null +++ b/external/licenses/jhasse-poly2tri @@ -0,0 +1,26 @@ +Copyright (c) 2009-2018, Poly2Tri Contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Poly2Tri nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/external/licenses/joltphysics b/external/licenses/joltphysics new file mode 100644 index 000000000..4f0976848 --- /dev/null +++ b/external/licenses/joltphysics @@ -0,0 +1,7 @@ +Copyright 2021 Jorrit Rouwe + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/external/licenses/kubazip b/external/licenses/kubazip new file mode 100644 index 000000000..ed7cccdf2 --- /dev/null +++ b/external/licenses/kubazip @@ -0,0 +1,26 @@ +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ diff --git a/external/licenses/loguru b/external/licenses/loguru new file mode 100644 index 000000000..334edabf8 --- /dev/null +++ b/external/licenses/loguru @@ -0,0 +1,3 @@ +This software is in the public domain. Where that dedication is not recognized, you are granted a perpetual, irrevocable license to copy, modify and distribute it as you see fit. + +That being said, I would appreciate credit! If you find Loguru useful, tweet me at @ernerfeldt mail me at emil.ernerfeldt@gmail.com. \ No newline at end of file diff --git a/external/licenses/minizip b/external/licenses/minizip new file mode 100644 index 000000000..57d715242 --- /dev/null +++ b/external/licenses/minizip @@ -0,0 +1,74 @@ +MiniZip - Copyright (c) 1998-2010 - by Gilles Vollant - version 1.1 64 bits from Mathias Svensson + +Introduction +--------------------- +MiniZip 1.1 is built from MiniZip 1.0 by Gilles Vollant ( http://www.winimage.com/zLibDll/minizip.html ) + +When adding ZIP64 support into minizip it would result into risk of breaking compatibility with minizip 1.0. +All possible work was done for compatibility. + + +Background +--------------------- +When adding ZIP64 support Mathias Svensson found that Even Rouault have added ZIP64 +support for unzip.c into minizip for a open source project called gdal ( http://www.gdal.org/ ) + +That was used as a starting point. And after that ZIP64 support was added to zip.c +some refactoring and code cleanup was also done. + + +Changed from MiniZip 1.0 to MiniZip 1.1 +--------------------------------------- +* Added ZIP64 support for unzip ( by Even Rouault ) +* Added ZIP64 support for zip ( by Mathias Svensson ) +* Reverted some changed that Even Rouault did. +* Bunch of patches received from Gulles Vollant that he received for MiniZip from various users. +* Added unzip patch for BZIP Compression method (patch create by Daniel Borca) +* Added BZIP Compress method for zip +* Did some refactoring and code cleanup + + +Credits + + Gilles Vollant - Original MiniZip author + Even Rouault - ZIP64 unzip Support + Daniel Borca - BZip Compression method support in unzip + Mathias Svensson - ZIP64 zip support + Mathias Svensson - BZip Compression method support in zip + + Resources + + ZipLayout http://result42.com/projects/ZipFileLayout + Command line tool for Windows that shows the layout and information of the headers in a zip archive. + Used when debugging and validating the creation of zip files using MiniZip64 + + + ZIP App Note http://www.pkware.com/documents/casestudies/APPNOTE.TXT + Zip File specification + + +Notes. + * To be able to use BZip compression method in zip64.c or unzip64.c the BZIP2 lib is needed and HAVE_BZIP2 need to be defined. + +License +---------------------------------------------------------- + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +---------------------------------------------------------- + diff --git a/external/licenses/nlohmann-json b/external/licenses/nlohmann-json new file mode 100644 index 000000000..1c1f7a690 --- /dev/null +++ b/external/licenses/nlohmann-json @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2022 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external/licenses/opengl-registry b/external/licenses/opengl-registry new file mode 100644 index 000000000..c42239352 --- /dev/null +++ b/external/licenses/opengl-registry @@ -0,0 +1,2 @@ +The files installed by the `opengl-registry` port are using different licenses. +Each file defines its license in a comment at the top of the file. diff --git a/external/licenses/polyclipping b/external/licenses/polyclipping new file mode 100644 index 000000000..3e3af47ba --- /dev/null +++ b/external/licenses/polyclipping @@ -0,0 +1,24 @@ +Boost Software License - Version 1.0 - August 17th, 2003 +http://www.boost.org/LICENSE_1_0.txt + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/external/licenses/pugixml b/external/licenses/pugixml new file mode 100644 index 000000000..91bdfc194 --- /dev/null +++ b/external/licenses/pugixml @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) 2006-2023 Arseny Kapoulkine + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/external/licenses/rapidjson b/external/licenses/rapidjson new file mode 100644 index 000000000..7ccc161c8 --- /dev/null +++ b/external/licenses/rapidjson @@ -0,0 +1,57 @@ +Tencent is pleased to support the open source community by making RapidJSON available. + +Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. + +If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License. +If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license. +A copy of the MIT License is included in this file. + +Other dependencies and licenses: + +Open Source Software Licensed Under the BSD License: +-------------------------------------------------------------------- + +The msinttypes r29 +Copyright (c) 2006-2013 Alexander Chemeris +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Open Source Software Licensed Under the JSON License: +-------------------------------------------------------------------- + +json.org +Copyright (c) 2002 JSON.org +All Rights Reserved. + +JSON_checker +Copyright (c) 2002 JSON.org +All Rights Reserved. + + +Terms of the JSON License: +--------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Terms of the MIT License: +-------------------------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/external/licenses/stb b/external/licenses/stb new file mode 100644 index 000000000..a77ae91f3 --- /dev/null +++ b/external/licenses/stb @@ -0,0 +1,37 @@ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/external/licenses/tinyfiledialogs b/external/licenses/tinyfiledialogs new file mode 100644 index 000000000..2b8ff4377 --- /dev/null +++ b/external/licenses/tinyfiledialogs @@ -0,0 +1,14 @@ + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/external/licenses/utfcpp b/external/licenses/utfcpp new file mode 100644 index 000000000..36b7cd93c --- /dev/null +++ b/external/licenses/utfcpp @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/external/licenses/vcpkg-cmake b/external/licenses/vcpkg-cmake new file mode 100644 index 000000000..4d23e0e39 --- /dev/null +++ b/external/licenses/vcpkg-cmake @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be included in all copies +or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/external/licenses/vcpkg-cmake-config b/external/licenses/vcpkg-cmake-config new file mode 100644 index 000000000..2e4eac826 --- /dev/null +++ b/external/licenses/vcpkg-cmake-config @@ -0,0 +1,23 @@ +Copyright (c) Microsoft Corporation + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external/licenses/vcpkg-cmake-get-vars b/external/licenses/vcpkg-cmake-get-vars new file mode 100644 index 000000000..4d23e0e39 --- /dev/null +++ b/external/licenses/vcpkg-cmake-get-vars @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be included in all copies +or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/external/licenses/zlib b/external/licenses/zlib new file mode 100644 index 000000000..ab8ee6f71 --- /dev/null +++ b/external/licenses/zlib @@ -0,0 +1,22 @@ +Copyright notice: + + (C) 1995-2022 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu diff --git a/scripts/COPYRIGHT.in b/scripts/COPYRIGHT.in new file mode 100644 index 000000000..b8ff53cea --- /dev/null +++ b/scripts/COPYRIGHT.in @@ -0,0 +1,27 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Comment: + NEXO Engine Exhaustive list of all licenses used in the project + --------------------------------------------------------------- + This file is a template that is processed by CMake to generate the + final copyright file. See scripts/copyright.cmake for more information. + When generated some information might be missing and should be + verified and updated manually. + . + This file lists copyright holders and licenses for the NEXO project, its + source code, dependencies and libraries. + . + The format is based on the Debian copyright format 1.0, which is a + human and machine readable format for copyright information. + For more information, see: + https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Upstream-Name: @UPSTREAM_NAME@ +Upstream-Contact: @UPSTREAM_CONTACT@ +Source: @UPSTREAM_SOURCE@ + +Files: * +Comment: NEXO Engine +Copyright: 2025 NEXO Engine contributors +License: MIT + +@COPYRIGHT_LIST@ diff --git a/scripts/copyright.cmake b/scripts/copyright.cmake new file mode 100644 index 000000000..eb6ed35b3 --- /dev/null +++ b/scripts/copyright.cmake @@ -0,0 +1,61 @@ +#### copyright.cmake ########################################################## +# +# 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: Guillaume HEIN +# Date: 15/04/2025 +# Description: CMake to generate the copyright file for the NEXO project. +# +############################################################################### + +cmake_minimum_required(VERSION 3.28) + +message(STATUS "Generating copyright file...") + +set(UPSTREAM_NAME "NEXO Engine") +set(UPSTREAM_CONTACT "NEXO Engine Team ") +set(UPSTREAM_SOURCE "https://github.com/NexoEngine/game-engine") + +set(COPYRIGHT_LIST "") + +# find copyright line with regex +function(extract_copyright_line file outvar) + file(READ ${file} file_content) + string(REGEX MATCH "Copyright[^\n\r]*" regex_match ${file_content}) + string(REGEX REPLACE "(Copyright[\\t\\n\\r ]*(\\(c\\))?[\\t\\n\\r ]*)" "" regex_match "${regex_match}") + set(${outvar} ${regex_match} PARENT_SCOPE) +endfunction() + +file(GLOB LICENSE_PATHS "${CMAKE_INSTALL_PREFIX}/external/licenses/*") + +foreach(LICENSE_PATH ${LICENSE_PATHS}) + cmake_path(GET LICENSE_PATH FILENAME LICENSE_LIB_NAME) + message(STATUS "Processing license: ${LICENSE_PATH}") + extract_copyright_line("${LICENSE_PATH}" COPYRIGHT_LINE) + if (NOT COPYRIGHT_LINE OR COPYRIGHT_LINE STREQUAL "") + set(COPYRIGHT_LINE "${LICENSE_LIB_NAME} TODO: copyright line not found") + endif() + set(COPYRIGHT_ENTRY_OUTPUT " + +Files: * +Copyright: ${COPYRIGHT_LINE} +License: TODO: license to determine +Comment: + ${LICENSE_LIB_NAME} full license in /external/licenses/${LICENSE_LIB_NAME}") + + set(COPYRIGHT_LIST "${COPYRIGHT_LIST}${COPYRIGHT_ENTRY_OUTPUT}") +endforeach() + +string(STRIP "${COPYRIGHT_LIST}" COPYRIGHT_LIST) + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/COPYRIGHT.in" + "${CMAKE_INSTALL_PREFIX}/COPYRIGHT_generated" + @ONLY +) + +message(STATUS "Copyright file generated at ${CMAKE_INSTALL_PREFIX}/COPYRIGHT_generated") diff --git a/scripts/install.cmake b/scripts/install.cmake index fc237bac7..f9a3a8691 100755 --- a/scripts/install.cmake +++ b/scripts/install.cmake @@ -53,3 +53,29 @@ install(DIRECTORY "${CMAKE_SOURCE_DIR}/src" # source directory FILES_MATCHING # install only matched files PATTERN "*.hpp" # select header files ) + +# Create license file +# install licenses +file(GLOB LICENSE_DIRS "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/share/*") +set(LICENSE_LIST "" CACHE INTERNAL "") +foreach(LICENSE_DIR ${LICENSE_DIRS}) + if (EXISTS ${LICENSE_DIR}/copyright) + cmake_path(GET LICENSE_DIR FILENAME LICENSE_LIB_NAME) + if ("${LICENSE_LIB_NAME}" MATCHES "boost-*") + set(LICENSE_LIB_NAME "boost") + endif() + list(FIND LICENSE_LIST ${LICENSE_LIB_NAME} LICENSE_LIB_NAME_FOUND) + if (NOT LICENSE_LIB_NAME_FOUND EQUAL -1) + continue() + endif() + install(FILES ${LICENSE_DIR}/copyright DESTINATION "external/licenses" RENAME ${LICENSE_LIB_NAME} + COMPONENT licenses EXCLUDE_FROM_ALL) + list(APPEND LICENSE_LIST ${LICENSE_LIB_NAME}) + endif() +endforeach() +list(LENGTH LICENSE_LIST LICENSE_LIST_LENGTH) +message(STATUS "Found ${LICENSE_LIST_LENGTH} licenses: ${LICENSE_LIST}") + +install(SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/copyright.cmake" + COMPONENT licenses EXCLUDE_FROM_ALL +) From dc0808234ca312b6b86096a48c556cbe646b2ab4 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 15 Apr 2025 13:00:33 +0900 Subject: [PATCH 287/450] chore(license): update copyright and license information for various files --- COPYRIGHT | 59 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/COPYRIGHT b/COPYRIGHT index b567d7dae..56b92264a 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -24,13 +24,25 @@ Comment: NEXO Engine Copyright: 2025 NEXO Engine contributors License: MIT +Files: + resources/nexo.ico + resources/nexo.png + resources/nexo.rc + resources/nexo_header.bmp +Comment: NEXO Engine Logo +Copyright: 2025 NEXO Engine contributors +License: CC-BY-SA-4.0 + Files: * Copyright: 2006-2021, assimp team License: BSD-3-clause Comment: assimp full license in /external/licenses/assimp -Files: * +Files: + engine/* + editor/* + common/* Copyright: Boost Software License - Version 1.0 - August 17th, 2003 License: BSL-1.0 Comment: @@ -48,38 +60,46 @@ License: Apache-2.0 Comment: egl-registry full license in /external/licenses/egl-registry -Files: * +Files: + engine/* Copyright: 2013-2021 David Herberth License: MIT Comment: glad full license in /external/licenses/glad -Files: * +Files: + engine/* Copyright: 2002-2006 Marcus Geelnard 2006-2019 Camilla Löwy License: zlib/libpng Comment: glfw3 full license in /external/licenses/glfw3 -Files: * +Files: + engine/* + editor/* + common/* Copyright: 2005 - G-Truc Creation License: MIT Comment: glm full license in /external/licenses/glm -Files: * +Files: + tests/* Copyright: 2008, Google Inc. License: BSD-3-clause Comment: gtest full license in /external/licenses/gtest -Files: * +Files: + editor/* Copyright: 2014-2025 Omar Cornut License: MIT Comment: imgui full license in /external/licenses/imgui -Files: * +Files: + editor/* Copyright: 2016 Cedric Guillemet License: MIT Comment: @@ -91,7 +111,8 @@ License: BSD-3-clause Comment: jhasse-poly2tri full license in /external/licenses/jhasse-poly2tri -Files: * +Files: + engine/* Copyright: 2021 Jorrit Rouwe License: MIT Comment: @@ -103,7 +124,10 @@ License: public-domain Comment: kubazip full license in /external/licenses/kubazip -Files: * +Files: + engine/* + editor/* + common/* Copyright: Emil Ernerfeldt License: public-domain Comment: @@ -115,7 +139,10 @@ License: zlib Comment: minizip full license in /external/licenses/minizip -Files: * +Files: + engine/* + editor/* + common/* Copyright: 2013-2022 Niels Lohmann License: MIT Comment: @@ -145,13 +172,15 @@ License: MIT Comment: rapidjson full license in /external/licenses/rapidjson -Files: * +Files: + engine/* Copyright: 2017 Sean Barrett License: MIT or public-domain Comment: stb full license in /external/licenses/stb -Files: * +Files: + editor/* Copyright: Pascal Costanza License: zlib/libpng Comment: @@ -163,19 +192,19 @@ License: BSL-1.0 Comment: utfcpp full license in /external/licenses/utfcpp -Files: * +Files: vcpkg Copyright: Microsoft Corporation License: MIT Comment: vcpkg-cmake full license in /external/licenses/vcpkg-cmake -Files: * +Files: vcpkg Copyright: Microsoft Corporation License: MIT Comment: vcpkg-cmake-config full license in /external/licenses/vcpkg-cmake-config -Files: * +Files: vcpkg Copyright: Microsoft Corporation License: MIT Comment: From e723229b4ad00b824e0fd2e8f0c7e85a8be3e32b Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 15 Apr 2025 14:02:46 +0900 Subject: [PATCH 288/450] feat(license): add installation of license files and components in NSIS and deb --- scripts/CMakeCPackOptions.cmake.in | 1 + scripts/install.cmake | 21 ++++++++++++++++----- scripts/pack.cmake | 7 +++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/scripts/CMakeCPackOptions.cmake.in b/scripts/CMakeCPackOptions.cmake.in index 20d23469d..12d792c9e 100755 --- a/scripts/CMakeCPackOptions.cmake.in +++ b/scripts/CMakeCPackOptions.cmake.in @@ -64,6 +64,7 @@ elseif (CPACK_GENERATOR MATCHES "NSIS") Unspecified documentation headers + licenses ) else() message(WARNING "Unknown CPACK_GENERATOR: ${CPACK_GENERATOR}, cannot configure package generator specific settings. This may result in a broken package.") diff --git a/scripts/install.cmake b/scripts/install.cmake index f9a3a8691..99faacedd 100755 --- a/scripts/install.cmake +++ b/scripts/install.cmake @@ -54,8 +54,17 @@ install(DIRECTORY "${CMAKE_SOURCE_DIR}/src" # source directory PATTERN "*.hpp" # select header files ) -# Create license file -# install licenses +# Component licenses +install(DIRECTORY "${CMAKE_SOURCE_DIR}/external/licenses/" + DESTINATION "external/licenses/" + COMPONENT licenses +) +install(FILES "${CMAKE_SOURCE_DIR}/LICENSE" "${CMAKE_SOURCE_DIR}/COPYRIGHT" + DESTINATION "." + COMPONENT licenses +) + +# Generate license files file(GLOB LICENSE_DIRS "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/share/*") set(LICENSE_LIST "" CACHE INTERNAL "") foreach(LICENSE_DIR ${LICENSE_DIRS}) @@ -69,13 +78,15 @@ foreach(LICENSE_DIR ${LICENSE_DIRS}) continue() endif() install(FILES ${LICENSE_DIR}/copyright DESTINATION "external/licenses" RENAME ${LICENSE_LIB_NAME} - COMPONENT licenses EXCLUDE_FROM_ALL) + COMPONENT generate-licenses EXCLUDE_FROM_ALL) list(APPEND LICENSE_LIST ${LICENSE_LIB_NAME}) endif() endforeach() list(LENGTH LICENSE_LIST LICENSE_LIST_LENGTH) -message(STATUS "Found ${LICENSE_LIST_LENGTH} licenses: ${LICENSE_LIST}") +# Generate copyright file install(SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/copyright.cmake" - COMPONENT licenses EXCLUDE_FROM_ALL + COMPONENT generate-copyright EXCLUDE_FROM_ALL ) + + diff --git a/scripts/pack.cmake b/scripts/pack.cmake index b4fe9faf2..52c681f3a 100755 --- a/scripts/pack.cmake +++ b/scripts/pack.cmake @@ -72,6 +72,13 @@ cpack_add_component(headers DISABLED GROUP optional ) + +cpack_add_component(licenses + DISPLAY_NAME "Licenses" + DESCRIPTION "The licenses of the NEXO Engine. Will be installed in the licenses directory." + REQUIRED +) + cpack_add_component_group(optional DISPLAY_NAME "Optional Components" DESCRIPTION "Optional components of the NEXO Engine." From af9e0fd733783d8c1b7e0d10ea3324608d50109c Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 15 Apr 2025 14:23:38 +0900 Subject: [PATCH 289/450] feat(license): force license installation process, remove optional license components --- scripts/CMakeCPackOptions.cmake.in | 1 - scripts/install.cmake | 18 ++++++++---------- scripts/pack.cmake | 6 ------ 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/scripts/CMakeCPackOptions.cmake.in b/scripts/CMakeCPackOptions.cmake.in index 12d792c9e..20d23469d 100755 --- a/scripts/CMakeCPackOptions.cmake.in +++ b/scripts/CMakeCPackOptions.cmake.in @@ -64,7 +64,6 @@ elseif (CPACK_GENERATOR MATCHES "NSIS") Unspecified documentation headers - licenses ) else() message(WARNING "Unknown CPACK_GENERATOR: ${CPACK_GENERATOR}, cannot configure package generator specific settings. This may result in a broken package.") diff --git a/scripts/install.cmake b/scripts/install.cmake index 99faacedd..cef29ed6c 100755 --- a/scripts/install.cmake +++ b/scripts/install.cmake @@ -34,6 +34,14 @@ install(DIRECTORY "${CMAKE_SOURCE_DIR}/resources/" DESTINATION resources) install(DIRECTORY "${CMAKE_SOURCE_DIR}/config/" DESTINATION config) install(DIRECTORY DESTINATION logs) +# Install licenses +install(DIRECTORY "${CMAKE_SOURCE_DIR}/external/licenses/" + DESTINATION "external/licenses/" +) +install(FILES "${CMAKE_SOURCE_DIR}/LICENSE" "${CMAKE_SOURCE_DIR}/COPYRIGHT" + DESTINATION "." +) + # Component documenation install(DIRECTORY "${CMAKE_SOURCE_DIR}/docs/" DESTINATION docs @@ -54,16 +62,6 @@ install(DIRECTORY "${CMAKE_SOURCE_DIR}/src" # source directory PATTERN "*.hpp" # select header files ) -# Component licenses -install(DIRECTORY "${CMAKE_SOURCE_DIR}/external/licenses/" - DESTINATION "external/licenses/" - COMPONENT licenses -) -install(FILES "${CMAKE_SOURCE_DIR}/LICENSE" "${CMAKE_SOURCE_DIR}/COPYRIGHT" - DESTINATION "." - COMPONENT licenses -) - # Generate license files file(GLOB LICENSE_DIRS "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/share/*") set(LICENSE_LIST "" CACHE INTERNAL "") diff --git a/scripts/pack.cmake b/scripts/pack.cmake index 52c681f3a..3e6bf9f13 100755 --- a/scripts/pack.cmake +++ b/scripts/pack.cmake @@ -73,12 +73,6 @@ cpack_add_component(headers GROUP optional ) -cpack_add_component(licenses - DISPLAY_NAME "Licenses" - DESCRIPTION "The licenses of the NEXO Engine. Will be installed in the licenses directory." - REQUIRED -) - cpack_add_component_group(optional DISPLAY_NAME "Optional Components" DESCRIPTION "Optional components of the NEXO Engine." From d26fe6aa658601288921c3abf241edb2a98aec57 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 15 Apr 2025 14:35:59 +0900 Subject: [PATCH 290/450] feat(license): make NSIS installer DPI aware, less blurry --- scripts/windows/nsis_config.cmake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/windows/nsis_config.cmake b/scripts/windows/nsis_config.cmake index 479664309..942c637c1 100755 --- a/scripts/windows/nsis_config.cmake +++ b/scripts/windows/nsis_config.cmake @@ -35,5 +35,6 @@ set(CPACK_PACKAGE_EXECUTABLES "nexoEditor" "NEXO Engine") set(CPACK_NSIS_MENU_LINKS "https://nexoengine.github.io/game-engine/" "NEXO Engine Website" ) -set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) -set(CPACK_NSIS_MODIFY_PATH ON) +set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) # Alert the user if the program is already installed +set(CPACK_NSIS_MODIFY_PATH ON) # Let user decide where to install +set(CPACK_NSIS_MANIFEST_DPI_AWARE ON) # Make the installer DPI aware, less blurry From b1058baf6bd9d62fea10d54b16249f9904d9b909 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 15 Apr 2025 15:09:54 +0900 Subject: [PATCH 291/450] docs(license): add license to README.md, add tutorial on how to use COPYRIGHT and licenses installers --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 7ce283086..0d858147d 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ Welcome to the NEXO Engine repository! This project is a collaborative effort to - [Create an installer for Linux (DEB)](#create-an-installer-for-linux-deb) - [Run the tests](#run-the-tests) - [The Team](#the-team) + - [Acknowledgements](#acknowledgements) + - [License](#license) + - [How to extract the third-party licenses file](#how-to-extract-the-third-party-licenses-file) + - [How to generate the COPYRIGHT file](#how-to-generate-the-copyright-file) > [!NOTE] > Find the whole documentation on our [website](https://nexoengine.github.io/game-engine/). @@ -175,3 +179,33 @@ NEXO Engine is brought to life by a dedicated team of fourth-year students from This project is part of our curriculum and end of studies project, showcasing our collective skills in advanced software development with modern C++. We thank Epitech for the opportunity to work on such an engaging project and for the support throughout our educational journey. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. +For more information about the copyright of the project, please refer to the [COPYRIGHT](COPYRIGHT) file, +you can also find the license of the third-party libraries used in the project in the [external/licenses](external/licenses) directory. + +### How to extract the third-party licenses file + +You can use the cmake install command: +```bash +cmake --install build --prefix /path/to/install --component generate-licenses +``` + +This will extract all licenses per third-party library in the `/path/to/install/external/licenses` directory. + +> [!NOTE] +> These licenses are automatically extracted from vcpkg, there might be missing third-party libraries. + +### How to generate the COPYRIGHT file + +You can use the cmake install command: +```bash +cmake --install build --prefix /path/to/install --component generate-copyright +``` + +This will generate the COPYRIGHT file in the `/path/to/install` directory. + +> [!WARNING] +> By default the COPYRIGHT file is generated with some `TODO:`, the generator cannot always determine exact licenses for some files. Please check each entry for errors. From fea782361e14ae551be0a4ca3c660f367e554b9b Mon Sep 17 00:00:00 2001 From: Guillaume HEIN <53176398+Thyodas@users.noreply.github.com> Date: Tue, 15 Apr 2025 15:20:12 +0900 Subject: [PATCH 292/450] docs(license): add line breaks, add mail contact info --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0d858147d..dfa0d538d 100644 --- a/README.md +++ b/README.md @@ -182,9 +182,12 @@ We thank Epitech for the opportunity to work on such an engaging project and for ## License -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. -For more information about the copyright of the project, please refer to the [COPYRIGHT](COPYRIGHT) file, -you can also find the license of the third-party libraries used in the project in the [external/licenses](external/licenses) directory. +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
    +For more information about the copyright of the project, please refer to the [COPYRIGHT](COPYRIGHT) file.
    +You can also find the license of the third-party libraries used in the project in the [external/licenses](external/licenses) directory. + +> [!TIP] +> For any license inquiry, please contact us at [nexo.engine@gmail.com](mailto:nexo.engine@gmail.com?subject=[NEXO%20Engine]%20License) ### How to extract the third-party licenses file From b0ee833ce7e449fd4c85a60ace67af4b24b7a53c Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 16 May 2025 20:50:09 +0900 Subject: [PATCH 293/450] ci(vcpkg-binary-cache): add binary caching for vcpkg --- .github/workflows/build.yml | 38 +++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3793fa2a9..b85da3777 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,10 +15,14 @@ jobs: name: Build, test and Package # env and permissions are setup to add dependencies from vcpkg to the repo's dependency graph env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VCPKG_FEATURE_FLAGS: dependencygraph + USERNAME: NexoEngine + VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg + FEED_URL: https://nuget.pkg.github.com/NexoEngine/index.json + VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/NexoEngine/index.json,readwrite" permissions: contents: write + packages: write strategy: fail-fast: false matrix: @@ -45,7 +49,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 # Fetch all history for all tags and branches (for SonarCloud) @@ -126,6 +130,36 @@ jobs: - name: Setup vcpkg uses: lukka/run-vcpkg@v11 + - name: Add NuGet sources + if: ${{ matrix.os == 'windows-latest' }} + shell: pwsh + run: | + .$(${{ env.VCPKG_EXE }} fetch nuget) ` + sources add ` + -Source "${{ env.FEED_URL }}" ` + -StorePasswordInClearText ` + -Name GitHubPackages ` + -UserName "${{ env.USERNAME }}" ` + -Password "${{ secrets.GITHUB_TOKEN }}" + .$(${{ env.VCPKG_EXE }} fetch nuget) ` + setapikey "${{ secrets.GITHUB_TOKEN }}" ` + -Source "${{ env.FEED_URL }}" + + - name: Add NuGet sources + if: ${{ matrix.os != 'windows-latest' }} + shell: bash + run: | + mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ + sources add \ + -Source "${{ env.FEED_URL }}" \ + -StorePasswordInClearText \ + -Name GitHubPackages \ + -UserName "${{ env.USERNAME }}" \ + -Password "${{ secrets.GITHUB_TOKEN }}" + mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ + setapikey "${{ secrets.GITHUB_TOKEN }}" \ + -Source "${{ env.FEED_URL }}" + - name: CMake Workflow with preset 'build-coverage' for tests uses: lukka/run-cmake@v10 with: From b641dd05d66a866489f3fe02b4a3f4e385dd936f Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 16 May 2025 20:58:09 +0900 Subject: [PATCH 294/450] fix(vcpkg-binary-cache): invalid script for vcpkg bin on linux --- .github/workflows/build.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b85da3777..3e865c038 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -131,7 +131,6 @@ jobs: uses: lukka/run-vcpkg@v11 - name: Add NuGet sources - if: ${{ matrix.os == 'windows-latest' }} shell: pwsh run: | .$(${{ env.VCPKG_EXE }} fetch nuget) ` @@ -145,21 +144,6 @@ jobs: setapikey "${{ secrets.GITHUB_TOKEN }}" ` -Source "${{ env.FEED_URL }}" - - name: Add NuGet sources - if: ${{ matrix.os != 'windows-latest' }} - shell: bash - run: | - mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ - sources add \ - -Source "${{ env.FEED_URL }}" \ - -StorePasswordInClearText \ - -Name GitHubPackages \ - -UserName "${{ env.USERNAME }}" \ - -Password "${{ secrets.GITHUB_TOKEN }}" - mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ - setapikey "${{ secrets.GITHUB_TOKEN }}" \ - -Source "${{ env.FEED_URL }}" - - name: CMake Workflow with preset 'build-coverage' for tests uses: lukka/run-cmake@v10 with: From c6a32d54843a1b6e654a7dc2b7de8e488237de19 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 16 May 2025 20:59:10 +0900 Subject: [PATCH 295/450] ci(vcpkg-binary-cache): add vcpkg binary cache to codeql action --- .github/workflows/codeql.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9c838d8b6..08ba1bfa0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,6 +5,11 @@ on: [push] jobs: analyze: name: Analyze (${{ matrix.language }}/${{ matrix.compiler }}) on ${{ matrix.os }} + env: + USERNAME: NexoEngine + VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg + FEED_URL: https://nuget.pkg.github.com/NexoEngine/index.json + VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/NexoEngine/index.json,readwrite" runs-on: ${{ matrix.os }} permissions: # required for all workflows @@ -82,6 +87,20 @@ jobs: - name: Setup vcpkg uses: lukka/run-vcpkg@v11 + - name: Add NuGet sources + shell: pwsh + run: | + .$(${{ env.VCPKG_EXE }} fetch nuget) ` + sources add ` + -Source "${{ env.FEED_URL }}" ` + -StorePasswordInClearText ` + -Name GitHubPackages ` + -UserName "${{ env.USERNAME }}" ` + -Password "${{ secrets.GITHUB_TOKEN }}" + .$(${{ env.VCPKG_EXE }} fetch nuget) ` + setapikey "${{ secrets.GITHUB_TOKEN }}" ` + -Source "${{ env.FEED_URL }}" + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 From 240b5611a03decb322f35330f2578eb08b590543 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 16 May 2025 21:07:44 +0900 Subject: [PATCH 296/450] ci(vcpkg-binary-cache): update NuGet source fetching for non-Windows environments --- .github/workflows/build.yml | 18 +++++++++++++++++- .github/workflows/codeql.yml | 20 ++++++++++---------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3e865c038..baf9c63ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,7 +110,7 @@ jobs: libxext-dev libxi-dev libgl1-mesa-dev libxinerama-dev \ libxcursor-dev '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev \ libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev \ - libegl1-mesa-dev + libegl1-mesa-dev mono-complete version: 1.0 execute_install_scripts: true @@ -131,6 +131,7 @@ jobs: uses: lukka/run-vcpkg@v11 - name: Add NuGet sources + if: ${{ matrix.os == 'windows-latest' }} shell: pwsh run: | .$(${{ env.VCPKG_EXE }} fetch nuget) ` @@ -144,6 +145,21 @@ jobs: setapikey "${{ secrets.GITHUB_TOKEN }}" ` -Source "${{ env.FEED_URL }}" + - name: Add NuGet sources + if: ${{ matrix.os != 'windows-latest' }} + shell: bash + run: | + mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ + sources add \ + -Source "${{ env.FEED_URL }}" \ + -StorePasswordInClearText \ + -Name GitHubPackages \ + -UserName "${{ env.USERNAME }}" \ + -Password "${{ secrets.GITHUB_TOKEN }}" + mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ + setapikey "${{ secrets.GITHUB_TOKEN }}" \ + -Source "${{ env.FEED_URL }}" + - name: CMake Workflow with preset 'build-coverage' for tests uses: lukka/run-cmake@v10 with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 08ba1bfa0..922d74091 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -62,7 +62,7 @@ jobs: libxext-dev libxi-dev libgl1-mesa-dev libxinerama-dev \ libxcursor-dev '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev \ libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev \ - libegl1-mesa-dev + libegl1-mesa-dev mono-complete version: 1.0 execute_install_scripts: true @@ -88,17 +88,17 @@ jobs: uses: lukka/run-vcpkg@v11 - name: Add NuGet sources - shell: pwsh + shell: bash run: | - .$(${{ env.VCPKG_EXE }} fetch nuget) ` - sources add ` - -Source "${{ env.FEED_URL }}" ` - -StorePasswordInClearText ` - -Name GitHubPackages ` - -UserName "${{ env.USERNAME }}" ` + mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ + sources add \ + -Source "${{ env.FEED_URL }}" \ + -StorePasswordInClearText \ + -Name GitHubPackages \ + -UserName "${{ env.USERNAME }}" \ -Password "${{ secrets.GITHUB_TOKEN }}" - .$(${{ env.VCPKG_EXE }} fetch nuget) ` - setapikey "${{ secrets.GITHUB_TOKEN }}" ` + mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ + setapikey "${{ secrets.GITHUB_TOKEN }}" \ -Source "${{ env.FEED_URL }}" # Initializes the CodeQL tools for scanning. From 7befba160eab984e8c7695041c6576f96efa6ffd Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 16 May 2025 21:55:09 +0900 Subject: [PATCH 297/450] fix(vcpkg-binary-cache): move job env to step env --- .github/workflows/build.yml | 6 +++++- .github/workflows/codeql.yml | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index baf9c63ca..bd7ee13a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,6 @@ jobs: # env and permissions are setup to add dependencies from vcpkg to the repo's dependency graph env: VCPKG_FEATURE_FLAGS: dependencygraph - USERNAME: NexoEngine VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg FEED_URL: https://nuget.pkg.github.com/NexoEngine/index.json VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/NexoEngine/index.json,readwrite" @@ -133,6 +132,8 @@ jobs: - name: Add NuGet sources if: ${{ matrix.os == 'windows-latest' }} shell: pwsh + env: + USERNAME: NexoEngine run: | .$(${{ env.VCPKG_EXE }} fetch nuget) ` sources add ` @@ -148,6 +149,8 @@ jobs: - name: Add NuGet sources if: ${{ matrix.os != 'windows-latest' }} shell: bash + env: + USERNAME: NexoEngine run: | mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ sources add \ @@ -170,6 +173,7 @@ jobs: CXX: ${{ matrix.compiler == 'clang' && steps.set-up-clang.outputs.clangxx || matrix.compiler == 'gcc' && steps.set-up-gcc.outputs.gxx || '' }} CMAKE_C_COMPILER: ${{ matrix.compiler == 'clang' && steps.set-up-clang.outputs.clang || matrix.compiler == 'gcc' && steps.set-up-gcc.outputs.gcc || '' }} CMAKE_CXX_COMPILER: ${{ matrix.compiler == 'clang' && steps.set-up-clang.outputs.clangxx || matrix.compiler == 'gcc' && steps.set-up-gcc.outputs.gxx || '' }} + USERNAME: NexoEngine - name: Install Mesa for Windows shell: cmd diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 922d74091..2630c63f7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -6,7 +6,6 @@ jobs: analyze: name: Analyze (${{ matrix.language }}/${{ matrix.compiler }}) on ${{ matrix.os }} env: - USERNAME: NexoEngine VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg FEED_URL: https://nuget.pkg.github.com/NexoEngine/index.json VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/NexoEngine/index.json,readwrite" @@ -89,6 +88,8 @@ jobs: - name: Add NuGet sources shell: bash + env: + USERNAME: NexoEngine run: | mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \ sources add \ @@ -104,6 +105,8 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 + env: + USERNAME: NexoEngine with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} From 6581e13e697c669fa17ee3c2b198df2e25721e7e Mon Sep 17 00:00:00 2001 From: Thyodas Date: Fri, 25 Apr 2025 15:52:49 +0900 Subject: [PATCH 298/450] feat(asset-ecs): finish migrating to new AssetRef after rebase --- engine/src/assets/Asset.hpp | 14 +++++++------- engine/src/assets/AssetCatalog.hpp | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index 60fc4de1b..9700ba4fa 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -86,7 +86,7 @@ namespace nexo::assets { /** * @brief Serializes an AssetType value to JSON. * - * Converts the provided AssetType enum into its string representation using getAssetTypeName + * Converts the provided AssetType enum into its string representation using getAssetTypeName * and assigns this string to the JSON object. * * @param j JSON object to receive the serialized asset type. @@ -147,12 +147,12 @@ namespace nexo::assets { 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; @@ -167,16 +167,16 @@ namespace nexo::assets { }) { } - + public: AssetMetadata m_metadata; - + /** * @brief Get the metadata of the asset (for modification) */ //[[nodiscard]] AssetMetadata& getMetadata() { return m_metadata; } }; - + template class Asset : public IAsset { friend class AssetCatalog; @@ -213,7 +213,7 @@ namespace nexo::assets { } std::unique_ptr data; - + private: /*virtual AssetStatus load() = 0; virtual AssetStatus unload() = 0;*/ diff --git a/engine/src/assets/AssetCatalog.hpp b/engine/src/assets/AssetCatalog.hpp index e3c83b834..70e417b86 100644 --- a/engine/src/assets/AssetCatalog.hpp +++ b/engine/src/assets/AssetCatalog.hpp @@ -22,6 +22,8 @@ #include "AssetLocation.hpp" #include "Assets/Texture/Texture.hpp" +#include "Assets/Texture/Texture.hpp" + namespace nexo::assets { /** From f17d032b9b438f620e627e53282e5d326e742b7f Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 21:24:01 +0900 Subject: [PATCH 299/450] refactor(undo-redo): restore method now in components themselves rather than in memento --- engine/src/components/Camera.hpp | 63 ++++++++++------------- engine/src/components/Light.hpp | 57 +++++++++++--------- engine/src/components/Render.hpp | 14 ++--- engine/src/components/SceneComponents.hpp | 12 +++-- engine/src/components/Transform.hpp | 12 +++-- engine/src/components/Uuid.hpp | 12 ++--- 6 files changed, 85 insertions(+), 85 deletions(-) diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index d06a4a5a8..3e4b8cb24 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -113,18 +113,24 @@ namespace nexo::components { float farPlane; CameraType type; glm::vec4 clearColor; - bool active; - bool render; bool main; - bool resizing; std::shared_ptr renderTarget; - - CameraComponent restore() const - { - return {width, height, viewportLocked, fov, nearPlane, farPlane, type, clearColor, active, render, main, resizing, renderTarget}; - } }; + void restore(const Memento& memento) + { + width = memento.width; + height = memento.height; + viewportLocked = memento.viewportLocked; + fov = memento.fov; + nearPlane = memento.nearPlane; + farPlane = memento.farPlane; + type = memento.type; + clearColor = memento.clearColor; + main = memento.main; + m_renderTarget = memento.renderTarget; + } + [[nodiscard]] Memento save() const { return { @@ -136,15 +142,10 @@ namespace nexo::components { farPlane, type, clearColor, - active, - render, main, - resizing, m_renderTarget }; } - - }; struct EditorCameraTag {}; @@ -166,19 +167,15 @@ namespace nexo::components { struct Memento { float mouseSensitivity; - float yaw; - float pitch; float translationSpeed; - - PerspectiveCameraController restore() const - { - PerspectiveCameraController controller; - controller.mouseSensitivity = mouseSensitivity; - controller.translationSpeed = translationSpeed; - return controller; - } }; + void restore(const Memento& memento) + { + mouseSensitivity = memento.mouseSensitivity; + translationSpeed = memento.translationSpeed; + } + [[nodiscard]] Memento save() const { return { @@ -186,8 +183,6 @@ namespace nexo::components { translationSpeed }; } - - }; /** @@ -205,17 +200,15 @@ namespace nexo::components { float mouseSensitivity; float distance; ecs::Entity targetEntity; - - PerspectiveCameraTarget restore() const - { - PerspectiveCameraTarget target; - target.mouseSensitivity = mouseSensitivity; - target.distance = distance; - target.targetEntity = targetEntity; - return target; - } }; + void restore(const Memento& memento) + { + mouseSensitivity = memento.mouseSensitivity; + distance = memento.distance; + targetEntity = memento.targetEntity; + } + [[nodiscard]] Memento save() const { return { @@ -224,8 +217,6 @@ namespace nexo::components { targetEntity }; } - - }; /** diff --git a/engine/src/components/Light.hpp b/engine/src/components/Light.hpp index 1d314cfdf..192733d36 100644 --- a/engine/src/components/Light.hpp +++ b/engine/src/components/Light.hpp @@ -29,13 +29,13 @@ namespace nexo::components { struct Memento { glm::vec3 color; - - AmbientLightComponent restore(const Memento& memento) const - { - return {color}; - } }; + void restore(const Memento& memento) + { + color = memento.color; + } + Memento save() const { return {color}; @@ -56,14 +56,14 @@ namespace nexo::components { struct Memento { glm::vec3 direction; glm::vec3 color; - - DirectionalLightComponent restore() const - { - DirectionalLightComponent dirLight(direction, color); - return dirLight; - } }; + void restore(const Memento& memento) + { + direction = memento.direction; + color = memento.color; + } + Memento save() const { return {direction, color}; @@ -83,19 +83,21 @@ namespace nexo::components { float quadratic{}; float maxDistance; float constant; - - PointLightComponent restore() const - { - return {color, linear, quadratic, maxDistance, constant}; - } }; + void restore(const Memento& memento) + { + color = memento.color; + linear = memento.linear; + quadratic = memento.quadratic; + maxDistance = memento.maxDistance; + constant = memento.constant; + } + Memento save() const { return {color, linear, quadratic, maxDistance, constant}; } - - }; struct SpotLightComponent { @@ -117,19 +119,24 @@ namespace nexo::components { float quadratic{}; float maxDistance; float constant; - - SpotLightComponent restore() const - { - return {direction, color, cutOff, outerCutoff, linear, quadratic, maxDistance, constant}; - } }; + void restore(const Memento& memento) + { + direction = memento.direction; + color = memento.color; + cutOff = memento.cutOff; + outerCutoff = memento.outerCutoff; + linear = memento.linear; + quadratic = memento.quadratic; + maxDistance = memento.maxDistance; + constant = memento.constant; + } + Memento save() const { return {direction, color, cutOff, outerCutoff, linear, quadratic, maxDistance, constant}; } - - }; struct LightContext { diff --git a/engine/src/components/Render.hpp b/engine/src/components/Render.hpp index a4d69c71f..c206b243c 100644 --- a/engine/src/components/Render.hpp +++ b/engine/src/components/Render.hpp @@ -108,15 +108,15 @@ namespace nexo::components RenderType type = RenderType::RENDER_2D; std::shared_ptr renderable; - - RenderComponent restore() const - { - RenderComponent restored(renderable, type); - restored.isRendered = isRendered; - return restored; - } }; + void restore(const Memento &memento) + { + isRendered = memento.isRendered; + type = memento.type; + renderable = memento.renderable; + } + Memento save() const { return { diff --git a/engine/src/components/SceneComponents.hpp b/engine/src/components/SceneComponents.hpp index 31c08150b..61cd2bb34 100644 --- a/engine/src/components/SceneComponents.hpp +++ b/engine/src/components/SceneComponents.hpp @@ -31,13 +31,15 @@ namespace nexo::components { unsigned int id; bool isActive; bool isRendered; - - SceneTag restore() const - { - return {id, isActive, isRendered}; - } }; + void restore(const Memento &memento) + { + id = memento.id; + isActive = memento.isActive; + isRendered = memento.isRendered; + } + Memento save() const { return {id, isActive, isRendered}; diff --git a/engine/src/components/Transform.hpp b/engine/src/components/Transform.hpp index 3ae10bd38..7742b9fce 100644 --- a/engine/src/components/Transform.hpp +++ b/engine/src/components/Transform.hpp @@ -24,13 +24,15 @@ namespace nexo::components { glm::vec3 position; glm::quat rotation; glm::vec3 scale; - - TransformComponent restore() const - { - return {position, scale, rotation}; - } }; + void restore(const Memento &memento) + { + pos = memento.position; + quat = memento.rotation; + size = memento.scale; + } + Memento save() const { return {pos, quat, size}; diff --git a/engine/src/components/Uuid.hpp b/engine/src/components/Uuid.hpp index c70e5bff8..4206dbee4 100644 --- a/engine/src/components/Uuid.hpp +++ b/engine/src/components/Uuid.hpp @@ -40,20 +40,18 @@ namespace nexo::components { struct UuidComponent { struct Memento { std::string uuid; - - UuidComponent restore() const - { - return {uuid}; - } }; + void restore(const Memento &memento) + { + uuid = memento.uuid; + } + Memento save() const { return {uuid}; } - - std::string uuid = genUuid(); }; } From 1a11fa562f652cfcfd1bd2a8b559fea76ce68e4a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 21:24:28 +0900 Subject: [PATCH 300/450] refactor(undo-redo): fix the meta helper + not using anymore lambdas functions map for add/save/restore --- engine/src/ecs/Coordinator.cpp | 29 ++--------------------------- engine/src/ecs/Coordinator.hpp | 28 +++------------------------- 2 files changed, 5 insertions(+), 52 deletions(-) diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index 0df1c40c6..43cadeaa7 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -77,34 +77,9 @@ namespace nexo::ecs { return components; } - bool Coordinator::supportsMementoPattern(const std::any& component) const + bool Coordinator::supportsMementoPattern(const std::type_index typeIndex) const { - auto typeId = std::type_index(component.type()); - auto it = m_supportsMementoPattern.find(typeId); + auto it = m_supportsMementoPattern.find(typeIndex); return (it != m_supportsMementoPattern.end()) && it->second; } - - std::any Coordinator::saveComponent(const std::any& component) const - { - auto typeId = std::type_index(component.type()); - auto it = m_saveComponentFunctions.find(typeId); - if (it != m_saveComponentFunctions.end()) - return it->second(component); - return std::any(); - } - - std::any Coordinator::restoreComponent(const std::any& memento, const std::type_index& componentType) const - { - auto it = m_restoreComponentFunctions.find(componentType); - if (it != m_restoreComponentFunctions.end()) - return it->second(memento); - return std::any(); - } - - void Coordinator::addComponentAny(Entity entity, const std::type_index& typeIndex, const std::any& component) - { - auto it = m_addComponentFunctions.find(typeIndex); - if (it != m_addComponentFunctions.end()) - it->second(entity, component); - } } diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index d2f8f4d99..bfac7276a 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -70,14 +70,13 @@ namespace nexo::ecs { std::void_t().save())>> : std::is_same().save()), typename T::Memento> {}; - // Check if T::Memento has a restore() method that returns T template struct has_restore_method : std::false_type {}; template struct has_restore_method().restore())>> - : std::is_same().restore()), T> {}; + std::void_t().restore(std::declval()))>> + : std::true_type {}; // Combined check for full memento pattern support template @@ -146,23 +145,8 @@ namespace nexo::ecs { }; m_typeIDtoTypeIndex.emplace(getComponentType(), typeid(T)); - m_addComponentFunctions[typeid(T)] = [this](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); - return std::any(memento.restore()); - }; } else { m_supportsMementoPattern.emplace(typeid(T), false); } @@ -463,10 +447,7 @@ namespace nexo::ecs { return signature.test(componentType); } - 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); + bool supportsMementoPattern(const std::type_index typeIndex) const; private: template @@ -488,9 +469,6 @@ namespace nexo::ecs { 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; }; } From 66ed8aefa2fe2c59a4d87eddbc6abd87bb14a30c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 21:24:51 +0900 Subject: [PATCH 301/450] feat(undo-redo): add component restore factory --- .../actions/ComponentRestoreFactory.cpp | 51 +++++++++++++++++++ .../actions/ComponentRestoreFactory.hpp | 26 ++++++++++ 2 files changed, 77 insertions(+) create mode 100644 editor/src/context/actions/ComponentRestoreFactory.cpp create mode 100644 editor/src/context/actions/ComponentRestoreFactory.hpp diff --git a/editor/src/context/actions/ComponentRestoreFactory.cpp b/editor/src/context/actions/ComponentRestoreFactory.cpp new file mode 100644 index 000000000..05f4cb426 --- /dev/null +++ b/editor/src/context/actions/ComponentRestoreFactory.cpp @@ -0,0 +1,51 @@ +//// ComponentRestoreFactory.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: 30/04/2025 +// Description: Source file for the component restore action factory +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ComponentRestoreFactory.hpp" +#include "EntityActions.hpp" +#include "components/Camera.hpp" +#include "components/Light.hpp" +#include "components/Render.hpp" +#include "components/Transform.hpp" +#include "components/Uuid.hpp" + +namespace nexo::editor { + + std::unique_ptr ComponentRestoreFactory::createRestoreComponent(ecs::Entity entity, const std::type_index typeIndex) + { + if (typeIndex == typeid(components::TransformComponent)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::RenderComponent)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::SceneTag)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::CameraComponent)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::AmbientLightComponent)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::DirectionalLightComponent)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::PointLightComponent)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::SpotLightComponent)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::UuidComponent)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::PerspectiveCameraController)) + return std::make_unique>(entity); + else if (typeIndex == typeid(components::PerspectiveCameraTarget)) + return std::make_unique>(entity); + return nullptr; + } +} diff --git a/editor/src/context/actions/ComponentRestoreFactory.hpp b/editor/src/context/actions/ComponentRestoreFactory.hpp new file mode 100644 index 000000000..caf5a674b --- /dev/null +++ b/editor/src/context/actions/ComponentRestoreFactory.hpp @@ -0,0 +1,26 @@ +//// ComponentRestoreFactory.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: Mehdy MORVAN +// Date: 30/04/2025 +// Description: Header file for the restore component action factory +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "Action.hpp" +#include "ecs/Definitions.hpp" +#include +#include + +namespace nexo::editor { + class ComponentRestoreFactory { + public: + static std::unique_ptr createRestoreComponent(ecs::Entity entity, const std::type_index typeIndex); + }; +} From 47803e3660ea4dad037033c8450c223db4c07e69 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 21:25:40 +0900 Subject: [PATCH 302/450] refactor(undo-redo): create and delete actions now use component restore actions + fix component change action to fit the new restore method --- editor/src/context/actions/EntityActions.cpp | 58 +++++++------------- editor/src/context/actions/EntityActions.hpp | 41 +++++++++++--- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/editor/src/context/actions/EntityActions.cpp b/editor/src/context/actions/EntityActions.cpp index bdd1bf651..a81dcbc82 100644 --- a/editor/src/context/actions/EntityActions.cpp +++ b/editor/src/context/actions/EntityActions.cpp @@ -13,55 +13,40 @@ /////////////////////////////////////////////////////////////////////////////// #include "EntityActions.hpp" +#include "ComponentRestoreFactory.hpp" namespace nexo::editor { - std::vector> captureEntityState(ecs::Entity entity) - { - auto& coordinator = Application::getInstance().m_coordinator; - const auto &componentData = coordinator->getAllComponents(entity); - std::vector> result; - - // For components that support the Memento pattern, create mementos - for (const auto& [typeIndex, componentAny] : componentData) { - if (coordinator->supportsMementoPattern(componentAny)) { - std::any memento = coordinator->saveComponent(componentAny); - if (memento.has_value()) { - result.emplace_back(typeIndex, std::move(memento)); - } - } - } - return result; - } - - void restoreComponents(ecs::Entity entity, const std::vector>& mementos) - { - auto &app = getApp(); - auto &coordinator = app.m_coordinator; - for (const auto& [typeIndex, mementoAny] : mementos) { - std::any component = coordinator->restoreComponent(mementoAny, typeIndex); - coordinator->addComponentAny(entity, typeIndex, component); - } - } - void EntityCreationAction::redo() { auto& coordinator = Application::getInstance().m_coordinator; m_entityId = coordinator->createEntity(); - restoreComponents(m_entityId, m_mementos); + for (const auto &action : m_componentRestoreActions) + action->undo(); } void EntityCreationAction::undo() { - m_mementos = captureEntityState(m_entityId); - auto& coordinator = Application::getInstance().m_coordinator; + auto &coordinator = Application::getInstance().m_coordinator; + std::vector componentsTypeIndex = coordinator->getAllComponentTypes(m_entityId); + for (const auto typeIndex : componentsTypeIndex) { + if (!coordinator->supportsMementoPattern(typeIndex)) + continue; + m_componentRestoreActions.push_back(ComponentRestoreFactory::createRestoreComponent(m_entityId, typeIndex)); + } coordinator->destroyEntity(m_entityId); } EntityDeletionAction::EntityDeletionAction(ecs::Entity entityId) : m_entityId(entityId) { - m_mementos = captureEntityState(m_entityId); + auto &coordinator = Application::getInstance().m_coordinator; + std::vector componentsTypeIndex = coordinator->getAllComponentTypes(entityId); + for (const auto typeIndex : componentsTypeIndex) { + if (!coordinator->supportsMementoPattern(typeIndex)) + continue; + m_componentRestoreActions.push_back(ComponentRestoreFactory::createRestoreComponent(entityId, typeIndex)); + } } void EntityDeletionAction::redo() @@ -74,12 +59,9 @@ namespace nexo::editor { void EntityDeletionAction::undo() { auto& coordinator = Application::getInstance().m_coordinator; - - // First recreate the entity with the same ID - // This assumes you have a createEntityWithId method, otherwise - // you'll need to adapt to your coordinator's API + // This can cause problem is the entity is not the same, maybe in the future we would need another method m_entityId = coordinator->createEntity(); - - restoreComponents(m_entityId, m_mementos); + for (const auto &action : m_componentRestoreActions) + action->undo(); } } diff --git a/editor/src/context/actions/EntityActions.hpp b/editor/src/context/actions/EntityActions.hpp index 1f8c7a5df..476e98746 100644 --- a/editor/src/context/actions/EntityActions.hpp +++ b/editor/src/context/actions/EntityActions.hpp @@ -7,6 +7,31 @@ namespace nexo::editor { + template + class ComponentRestoreAction : public Action { + public: + ComponentRestoreAction(ecs::Entity entity) : m_entity(entity) + { + auto &app = getApp(); + ComponentType &target = app.m_coordinator->getComponent(m_entity); + m_memento = target.save(); + }; + + void undo() override + { + auto &app = getApp(); + ComponentType target; + target.restore(m_memento); + app.m_coordinator->addComponent(m_entity, target); + } + + void redo() override {} + + private: + ecs::Entity m_entity; + typename ComponentType::Memento m_memento; + }; + template class ComponentChangeAction : public Action { public: @@ -20,14 +45,14 @@ namespace nexo::editor { { auto &app = getApp(); ComponentType &target = app.m_coordinator->getComponent(m_entity); - target = m_afterState.restore(); + target.restore(m_afterState); } void undo() override { auto &app = getApp(); ComponentType &target = app.m_coordinator->getComponent(m_entity); - target = m_beforeState.restore(); + target.restore(m_beforeState); } private: @@ -36,9 +61,6 @@ namespace nexo::editor { typename ComponentType::Memento m_afterState; }; - std::vector> captureEntityState(ecs::Entity entity); - void restoreComponents(ecs::Entity entity, const std::vector>& mementos); - /** * Stores information needed to undo/redo entity creation * Relies on engine systems for actual creation/deletion logic @@ -46,14 +68,16 @@ namespace nexo::editor { class EntityCreationAction : public Action { public: EntityCreationAction(ecs::Entity entityId) - : m_entityId(entityId) {} + : m_entityId(entityId) { + std::cout << "Crée" << std::endl; + } void redo() override; void undo() override; private: ecs::Entity m_entityId; - std::vector> m_mementos; + std::vector> m_componentRestoreActions; }; /** @@ -68,8 +92,7 @@ namespace nexo::editor { void undo() override; private: ecs::Entity m_entityId; - // Store component mementos for those that support the pattern - std::vector> m_mementos; + std::vector> m_componentRestoreActions; }; } From 64b66988f5e4f97e617d77358e865ababc501f56 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 21:25:52 +0900 Subject: [PATCH 303/450] chore(undo-redo): add source file --- editor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 773978c70..6636a166d 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -19,6 +19,7 @@ set(SRCS editor/src/context/ActionHistory.cpp editor/src/context/ActionGroup.cpp editor/src/context/actions/EntityActions.cpp + editor/src/context/actions/ComponentRestoreFactory.cpp editor/src/ImNexo/EntityProperties.cpp editor/src/ImNexo/Components.cpp editor/src/ImNexo/Elements.cpp From 8a9d139dd676565dcc36e886623c50e69378f38a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 22:26:59 +0900 Subject: [PATCH 304/450] chore(undo-redo): remove useless print --- editor/src/context/actions/EntityActions.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/editor/src/context/actions/EntityActions.hpp b/editor/src/context/actions/EntityActions.hpp index 476e98746..3f8532d74 100644 --- a/editor/src/context/actions/EntityActions.hpp +++ b/editor/src/context/actions/EntityActions.hpp @@ -68,9 +68,7 @@ namespace nexo::editor { class EntityCreationAction : public Action { public: EntityCreationAction(ecs::Entity entityId) - : m_entityId(entityId) { - std::cout << "Crée" << std::endl; - } + : m_entityId(entityId) {} void redo() override; void undo() override; From 96a67d0abef39188230011c5711a731a6bd0f81a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 22:27:20 +0900 Subject: [PATCH 305/450] refactor(undo-redo): add a way to clear a certain number of undoable actions --- editor/src/context/ActionHistory.cpp | 16 +++++++++++----- editor/src/context/ActionHistory.hpp | 8 ++++---- editor/src/context/ActionManager.cpp | 2 +- editor/src/context/ActionManager.hpp | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/editor/src/context/ActionHistory.cpp b/editor/src/context/ActionHistory.cpp index bcfecd02b..21705574b 100644 --- a/editor/src/context/ActionHistory.cpp +++ b/editor/src/context/ActionHistory.cpp @@ -21,7 +21,7 @@ namespace nexo::editor { redoStack.clear(); while (undoStack.size() > maxUndoLevels) - undoStack.erase(undoStack.begin()); + undoStack.pop_front(); } bool ActionHistory::canUndo() const @@ -58,12 +58,18 @@ namespace nexo::editor { { maxUndoLevels = levels; while (undoStack.size() > maxUndoLevels) - undoStack.erase(undoStack.begin()); + undoStack.pop_front(); } - void ActionHistory::clear() + void ActionHistory::clear(unsigned int count) { - undoStack.clear(); - redoStack.clear(); + if (!count) { + undoStack.clear(); + redoStack.clear(); + return; + } + unsigned int elementsToRemove = std::min(static_cast(undoStack.size()), count); + for (unsigned int i = 0; i < elementsToRemove; ++i) + undoStack.pop_back(); } } diff --git a/editor/src/context/ActionHistory.hpp b/editor/src/context/ActionHistory.hpp index 5b8c5a149..77e4fbcf8 100644 --- a/editor/src/context/ActionHistory.hpp +++ b/editor/src/context/ActionHistory.hpp @@ -15,7 +15,7 @@ #include "actions/Action.hpp" #include -#include +#include #include namespace nexo::editor { @@ -35,11 +35,11 @@ namespace nexo::editor { void setMaxUndoLevels(size_t levels); - void clear(); + void clear(unsigned int count = 0); private: - std::vector> undoStack; - std::vector> redoStack; + std::deque> undoStack; + std::deque> redoStack; size_t maxUndoLevels = 50; }; diff --git a/editor/src/context/ActionManager.cpp b/editor/src/context/ActionManager.cpp index 92842e088..74f0055b1 100644 --- a/editor/src/context/ActionManager.cpp +++ b/editor/src/context/ActionManager.cpp @@ -56,7 +56,7 @@ namespace nexo::editor { return history.canRedo(); } - void ActionManager::clearHistory() + void ActionManager::clearHistory(unsigned int count) { history.clear(); } diff --git a/editor/src/context/ActionManager.hpp b/editor/src/context/ActionManager.hpp index 17e1051c8..4c2e73d6d 100644 --- a/editor/src/context/ActionManager.hpp +++ b/editor/src/context/ActionManager.hpp @@ -51,7 +51,7 @@ namespace nexo::editor { void redo(); bool canUndo() const; bool canRedo() const; - void clearHistory(); + void clearHistory(unsigned int count = 0); static ActionManager& get() { static ActionManager instance; From bb4c0f04881d4a94279e06a2e2ad2395aba47211 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 22:27:50 +0900 Subject: [PATCH 306/450] feat(undo-redo): add final actions in the editor scene --- editor/src/DocumentWindows/EditorScene/Show.cpp | 15 ++++++++++++--- .../src/DocumentWindows/EditorScene/Toolbar.cpp | 4 ++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index 796536662..d95498c17 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -19,6 +19,8 @@ #include "IconsFontAwesome.h" #include "ImNexo/Panels.hpp" #include "utils/EditorProps.hpp" +#include "context/actions/EntityActions.hpp" +#include "context/ActionManager.hpp" namespace nexo::editor { @@ -44,6 +46,8 @@ namespace nexo::editor { const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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); + ActionManager::get().recordAction(std::move(createAction)); } ImGui::EndMenu(); } @@ -58,16 +62,22 @@ namespace nexo::editor { if (ImGui::MenuItem("Directional")) { const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.0f, -1.0f, 0.0f}); sceneManager.getScene(sceneId).addEntity(directionalLight); + auto createAction = std::make_unique(directionalLight); + ActionManager::get().recordAction(std::move(createAction)); } if (ImGui::MenuItem("Point")) { const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT); sceneManager.getScene(sceneId).addEntity(pointLight); + auto createAction = std::make_unique(pointLight); + ActionManager::get().recordAction(std::move(createAction)); } if (ImGui::MenuItem("Spot")) { const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); sceneManager.getScene(sceneId).addEntity(spotLight); + auto createAction = std::make_unique(spotLight); + ActionManager::get().recordAction(std::move(createAction)); } ImGui::EndMenu(); } @@ -89,13 +99,12 @@ namespace nexo::editor { return; const glm::vec2 renderTargetSize = cameraComponent.m_renderTarget->getSize(); - // Resize handling - if ((m_contentSize.x > 0 && m_contentSize.y > 0) && (m_contentSize.x != renderTargetSize.x || m_contentSize.y != renderTargetSize.y)) + if ((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)); - } // Render framebuffer diff --git a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp index 20c0608b2..cd07b0754 100644 --- a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp +++ b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp @@ -17,6 +17,8 @@ #include "IconsFontAwesome.h" #include "context/Selector.hpp" #include "components/Uuid.hpp" +#include "context/actions/EntityActions.hpp" +#include "context/ActionManager.hpp" namespace nexo::editor { @@ -66,6 +68,8 @@ namespace nexo::editor { const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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" } From 8e668eaff514d352cb7cee87b5ce03748975c921 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 22:37:10 +0900 Subject: [PATCH 307/450] chore(undo-redo): remove useless print + function --- editor/src/inputs/InputManager.cpp | 75 +----------------------------- editor/src/inputs/InputManager.hpp | 5 -- 2 files changed, 1 insertion(+), 79 deletions(-) diff --git a/editor/src/inputs/InputManager.cpp b/editor/src/inputs/InputManager.cpp index eeb68326b..1ace81529 100644 --- a/editor/src/inputs/InputManager.cpp +++ b/editor/src/inputs/InputManager.cpp @@ -61,7 +61,6 @@ namespace nexo::editor { if (keyDown) { currentlyHeldKeys.set(idx); - // Handle key press detection if (!lastFrameHeldKeys[idx]) { // Key was just pressed this frame pressedSignature.set(idx); @@ -77,7 +76,6 @@ namespace nexo::editor { keyLastPressTime[idx] = currentTime; } } else { - // Key is not down if (lastFrameHeldKeys[idx]) { // Key was just released releasedSignature.set(idx); @@ -96,19 +94,15 @@ namespace nexo::editor { for (const auto& command : commands) { if (command.isModifier()) { - // Check if modifier is pressed std::bitset modifierSignature = command.getSignature(); if ((modifierSignature & currentlyHeldKeys) == modifierSignature) { // This modifier is held down, now check its children for (const auto& childCmd : command.getChildren()) { - // Find a key that was just pressed while the modifier is held std::bitset childSignature = childCmd.getSignature(); if ((childSignature & pressedSignature).any()) { // We found a modifier+key combination! Execute it childCmd.executePressedCallback(); modifierCombinationProcessed = true; - std::cout << "Executed modifier combination! Mod: " << command.getKey() - << " + Key: " << childCmd.getKey() << std::endl; break; // Process only one modifier combination at a time } @@ -119,7 +113,7 @@ namespace nexo::editor { } if (modifierCombinationProcessed) { - break; // Stop checking other modifiers once we've processed one + break; } } } @@ -131,19 +125,16 @@ namespace nexo::editor { // Skip modifiers, we already handled them if (command.isModifier()) continue; - // Handle pressed callbacks if (command.exactMatch(pressedSignature)) { command.executePressedCallback(); } - // Handle released callbacks if (command.exactMatch(releasedSignature)) { command.executeReleasedCallback(); } } } - // Process repeat commands if (repeatSignature.any()) { processRepeatCommands(windowState.getCommands(), repeatSignature, currentlyHeldKeys); } @@ -152,67 +143,6 @@ namespace nexo::editor { lastFrameHeldKeys = currentlyHeldKeys; } - void InputManager::processCommands( - const std::span& commands, - const std::bitset& pressedSignature, - const std::bitset& releasedSignature - ) { - // First pass: Check for modifier keys and their children - for (const auto& command : commands) { - // Only process modifiers in the first pass - if (!command.isModifier()) continue; - - // Check if this modifier is currently pressed - if ((command.getSignature() & pressedSignature) == command.getSignature()) { - // Calculate remaining pressed keys after removing the modifier - auto remainingPressedBits = pressedSignature; - remainingPressedBits ^= command.getSignature(); - - // Process child commands with this modifier - bool childCommandExecuted = false; - - for (const auto& child : command.getChildren()) { - // Check if child exactly matches remaining bits (modifier+key combination) - if (child.exactMatch(remainingPressedBits)) { - // We found an exact match for a modifier+key combination - child.executePressedCallback(); - childCommandExecuted = true; - - // Debugging - std::cout << "Executed modifier child: " << child.getKey() - << " with modifier: " << command.getKey() << std::endl; - } - - // Check for child key releases while modifier is held - if (child.exactMatch(releasedSignature)) { - child.executeReleasedCallback(); - } - } - - // If we executed a child command, return early to prevent regular commands from executing - if (childCommandExecuted) { - return; - } - } - } - - // Second pass: Process regular commands if no modifier combinations were executed - for (const auto& command : commands) { - // Skip modifiers in the second pass - if (command.isModifier()) continue; - - // Handle pressed callbacks for non-modifiers - if (command.exactMatch(pressedSignature)) { - command.executePressedCallback(); - } - - // Handle released callbacks for non-modifiers - if (command.exactMatch(releasedSignature)) { - command.executeReleasedCallback(); - } - } - } - void InputManager::processRepeatCommands( const std::span& commands, const std::bitset& repeatSignature, @@ -310,9 +240,7 @@ namespace nexo::editor { // If this command matches the pressed signature exactly or partially if (command.partialMatch(pressedSignature)) { - // If it's a modifier and matches exactly if (command.isModifier() && (command.getSignature() & pressedSignature) == command.getSignature()) { - // Check if any child modifiers are pressed bool hasActivatedChildModifier = false; for (const auto& child : command.getChildren()) { @@ -324,7 +252,6 @@ namespace nexo::editor { // For each child command, add the appropriate representation for (const auto& child : command.getChildren()) { - // If we have an activated child modifier, only process those if (hasActivatedChildModifier) { // Skip non-modifier children or modifiers that aren't pressed if (!child.isModifier() || !((child.getSignature() & pressedSignature) == child.getSignature())) { diff --git a/editor/src/inputs/InputManager.hpp b/editor/src/inputs/InputManager.hpp index a5d76b765..e1e78d364 100644 --- a/editor/src/inputs/InputManager.hpp +++ b/editor/src/inputs/InputManager.hpp @@ -42,11 +42,6 @@ namespace nexo::editor { std::bitset m_currentKeyState; std::bitset m_keyWasDownLastFrame; - // Recursive command matching - void processCommands(const std::span& commands, - const std::bitset& pressedSignature, - const std::bitset& releasedSignature - ); void processRepeatCommands( const std::span& commands, const std::bitset& repeatSignature, From ee3c4bea7bb067582e4e886da102992b5d51a60e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 22:43:03 +0900 Subject: [PATCH 308/450] feat(undo-redo): add undo/redo action for ambient light property --- .../EntityProperties/AmbientLightProperty.cpp | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp index 83a11ecd9..f93cb1944 100644 --- a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp @@ -15,24 +15,46 @@ #include "AmbientLightProperty.hpp" #include "components/Light.hpp" #include "ImNexo/Widgets.hpp" +#include "context/actions/EntityActions.hpp" +#include "context/ActionManager.hpp" +#include namespace nexo::editor { - void AmbientLightProperty::show(const ecs::Entity entity) - { + void AmbientLightProperty::show(const ecs::Entity entity) + { auto& ambientComponent = Application::getEntityComponent(entity); if (ImNexo::Header("##AmbientNode", "Ambient light")) { - ImGui::Spacing(); - static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; - ImGui::Text("Color"); - ImGui::SameLine(); - glm::vec4 color = {ambientComponent.color, 1.0f}; - ImNexo::ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker); - ambientComponent.color = color; - ImGui::TreePop(); + ImGui::Spacing(); + static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPicker = false; + static bool wasUsingLastFrame = false; + static components::AmbientLightComponent::Memento beforeState; + + ImGui::Text("Color"); + ImGui::SameLine(); + glm::vec4 color = {ambientComponent.color, 1.0f}; + if (ImNexo::ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker)) { + if (!wasUsingLastFrame) { + beforeState = ambientComponent.save(); + wasUsingLastFrame = true; + } + ambientComponent.color = color; + } + + // Only record the action when the user is done with the color picker + if (wasUsingLastFrame && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + auto afterState = ambientComponent.save(); + auto action = std::make_unique>( + entity, beforeState, afterState); + ActionManager::get().recordAction(std::move(action)); + wasUsingLastFrame = false; + beforeState = components::AmbientLightComponent::Memento{}; + } + + ImGui::TreePop(); } - } + } } From 97b4c796b52107c9d492829fdc98477a982d2b9a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Wed, 30 Apr 2025 23:08:23 +0900 Subject: [PATCH 309/450] refactor(undo-redo): improve undo/redo logic of the ambient light property --- .../EntityProperties/AmbientLightProperty.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp index f93cb1944..5312d85ea 100644 --- a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp @@ -36,16 +36,17 @@ namespace nexo::editor { ImGui::Text("Color"); ImGui::SameLine(); glm::vec4 color = {ambientComponent.color, 1.0f}; - if (ImNexo::ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker)) { - if (!wasUsingLastFrame) { - beforeState = ambientComponent.save(); - wasUsingLastFrame = true; - } - ambientComponent.color = color; + ImNexo::ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker); + + // The user first clicked on the color picker + if (!wasUsingLastFrame && ImGui::IsItemActive()) { + beforeState = ambientComponent.save(); + wasUsingLastFrame = true; } + ambientComponent.color = color; - // Only record the action when the user is done with the color picker - if (wasUsingLastFrame && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + // The user released the color picker + if (wasUsingLastFrame && !ImGui::IsItemActive()) { auto afterState = ambientComponent.save(); auto action = std::make_unique>( entity, beforeState, afterState); From 5fe7c0640f489fbdf79a66f6f7a639ec9bc42b24 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 00:19:15 +0900 Subject: [PATCH 310/450] refactor(undo-redo): moved the ambient property to ImNexo entity properties for better handling --- .../EntityProperties/AmbientLightProperty.cpp | 28 +++------------- editor/src/ImNexo/EntityProperties.cpp | 21 ++++++++++++ editor/src/ImNexo/EntityProperties.hpp | 32 +++++++++++++++++++ 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp index 5312d85ea..d818e98aa 100644 --- a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp @@ -17,6 +17,7 @@ #include "ImNexo/Widgets.hpp" #include "context/actions/EntityActions.hpp" #include "context/ActionManager.hpp" +#include "ImNexo/EntityProperties.hpp" #include namespace nexo::editor { @@ -24,34 +25,15 @@ namespace nexo::editor { void AmbientLightProperty::show(const ecs::Entity entity) { auto& ambientComponent = Application::getEntityComponent(entity); + static components::AmbientLightComponent::Memento beforeState; if (ImNexo::Header("##AmbientNode", "Ambient light")) { - ImGui::Spacing(); - static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; - static bool wasUsingLastFrame = false; - static components::AmbientLightComponent::Memento beforeState; - - ImGui::Text("Color"); - ImGui::SameLine(); - glm::vec4 color = {ambientComponent.color, 1.0f}; - ImNexo::ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker); - - // The user first clicked on the color picker - if (!wasUsingLastFrame && ImGui::IsItemActive()) { - beforeState = ambientComponent.save(); - wasUsingLastFrame = true; - } - ambientComponent.color = color; - - // The user released the color picker - if (wasUsingLastFrame && !ImGui::IsItemActive()) { + ImNexo::InteractionState state = ImNexo::Ambient(ambientComponent, beforeState); + if (state == ImNexo::InteractionState::RELEASED) { 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)); - wasUsingLastFrame = false; beforeState = components::AmbientLightComponent::Memento{}; } diff --git a/editor/src/ImNexo/EntityProperties.cpp b/editor/src/ImNexo/EntityProperties.cpp index c1b53a02e..6f5781dbd 100644 --- a/editor/src/ImNexo/EntityProperties.cpp +++ b/editor/src/ImNexo/EntityProperties.cpp @@ -18,6 +18,7 @@ #include "EntityProperties.hpp" #include "IconsFontAwesome.h" #include "Nexo.hpp" +#include "components/Camera.hpp" #include "context/Selector.hpp" #include "components/Uuid.hpp" #include "components/Light.hpp" @@ -25,6 +26,26 @@ #include "math/Vector.hpp" namespace ImNexo { + + InteractionState Ambient( + nexo::components::AmbientLightComponent &ambientComponent, + nexo::components::AmbientLightComponent::Memento &beforeState + ) { + ImGui::Spacing(); + InteractionState state = InteractionState::NONE; + static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPicker = false; + + ImGui::Text("Color"); + ImGui::SameLine(); + glm::vec4 color = {ambientComponent.color, 1.0f}; + ImNexo::ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker); + + state = trackEditingLifecylce(ambientComponent, beforeState); + ambientComponent.color = color; + return state; + } + void Transform(nexo::components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler) { // Increase cell padding so rows have more space: diff --git a/editor/src/ImNexo/EntityProperties.hpp b/editor/src/ImNexo/EntityProperties.hpp index b484ddd5d..de363dcc5 100644 --- a/editor/src/ImNexo/EntityProperties.hpp +++ b/editor/src/ImNexo/EntityProperties.hpp @@ -17,11 +17,42 @@ #include #include +#include "components/Light.hpp" #include "components/Transform.hpp" #include "components/Camera.hpp" namespace ImNexo { + enum class InteractionState { + NONE, + FIRST_INTERACTION, + USING, + RELEASED + }; + + template + InteractionState trackEditingLifecylce(Component &component, typename Component::Memento &beforeState) + { + InteractionState state = InteractionState::NONE; + static bool wasUsingLastFrame = false; + if (!wasUsingLastFrame && ImGui::IsItemActive()) { + beforeState = component.save(); + wasUsingLastFrame = true; + state = InteractionState::FIRST_INTERACTION; + } else if (ImGui::IsItemActive()) + state = InteractionState::USING; + if (wasUsingLastFrame && !ImGui::IsItemActive()) { + wasUsingLastFrame = false; + state = InteractionState::RELEASED; + } + return state; + } + + InteractionState Ambient( + nexo::components::AmbientLightComponent &ambientComponent, + nexo::components::AmbientLightComponent::Memento &beforeState + ); + /** * @brief Renders and handles the transform component editor UI. * @@ -35,6 +66,7 @@ namespace ImNexo { */ void Transform(nexo::components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler); + /** * @brief Renders and handles the camera component editor UI. * From 9cb1b40abf7be9a144cdc0fd3fbfc579b8e61e69 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 00:25:14 +0900 Subject: [PATCH 311/450] feat(undo-redo): add undo/redo on camera controller modification --- .../EntityProperties/CameraController.cpp | 11 ++++++++++- editor/src/ImNexo/EntityProperties.cpp | 14 +++++++++++--- editor/src/ImNexo/EntityProperties.hpp | 5 ++++- editor/src/ImNexo/Panels.cpp | 4 +++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp index 0c37d4528..0384e86fe 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp @@ -16,18 +16,27 @@ #include "components/Camera.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" +#include "context/ActionManager.hpp" +#include "context/actions/EntityActions.hpp" namespace nexo::editor { void CameraController::show(const ecs::Entity entity) { auto& controllerComponent = Application::getEntityComponent(entity); + static components::PerspectiveCameraController::Memento beforeState; if (ImNexo::Header("##ControllerNode", "Camera Controller")) { ImGui::Spacing(); - ImNexo::CameraController(controllerComponent); + ImNexo::InteractionState state = ImNexo::CameraController(controllerComponent, beforeState); + if (state == ImNexo::InteractionState::RELEASED) { + auto afterState = controllerComponent.save(); + auto action = std::make_unique>(entity, beforeState, afterState); + ActionManager::get().recordAction(std::move(action)); + beforeState = components::PerspectiveCameraController::Memento{}; + } ImGui::TreePop(); } } diff --git a/editor/src/ImNexo/EntityProperties.cpp b/editor/src/ImNexo/EntityProperties.cpp index 6f5781dbd..d03509441 100644 --- a/editor/src/ImNexo/EntityProperties.cpp +++ b/editor/src/ImNexo/EntityProperties.cpp @@ -178,9 +178,13 @@ namespace ImNexo { ImGui::PopStyleVar(); } - void CameraController(nexo::components::PerspectiveCameraController &cameraControllerComponent) - { + InteractionState CameraController( + nexo::components::PerspectiveCameraController &cameraControllerComponent, + nexo::components::PerspectiveCameraController::Memento &beforeState + ) { ImGui::Spacing(); + InteractionState state = InteractionState::NONE; + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); if (ImGui::BeginTable("InspectorControllerTable", 2, @@ -189,11 +193,15 @@ namespace ImNexo { ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - RowDragFloat1("Mouse sensitivity", "", &cameraControllerComponent.mouseSensitivity); + float mouseSensitivity = cameraControllerComponent.mouseSensitivity; + RowDragFloat1("Mouse sensitivity", "", &mouseSensitivity); + state = trackEditingLifecylce(cameraControllerComponent, beforeState); + cameraControllerComponent.mouseSensitivity = mouseSensitivity; ImGui::EndTable(); } ImGui::PopStyleVar(); + return state; } } diff --git a/editor/src/ImNexo/EntityProperties.hpp b/editor/src/ImNexo/EntityProperties.hpp index de363dcc5..1d3d8e3b1 100644 --- a/editor/src/ImNexo/EntityProperties.hpp +++ b/editor/src/ImNexo/EntityProperties.hpp @@ -107,6 +107,9 @@ namespace ImNexo { * * @param cameraControllerComponent Reference to the camera controller component being edited */ - void CameraController(nexo::components::PerspectiveCameraController &cameraControllerComponent); + InteractionState CameraController( + nexo::components::PerspectiveCameraController &cameraControllerComponent, + nexo::components::PerspectiveCameraController::Memento &beforeState + ); } diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index d49f97d39..a43ce35aa 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -21,6 +21,7 @@ #include "Path.hpp" #include "IconsFontAwesome.h" #include "assets/AssetCatalog.hpp" +#include "components/Camera.hpp" #include "components/Uuid.hpp" #include "context/Selector.hpp" #include "utils/EditorProps.hpp" @@ -237,8 +238,9 @@ namespace ImNexo { if (app.m_coordinator->entityHasComponent(camera) && Header("##PerspectiveCameraController", "Camera Controller Component")) { + static nexo::components::PerspectiveCameraController::Memento beforeState{}; auto &cameraControllerComponent = app.m_coordinator->getComponent(camera); - CameraController(cameraControllerComponent); + CameraController(cameraControllerComponent, beforeState); ImGui::TreePop(); } From 5cf3cf02fb5c3291a2c7eb1601f1ae0a44a77b09 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:27:24 +0900 Subject: [PATCH 312/450] feat(undo-redo): add isActive,isActivated,isDeactivaed util method --- editor/src/ImNexo/ImNexo.cpp | 70 ++++++++++++++++++++++++++++++++++++ editor/src/ImNexo/ImNexo.hpp | 37 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 editor/src/ImNexo/ImNexo.cpp create mode 100644 editor/src/ImNexo/ImNexo.hpp diff --git a/editor/src/ImNexo/ImNexo.cpp b/editor/src/ImNexo/ImNexo.cpp new file mode 100644 index 000000000..8d77dc271 --- /dev/null +++ b/editor/src/ImNexo/ImNexo.cpp @@ -0,0 +1,70 @@ +//// ImNexo.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: 01/05/2025 +// Description: Source file for the ImNexo functions +// +/////////////////////////////////////////////////////////////////////////////// + +#include "ImNexo.hpp" + +namespace ImNexo { + bool isItemActive() + { + return g_isItemActive; + } + + void resetItemActiveState() + { + g_isItemActive = false; + } + + void itemIsActive() + { + g_isItemActive = true; + } + + bool isItemActivated() + { + return g_isItemActivated; + } + + void resetItemActivatedState() + { + g_isItemActivated = false; + } + + void itemIsActivated() + { + g_isItemActivated = true; + } + + bool isItemDeactivated() + { + return g_isItemDeactivated; + } + + void resetItemDeactivatedState() + { + g_isItemDeactivated = false; + } + + void itemIsDeactivated() + { + g_isItemDeactivated = true; + } + + void resetItemStates() + { + resetItemActivatedState(); + resetItemActiveState(); + resetItemDeactivatedState(); + } + +} diff --git a/editor/src/ImNexo/ImNexo.hpp b/editor/src/ImNexo/ImNexo.hpp new file mode 100644 index 000000000..bd1b21218 --- /dev/null +++ b/editor/src/ImNexo/ImNexo.hpp @@ -0,0 +1,37 @@ +//// ImNexo.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: Mehdy MORVAN +// Date: 01/05/2025 +// Description: Header for the ImNexo functions +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include +namespace ImNexo { + + inline bool g_isItemActive = false; + inline bool g_isItemActivated = false; + inline bool g_isItemDeactivated = false; + + bool isItemActive(); + void resetItemActiveState(); + void itemIsActive(); + + bool isItemActivated(); + void resetItemActivatedState(); + void itemIsActivated(); + + bool isItemDeactivated(); + void resetItemDeactivatedState(); + void itemIsDeactivated(); + + void resetItemStates(); + +} From 093c4e169be214eb67f6b1978c1996534d026591 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:28:02 +0900 Subject: [PATCH 313/450] feat(undo-redo): use the set item active/deactive/activated in the RowDragFloat function --- editor/src/ImNexo/Components.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp index 1cd21c7ae..e5bad93bf 100644 --- a/editor/src/ImNexo/Components.cpp +++ b/editor/src/ImNexo/Components.cpp @@ -17,6 +17,7 @@ #include "Guard.hpp" #include "Utils.hpp" #include "tinyfiledialogs.h" +#include "ImNexo.hpp" #include #include @@ -285,6 +286,13 @@ namespace ImNexo { slider.textColor); modified |= changed; + if (ImGui::IsItemActive()) + itemIsActive(); + if (ImGui::IsItemActivated()) + itemIsActivated(); + if (ImGui::IsItemDeactivated()) { + itemIsDeactivated(); + } } return modified; From 5595a28ad36a11d47246c3b2e0dc17f437a08fd8 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:28:31 +0900 Subject: [PATCH 314/450] refactor(undo-redo): not passing the memento anymore to CameraController function --- editor/src/ImNexo/Panels.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index a43ce35aa..27bc238b7 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -238,9 +238,8 @@ namespace ImNexo { if (app.m_coordinator->entityHasComponent(camera) && Header("##PerspectiveCameraController", "Camera Controller Component")) { - static nexo::components::PerspectiveCameraController::Memento beforeState{}; auto &cameraControllerComponent = app.m_coordinator->getComponent(camera); - CameraController(cameraControllerComponent, beforeState); + CameraController(cameraControllerComponent); ImGui::TreePop(); } From f7e4732fc9d03a907f3c1bcf8423ac328638ab35 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:28:59 +0900 Subject: [PATCH 315/450] feat(undo-redo): add active/deactive/activated function to color editor --- editor/src/ImNexo/Widgets.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/editor/src/ImNexo/Widgets.cpp b/editor/src/ImNexo/Widgets.cpp index 7e375d5a8..5b4ed7e9d 100644 --- a/editor/src/ImNexo/Widgets.cpp +++ b/editor/src/ImNexo/Widgets.cpp @@ -29,6 +29,7 @@ #include "components/Transform.hpp" #include "components/Uuid.hpp" #include "context/Selector.hpp" +#include "ImNexo.hpp" namespace ImNexo { @@ -84,6 +85,12 @@ namespace ImNexo { ImGui::Spacing(); colorModified = ImGui::ColorPicker4(colorPickerInline.c_str(), reinterpret_cast(selectedEntityColor), *colorPickerMode); + if (ImGui::IsItemActive()) + itemIsActive(); + if (ImGui::IsItemActivated()) + itemIsActivated(); + if (ImGui::IsItemDeactivated()) + itemIsDeactivated(); } return colorModified; } From 3767086944d84695c4ba9f13e13f5564d7dbe7fa Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:29:29 +0900 Subject: [PATCH 316/450] refactor(undo-redo): not passing the memento anymore + feat: add camera property --- editor/src/ImNexo/EntityProperties.cpp | 31 +++++++--------------- editor/src/ImNexo/EntityProperties.hpp | 36 +++----------------------- 2 files changed, 12 insertions(+), 55 deletions(-) diff --git a/editor/src/ImNexo/EntityProperties.cpp b/editor/src/ImNexo/EntityProperties.cpp index d03509441..f42e03425 100644 --- a/editor/src/ImNexo/EntityProperties.cpp +++ b/editor/src/ImNexo/EntityProperties.cpp @@ -12,6 +12,7 @@ // /////////////////////////////////////////////////////////////////////////////// +#include "ImNexo/ImNexo.hpp" #include "Widgets.hpp" #include "Guard.hpp" #include "Utils.hpp" @@ -27,12 +28,9 @@ namespace ImNexo { - InteractionState Ambient( - nexo::components::AmbientLightComponent &ambientComponent, - nexo::components::AmbientLightComponent::Memento &beforeState - ) { + void Ambient(nexo::components::AmbientLightComponent &ambientComponent) + { ImGui::Spacing(); - InteractionState state = InteractionState::NONE; static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; static bool showColorPicker = false; @@ -40,10 +38,7 @@ namespace ImNexo { ImGui::SameLine(); glm::vec4 color = {ambientComponent.color, 1.0f}; ImNexo::ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker); - - state = trackEditingLifecylce(ambientComponent, beforeState); ambientComponent.color = color; - return state; } void Transform(nexo::components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler) @@ -99,21 +94,19 @@ namespace ImNexo { std::vector badgeColors; std::vector textBadgeColors; - const bool disabled = cameraComponent.viewportLocked; + const bool disabled = !cameraComponent.viewportLocked; if (disabled) ImGui::BeginDisabled(); - if (RowDragFloat2("Viewport size", "W", "H", &viewPort.x, -FLT_MAX, FLT_MAX, 1.0f, badgeColors, textBadgeColors, disabled)) - { - if (!cameraComponent.viewportLocked) + 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(); 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; } @@ -178,13 +171,9 @@ namespace ImNexo { ImGui::PopStyleVar(); } - InteractionState CameraController( - nexo::components::PerspectiveCameraController &cameraControllerComponent, - nexo::components::PerspectiveCameraController::Memento &beforeState - ) { + void CameraController(nexo::components::PerspectiveCameraController &cameraControllerComponent) + { ImGui::Spacing(); - InteractionState state = InteractionState::NONE; - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); if (ImGui::BeginTable("InspectorControllerTable", 2, @@ -195,13 +184,11 @@ namespace ImNexo { float mouseSensitivity = cameraControllerComponent.mouseSensitivity; RowDragFloat1("Mouse sensitivity", "", &mouseSensitivity); - state = trackEditingLifecylce(cameraControllerComponent, beforeState); cameraControllerComponent.mouseSensitivity = mouseSensitivity; ImGui::EndTable(); } ImGui::PopStyleVar(); - return state; } } diff --git a/editor/src/ImNexo/EntityProperties.hpp b/editor/src/ImNexo/EntityProperties.hpp index 1d3d8e3b1..d3c65c62f 100644 --- a/editor/src/ImNexo/EntityProperties.hpp +++ b/editor/src/ImNexo/EntityProperties.hpp @@ -20,38 +20,11 @@ #include "components/Light.hpp" #include "components/Transform.hpp" #include "components/Camera.hpp" +#include "ImNexo.hpp" namespace ImNexo { - enum class InteractionState { - NONE, - FIRST_INTERACTION, - USING, - RELEASED - }; - - template - InteractionState trackEditingLifecylce(Component &component, typename Component::Memento &beforeState) - { - InteractionState state = InteractionState::NONE; - static bool wasUsingLastFrame = false; - if (!wasUsingLastFrame && ImGui::IsItemActive()) { - beforeState = component.save(); - wasUsingLastFrame = true; - state = InteractionState::FIRST_INTERACTION; - } else if (ImGui::IsItemActive()) - state = InteractionState::USING; - if (wasUsingLastFrame && !ImGui::IsItemActive()) { - wasUsingLastFrame = false; - state = InteractionState::RELEASED; - } - return state; - } - - InteractionState Ambient( - nexo::components::AmbientLightComponent &ambientComponent, - nexo::components::AmbientLightComponent::Memento &beforeState - ); + void Ambient(nexo::components::AmbientLightComponent &ambientComponent); /** * @brief Renders and handles the transform component editor UI. @@ -107,9 +80,6 @@ namespace ImNexo { * * @param cameraControllerComponent Reference to the camera controller component being edited */ - InteractionState CameraController( - nexo::components::PerspectiveCameraController &cameraControllerComponent, - nexo::components::PerspectiveCameraController::Memento &beforeState - ); + void CameraController(nexo::components::PerspectiveCameraController &cameraControllerComponent); } From ed6ce31a6f4c96bc6967db3a4ee4f7089cfd1017 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:30:06 +0900 Subject: [PATCH 317/450] refactor(undo-redo): now use the new isActive... methods to register the action --- .../EntityProperties/AmbientLightProperty.cpp | 8 ++++++-- .../EntityProperties/CameraController.cpp | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp index d818e98aa..121fd95c1 100644 --- a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp @@ -29,8 +29,12 @@ namespace nexo::editor { if (ImNexo::Header("##AmbientNode", "Ambient light")) { - ImNexo::InteractionState state = ImNexo::Ambient(ambientComponent, beforeState); - if (state == ImNexo::InteractionState::RELEASED) { + auto ambientComponentCopy = ambientComponent; + ImNexo::resetItemStates(); + ImNexo::Ambient(ambientComponent); + if (ImNexo::isItemActivated()) { + beforeState = ambientComponentCopy.save(); + } else if (ImNexo::isItemDeactivated()) { auto afterState = ambientComponent.save(); auto action = std::make_unique>(entity, beforeState, afterState); ActionManager::get().recordAction(std::move(action)); diff --git a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp index 0384e86fe..f06daa688 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "CameraController.hpp" +#include "ImNexo/ImNexo.hpp" #include "components/Camera.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" @@ -29,9 +30,12 @@ namespace nexo::editor { if (ImNexo::Header("##ControllerNode", "Camera Controller")) { ImGui::Spacing(); - - ImNexo::InteractionState state = ImNexo::CameraController(controllerComponent, beforeState); - if (state == ImNexo::InteractionState::RELEASED) { + auto controllerComponentCopy = controllerComponent; + ImNexo::resetItemStates(); + ImNexo::CameraController(controllerComponent); + if (ImNexo::isItemActivated()) { + beforeState = controllerComponentCopy.save(); + } else if (ImNexo::isItemDeactivated()) { auto afterState = controllerComponent.save(); auto action = std::make_unique>(entity, beforeState, afterState); ActionManager::get().recordAction(std::move(action)); From e864589cec0e01c63e441bdaee2c9e52c9b9ed8a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:30:25 +0900 Subject: [PATCH 318/450] feat(undo-redo): add undo/redo on camera property --- .../EntityProperties/CameraProperty.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp index 0f58fbcbb..1f2254fbe 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp @@ -13,19 +13,34 @@ /////////////////////////////////////////////////////////////////////////////// #include "CameraProperty.hpp" +#include "ImNexo/ImNexo.hpp" #include "components/Camera.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" +#include "context/ActionManager.hpp" +#include "context/actions/EntityActions.hpp" namespace nexo::editor { void CameraProperty::show(const ecs::Entity entity) { auto& cameraComponent = Application::getEntityComponent(entity); + static components::CameraComponent::Memento beforeState; if (ImNexo::Header("##CameraNode", "Camera")) { + auto cameraComponentCopy = cameraComponent; + ImNexo::resetItemStates(); ImNexo::Camera(cameraComponent); + if (ImNexo::isItemActivated()) { + beforeState = cameraComponentCopy.save(); + } else if (ImNexo::isItemDeactivated()) { + cameraComponent = cameraComponentCopy; + auto afterState = cameraComponent.save(); + auto action = std::make_unique>(entity, beforeState, afterState); + ActionManager::get().recordAction(std::move(action)); + beforeState = components::CameraComponent::Memento{}; + } ImGui::TreePop(); } } From 0b4823a61bace7781b96685723f3b139949efc2d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:30:44 +0900 Subject: [PATCH 319/450] fix(undo-redo): now block resize when the camera viewport is locked --- editor/src/DocumentWindows/EditorScene/Show.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index d95498c17..70826fb68 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -100,7 +100,7 @@ namespace nexo::editor { const glm::vec2 renderTargetSize = cameraComponent.m_renderTarget->getSize(); // Resize handling - if ((m_contentSize.x > 0 && m_contentSize.y > 0) + 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), From 3229411275910822d31a0ce23e0febe67e0cade4 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:31:07 +0900 Subject: [PATCH 320/450] chore(undo-redo): add source file --- editor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 6636a166d..e2496b699 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -26,6 +26,7 @@ set(SRCS editor/src/ImNexo/Panels.cpp editor/src/ImNexo/Utils.cpp editor/src/ImNexo/Widgets.cpp + editor/src/ImNexo/ImNexo.cpp editor/src/utils/ScenePreview.cpp editor/src/utils/Config.cpp editor/src/utils/String.cpp From 96bf24ccc67e4fc86f2ca39b077ca87c1db4e0f5 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:31:43 +0900 Subject: [PATCH 321/450] fix(undo-redo): now call the resize func in the restore func of the camera component --- engine/src/components/Camera.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 3e4b8cb24..811ff3968 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -20,6 +20,7 @@ #include "ecs/Entity.hpp" #include #include +#include namespace nexo::components { @@ -119,8 +120,11 @@ namespace nexo::components { void restore(const Memento& memento) { - width = memento.width; - height = memento.height; + if (width != memento.width || height != memento.height) { + width = memento.width; + height = memento.height; + resize(width, height); + } viewportLocked = memento.viewportLocked; fov = memento.fov; nearPlane = memento.nearPlane; From 85a05af2c6ee4aae45e29749b22ad82ed3ca4b01 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:42:55 +0900 Subject: [PATCH 322/450] fix(undo-redo): remove useless copy --- editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp index 1f2254fbe..20d95a848 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp @@ -35,7 +35,6 @@ namespace nexo::editor { if (ImNexo::isItemActivated()) { beforeState = cameraComponentCopy.save(); } else if (ImNexo::isItemDeactivated()) { - cameraComponent = cameraComponentCopy; auto afterState = cameraComponent.save(); auto action = std::make_unique>(entity, beforeState, afterState); ActionManager::get().recordAction(std::move(action)); From 383945cb421a8aeb90d2c6db0847e02bb6dee390 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:43:20 +0900 Subject: [PATCH 323/450] feat(undo-redo): add camera target property --- .../EntityProperties/CameraTarget.cpp | 17 +++++++++++++++-- .../DocumentWindows/InspectorWindow/Init.cpp | 3 +++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp index e25fc732f..b6f74b76a 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp @@ -14,20 +14,33 @@ #include "CameraTarget.hpp" #include "Definitions.hpp" +#include "ImNexo/ImNexo.hpp" #include "components/Camera.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" +#include "context/ActionManager.hpp" namespace nexo::editor { void CameraTarget::show(const ecs::Entity entity) { auto& targetComponent = Application::getEntityComponent(entity); + static components::PerspectiveCameraTarget::Memento beforeState; - if (ImNexo::Header("##ControllerNode", "Camera Controller")) + if (ImNexo::Header("##TargetNode", "Camera Target")) { + auto targetComponentCopy = targetComponent; ImGui::Spacing(); - ImNexo::CameraTarget(targetComponent), + ImNexo::resetItemActiveState(); + ImNexo::CameraTarget(targetComponent); + if (ImNexo::isItemActivated()) { + beforeState = targetComponentCopy.save(); + } else if (ImNexo::isItemDeactivated()) { + auto afterState = targetComponent.save(); + auto action = std::make_unique>(entity, beforeState, afterState); + ActionManager::get().recordAction(std::move(action)); + beforeState = components::PerspectiveCameraTarget::Memento{}; + } ImGui::TreePop(); } } diff --git a/editor/src/DocumentWindows/InspectorWindow/Init.cpp b/editor/src/DocumentWindows/InspectorWindow/Init.cpp index 1f274327b..87f3d27be 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Init.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Init.cpp @@ -21,6 +21,8 @@ #include "../EntityProperties/SpotLightProperty.hpp" #include "../EntityProperties/CameraProperty.hpp" #include "../EntityProperties/CameraController.hpp" +#include "../EntityProperties/CameraTarget.hpp" +#include "components/Camera.hpp" namespace nexo::editor { @@ -34,5 +36,6 @@ namespace nexo::editor { registerProperty(); registerProperty(); registerProperty(); + registerProperty(); } } From 3bcefc98e03f6edb84f1ec6b62c31c366d8aac67 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 17:43:54 +0900 Subject: [PATCH 324/450] feat(undo-reo): add active/activated/deactivated to RowEntityDropdown func --- editor/src/ImNexo/Components.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp index e5bad93bf..4f740a904 100644 --- a/editor/src/ImNexo/Components.cpp +++ b/editor/src/ImNexo/Components.cpp @@ -250,6 +250,12 @@ namespace ImNexo { } ImGui::EndCombo(); } + if (ImGui::IsItemActive()) + itemIsActive(); + if (ImGui::IsItemActivated()) + itemIsActivated(); + if (ImGui::IsItemDeactivated()) + itemIsDeactivated(); return changed; } @@ -290,9 +296,8 @@ namespace ImNexo { itemIsActive(); if (ImGui::IsItemActivated()) itemIsActivated(); - if (ImGui::IsItemDeactivated()) { + if (ImGui::IsItemDeactivated()) itemIsDeactivated(); - } } return modified; From b4220097c7e32020fa54529bcdee19a4eb73139b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 18:17:00 +0900 Subject: [PATCH 325/450] refactor(undo-redo): made the specific item state reset func internal --- .../src/DocumentWindows/EntityProperties/CameraTarget.cpp | 2 +- editor/src/ImNexo/ImNexo.cpp | 6 +++--- editor/src/ImNexo/ImNexo.hpp | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp index b6f74b76a..4116ca293 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp @@ -31,7 +31,7 @@ namespace nexo::editor { { auto targetComponentCopy = targetComponent; ImGui::Spacing(); - ImNexo::resetItemActiveState(); + ImNexo::resetItemStates(); ImNexo::CameraTarget(targetComponent); if (ImNexo::isItemActivated()) { beforeState = targetComponentCopy.save(); diff --git a/editor/src/ImNexo/ImNexo.cpp b/editor/src/ImNexo/ImNexo.cpp index 8d77dc271..bb029fa71 100644 --- a/editor/src/ImNexo/ImNexo.cpp +++ b/editor/src/ImNexo/ImNexo.cpp @@ -20,7 +20,7 @@ namespace ImNexo { return g_isItemActive; } - void resetItemActiveState() + static void resetItemActiveState() { g_isItemActive = false; } @@ -35,7 +35,7 @@ namespace ImNexo { return g_isItemActivated; } - void resetItemActivatedState() + static void resetItemActivatedState() { g_isItemActivated = false; } @@ -50,7 +50,7 @@ namespace ImNexo { return g_isItemDeactivated; } - void resetItemDeactivatedState() + static void resetItemDeactivatedState() { g_isItemDeactivated = false; } diff --git a/editor/src/ImNexo/ImNexo.hpp b/editor/src/ImNexo/ImNexo.hpp index bd1b21218..f927b2976 100644 --- a/editor/src/ImNexo/ImNexo.hpp +++ b/editor/src/ImNexo/ImNexo.hpp @@ -21,15 +21,12 @@ namespace ImNexo { inline bool g_isItemDeactivated = false; bool isItemActive(); - void resetItemActiveState(); void itemIsActive(); bool isItemActivated(); - void resetItemActivatedState(); void itemIsActivated(); bool isItemDeactivated(); - void resetItemDeactivatedState(); void itemIsDeactivated(); void resetItemStates(); From 7d72b92775344ae259b7d5b08dddbaa8b8ac8cef Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 18:17:25 +0900 Subject: [PATCH 326/450] feat(undo-redo): add undo/redo on directional light property --- .../DirectionalLightProperty.cpp | 36 ++++++++----------- editor/src/ImNexo/EntityProperties.cpp | 27 ++++++++++++++ editor/src/ImNexo/EntityProperties.hpp | 2 ++ 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp index 3d35e8307..ced02b3ef 100644 --- a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp @@ -13,40 +13,32 @@ /////////////////////////////////////////////////////////////////////////////// #include "DirectionalLightProperty.hpp" +#include "ImNexo/EntityProperties.hpp" +#include "ImNexo/ImNexo.hpp" #include "components/Light.hpp" #include "ImNexo/Widgets.hpp" +#include "context/ActionManager.hpp" namespace nexo::editor { void DirectionalLightProperty::show(const ecs::Entity entity) { auto& directionalComponent = Application::getEntityComponent(entity); + static components::DirectionalLightComponent::Memento beforeState; if (ImNexo::Header("##DirectionalNode", "Directional light")) { - ImGui::Spacing(); - static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; - ImGui::Text("Color"); - ImGui::SameLine(); - glm::vec4 color = {directionalComponent.color, 1.0f}; - ImNexo::ColorEditor("##ColorEditor Directional light", &color, &colorPickerMode, &showColorPicker); - 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); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - ImNexo::RowDragFloat3("Direction", "X", "Y", "Z", &directionalComponent.direction.x); - - ImGui::EndTable(); + auto directionalComponentCopy = directionalComponent; + ImNexo::resetItemStates(); + ImNexo::DirectionalLight(directionalComponent); + if (ImNexo::isItemActivated()) { + beforeState = directionalComponentCopy.save(); + } else if (ImNexo::isItemDeactivated()) { + auto afterState = directionalComponent.save(); + auto action = std::make_unique>(entity, beforeState, afterState); + ActionManager::get().recordAction(std::move(action)); + beforeState = components::DirectionalLightComponent::Memento{}; } - ImGui::PopStyleVar(); ImGui::TreePop(); } } diff --git a/editor/src/ImNexo/EntityProperties.cpp b/editor/src/ImNexo/EntityProperties.cpp index f42e03425..f10484416 100644 --- a/editor/src/ImNexo/EntityProperties.cpp +++ b/editor/src/ImNexo/EntityProperties.cpp @@ -41,6 +41,33 @@ namespace ImNexo { ambientComponent.color = color; } + void DirectionalLight(nexo::components::DirectionalLightComponent &directionalComponent) + { + ImGui::Spacing(); + static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPicker = false; + ImGui::Text("Color"); + ImGui::SameLine(); + glm::vec4 color = {directionalComponent.color, 1.0f}; + ImNexo::ColorEditor("##ColorEditor Directional light", &color, &colorPickerMode, &showColorPicker); + 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); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + ImNexo::RowDragFloat3("Direction", "X", "Y", "Z", &directionalComponent.direction.x); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + void Transform(nexo::components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler) { // Increase cell padding so rows have more space: diff --git a/editor/src/ImNexo/EntityProperties.hpp b/editor/src/ImNexo/EntityProperties.hpp index d3c65c62f..56b8ace86 100644 --- a/editor/src/ImNexo/EntityProperties.hpp +++ b/editor/src/ImNexo/EntityProperties.hpp @@ -26,6 +26,8 @@ namespace ImNexo { void Ambient(nexo::components::AmbientLightComponent &ambientComponent); + void DirectionalLight(nexo::components::DirectionalLightComponent &directionalComponent); + /** * @brief Renders and handles the transform component editor UI. * From 7e95ea24731a1c79a060233f22c06a472cee5929 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 19:12:00 +0900 Subject: [PATCH 327/450] feat(undo-redo): add undo/redo for the point light property --- .../EntityProperties/PointLightProperty.cpp | 57 ++++++++----------- editor/src/ImNexo/EntityProperties.cpp | 46 +++++++++++++++ editor/src/ImNexo/EntityProperties.hpp | 2 + 3 files changed, 71 insertions(+), 34 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp index e1c5a1683..e861cb61f 100644 --- a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp @@ -13,10 +13,14 @@ /////////////////////////////////////////////////////////////////////////////// #include "PointLightProperty.hpp" +#include "ImNexo/EntityProperties.hpp" +#include "ImNexo/ImNexo.hpp" #include "components/Light.hpp" #include "components/Transform.hpp" +#include "context/actions/EntityActions.hpp" #include "math/Light.hpp" #include "ImNexo/Widgets.hpp" +#include "context/ActionManager.hpp" namespace nexo::editor { @@ -25,43 +29,28 @@ namespace nexo::editor { auto& pointComponent = nexo::Application::getEntityComponent(entity); auto &transform = Application::getEntityComponent(entity); + static components::PointLightComponent::Memento beforeStatePoint; + static components::TransformComponent::Memento beforeStateTransform; + if (ImNexo::Header("##PointNode", "Point light")) { - ImGui::Spacing(); - static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; - ImGui::Text("Color"); - ImGui::SameLine(); - glm::vec4 color = {pointComponent.color, 1.0f}; - ImNexo::ColorEditor("##ColorEditor Point light", &color, &colorPickerMode, &showColorPicker); - 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); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - ImNexo::RowDragFloat3("Position", "X", "Y", "Z", &transform.pos.x); - - ImGui::EndTable(); - } - - ImGui::Spacing(); - ImGui::Text("Distance"); - ImGui::SameLine(); - if (ImGui::DragFloat("##DistanceSlider", &pointComponent.maxDistance, 1.0f, 1.0f, 3250.0f)) - { - // Recompute the attenuation from the distance - auto [lin, quad] = math::computeAttenuationFromDistance(pointComponent.maxDistance); - pointComponent.constant = 1.0f; - pointComponent.linear = lin; - pointComponent.quadratic = quad; + auto pointComponentCopy = pointComponent; + auto transformComponentCopy = transform; + ImNexo::resetItemStates(); + ImNexo::PointLight(pointComponent, transform); + if (ImNexo::isItemActivated()) { + beforeStatePoint = pointComponentCopy.save(); + beforeStateTransform = transformComponentCopy.save(); + } else if (ImNexo::isItemDeactivated()) { + auto afterStatePoint = pointComponent.save(); + auto afterStateTransform = transform.save(); + auto actionGroup = ActionManager::get().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::PopStyleVar(); ImGui::TreePop(); } } diff --git a/editor/src/ImNexo/EntityProperties.cpp b/editor/src/ImNexo/EntityProperties.cpp index f10484416..18439f2de 100644 --- a/editor/src/ImNexo/EntityProperties.cpp +++ b/editor/src/ImNexo/EntityProperties.cpp @@ -25,6 +25,7 @@ #include "components/Light.hpp" #include "components/Transform.hpp" #include "math/Vector.hpp" +#include "math/Light.hpp" namespace ImNexo { @@ -68,6 +69,51 @@ namespace ImNexo { ImGui::PopStyleVar(); } + void PointLight( + nexo::components::PointLightComponent &pointComponent, + nexo::components::TransformComponent &pointTransform + ) { + ImGui::Spacing(); + static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; + static bool showColorPicker = false; + ImGui::Text("Color"); + ImGui::SameLine(); + glm::vec4 color = {pointComponent.color, 1.0f}; + ImNexo::ColorEditor("##ColorEditor Point light", &color, &colorPickerMode, &showColorPicker); + 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); + 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", &pointTransform.pos.x); + ImGui::EndTable(); + } + + ImGui::Spacing(); + ImGui::Text("Distance"); + 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; + pointComponent.quadratic = quad; + } + if (ImGui::IsItemActive()) + itemIsActive(); + if (ImGui::IsItemActivated()) + itemIsActivated(); + if (ImGui::IsItemDeactivated()) + itemIsDeactivated(); + ImGui::PopStyleVar(); + } + void Transform(nexo::components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler) { // Increase cell padding so rows have more space: diff --git a/editor/src/ImNexo/EntityProperties.hpp b/editor/src/ImNexo/EntityProperties.hpp index 56b8ace86..18a28e87f 100644 --- a/editor/src/ImNexo/EntityProperties.hpp +++ b/editor/src/ImNexo/EntityProperties.hpp @@ -28,6 +28,8 @@ namespace ImNexo { void DirectionalLight(nexo::components::DirectionalLightComponent &directionalComponent); + void PointLight(nexo::components::PointLightComponent &pointComponent, nexo::components::TransformComponent &pointTransform); + /** * @brief Renders and handles the transform component editor UI. * From f48a0494df2c20a2091668424d469be0389b97e7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 19:42:44 +0900 Subject: [PATCH 328/450] feat(undo-redo): add undo/redo for render component --- .../EntityProperties/RenderProperty.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index 11fc5c62e..3a8d87c7d 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -19,11 +19,13 @@ #include "Application.hpp" #include "Framebuffer.hpp" #include "components/Light.hpp" +#include "context/actions/EntityActions.hpp" #include "utils/ScenePreview.hpp" #include "components/Camera.hpp" #include "components/Render.hpp" #include "DocumentWindows/InspectorWindow/InspectorWindow.hpp" #include "DocumentWindows/MaterialInspector/MaterialInspector.hpp" +#include "context/ActionManager.hpp" #include "ImNexo/Panels.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/Components.hpp" @@ -153,8 +155,13 @@ namespace nexo::editor { ImGui::Text("Hide"); ImGui::SameLine(0, 12); bool hidden = !renderComponent.isRendered; - ImGui::Checkbox("##HideCheckBox", &hidden); - renderComponent.isRendered = !hidden; + if (ImGui::Checkbox("##HideCheckBox", &hidden)) { + auto beforeState = renderComponent.save(); + renderComponent.isRendered = !hidden; + auto afterState = renderComponent.save(); + auto action = std::make_unique>(entity, beforeState, afterState); + ActionManager::get().recordAction(std::move(action)); + } ImNexo::ToggleButtonWithSeparator("Material", §ionOpen); static std::shared_ptr framebuffer = nullptr; From 6d91704e0874992e7572499c27cc93898fc22318 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 19:48:31 +0900 Subject: [PATCH 329/450] feat(undo-redo): add undo/redo for spot light property --- .../EntityProperties/SpotLightProperty.cpp | 70 +++++++------------ editor/src/ImNexo/EntityProperties.cpp | 51 ++++++++++++++ editor/src/ImNexo/EntityProperties.hpp | 3 + 3 files changed, 78 insertions(+), 46 deletions(-) diff --git a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp index 0c9a8a4d5..ec56a0a02 100644 --- a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp @@ -13,9 +13,14 @@ /////////////////////////////////////////////////////////////////////////////// #include "SpotLightProperty.hpp" +#include "ImNexo/EntityProperties.hpp" +#include "ImNexo/ImNexo.hpp" #include "components/Light.hpp" +#include "components/Transform.hpp" +#include "context/actions/EntityActions.hpp" #include "math/Light.hpp" #include "ImNexo/Widgets.hpp" +#include "context/ActionManager.hpp" namespace nexo::editor { @@ -24,55 +29,28 @@ namespace nexo::editor { 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")) { - ImGui::Spacing(); - static ImGuiColorEditFlags colorPickerMode = ImGuiColorEditFlags_PickerHueBar; - static bool showColorPicker = false; - ImGui::Text("Color"); - ImGui::SameLine(); - glm::vec4 color = {spotComponent.color, 1.0f}; - ImNexo::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); - - ImNexo::RowDragFloat3("Direction", "X", "Y", "Z", &spotComponent.direction.x, -FLT_MAX, FLT_MAX, 0.1f); - ImNexo::RowDragFloat3("Position", "X", "Y", "Z", &transformComponent.pos.x, -FLT_MAX, FLT_MAX, 0.1f); - - - ImGui::EndTable(); + ImNexo::resetItemStates(); + auto spotComponentCopy = spotComponent; + auto transformComponentCopy = transformComponent; + ImNexo::SpotLight(spotComponent, transformComponent); + if (ImNexo::isItemActivated()) { + beforeStateSpot = spotComponentCopy.save(); + beforeStateTransform = transformComponentCopy.save(); + } else if (ImNexo::isItemDeactivated()) { + auto afterStateSpot = spotComponent.save(); + auto afterStateTransform = transformComponent.save(); + auto actionGroup = ActionManager::get().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)); } - - if (ImGui::BeginTable("InspectorCutOffSpotTable", 2, ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - - if (ImNexo::RowDragFloat1("Distance", "", &spotComponent.maxDistance, 1.0f, 3250.0f, 1.0f)) - { - auto [lin, quad] = 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 (ImNexo::RowDragFloat1("Inner cut off", "", &innerCutOffDegrees, 0.0f, outerCutOffDegrees, 0.5f)) - spotComponent.cutOff = glm::cos(glm::radians(innerCutOffDegrees)); - if (ImNexo::RowDragFloat1("Outer cut off", "", &outerCutOffDegrees, innerCutOffDegrees, 90.0f, 0.5f)) - spotComponent.outerCutoff = glm::cos(glm::radians(outerCutOffDegrees)); - - ImGui::EndTable(); - } - - ImGui::PopStyleVar(); ImGui::TreePop(); } } diff --git a/editor/src/ImNexo/EntityProperties.cpp b/editor/src/ImNexo/EntityProperties.cpp index 18439f2de..9eee42f5f 100644 --- a/editor/src/ImNexo/EntityProperties.cpp +++ b/editor/src/ImNexo/EntityProperties.cpp @@ -114,6 +114,57 @@ namespace ImNexo { ImGui::PopStyleVar(); } + 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); + ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); + + 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 (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)); + if (RowDragFloat1("Outer cut off", "", &outerCutOffDegrees, innerCutOffDegrees, 90.0f, 0.5f)) + spotComponent.outerCutoff = glm::cos(glm::radians(outerCutOffDegrees)); + + ImGui::EndTable(); + } + + ImGui::PopStyleVar(); + } + void Transform(nexo::components::TransformComponent &transformComponent, glm::vec3 &lastDisplayedEuler) { // Increase cell padding so rows have more space: diff --git a/editor/src/ImNexo/EntityProperties.hpp b/editor/src/ImNexo/EntityProperties.hpp index 18a28e87f..da4401373 100644 --- a/editor/src/ImNexo/EntityProperties.hpp +++ b/editor/src/ImNexo/EntityProperties.hpp @@ -30,6 +30,9 @@ namespace ImNexo { 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 transform component editor UI. * From 338555ddb7b776af2700f9c6aca666432ab8b06c Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 19:51:09 +0900 Subject: [PATCH 330/450] feat(undo-redo): add undo/redo for transform property --- .../EntityProperties/TransformProperty.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp index b67a5f900..50203a7cc 100644 --- a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp @@ -17,7 +17,10 @@ #include "TransformProperty.hpp" #include "ImNexo/Elements.hpp" #include "ImNexo/EntityProperties.hpp" +#include "ImNexo/ImNexo.hpp" #include "components/Light.hpp" +#include "components/Transform.hpp" +#include "context/ActionManager.hpp" namespace nexo::editor { @@ -28,10 +31,20 @@ namespace nexo::editor { return; auto& transformComponent = Application::getEntityComponent(entity); static glm::vec3 lastDisplayedEuler(0.0f); + static components::TransformComponent::Memento beforeState; if (ImNexo::Header("##TransformNode", "Transform Component")) { + auto transformComponentCopy = transformComponent; + ImNexo::resetItemStates(); ImNexo::Transform(transformComponent, lastDisplayedEuler); + if (ImNexo::isItemActivated()) { + beforeState = transformComponentCopy.save(); + } else if (ImNexo::isItemDeactivated()) { + auto afterState = transformComponent.save(); + auto action = std::make_unique>(entity, beforeState, afterState); + ActionManager::get().recordAction(std::move(action)); + } ImGui::TreePop(); } } From af251b1240816849951a00d0a9fa9d8eef5101ec Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 20:21:34 +0900 Subject: [PATCH 331/450] feat(undo-redo): add undo/redo on entity deletion in the scene tree --- .../SceneTreeWindow/Selection.cpp | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp index a70f826ca..fb93115b4 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp @@ -17,6 +17,10 @@ #include "LightFactory.hpp" #include "utils/EditorProps.hpp" #include "ImNexo/Panels.hpp" +#include "context/ActionManager.hpp" +#include "context/actions/EntityActions.hpp" + +#include namespace nexo::editor { @@ -37,13 +41,19 @@ namespace nexo::editor { { if (multipleSelected) { // Delete all selected entities + auto actionGroup = ActionManager::get().createActionGroup(); for (const auto& entityId : selectedEntities) { + auto deleteAction = std::make_unique(entityId); + actionGroup->addAction(std::move(deleteAction)); app.deleteEntity(entityId); } + ActionManager::get().recordAction(std::move(actionGroup)); selector.clearSelection(); } else { // Delete just this entity selector.clearSelection(); + auto action = std::make_unique(obj.data.entity); + ActionManager::get().recordAction(std::move(action)); app.deleteEntity(obj.data.entity); } } @@ -59,20 +69,26 @@ namespace nexo::editor { bool multipleSelected = selectedEntities.size() > 1; std::string deleteMenuText = multipleSelected ? - "Delete Selected Cameras (" + std::to_string(selectedEntities.size()) + ")" : + "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : "Delete Camera"; if (ImGui::MenuItem(deleteMenuText.c_str())) { if (multipleSelected) { - // Delete all selected cameras + // Delete all selected entities + auto actionGroup = ActionManager::get().createActionGroup(); for (const auto& entityId : selectedEntities) { + auto deleteAction = std::make_unique(entityId); + actionGroup->addAction(std::move(deleteAction)); app.deleteEntity(entityId); } + ActionManager::get().recordAction(std::move(actionGroup)); selector.clearSelection(); } else { - // Delete just this camera + // Delete just this entity selector.clearSelection(); + auto action = std::make_unique(obj.data.entity); + ActionManager::get().recordAction(std::move(action)); app.deleteEntity(obj.data.entity); } } @@ -103,20 +119,26 @@ namespace nexo::editor { bool multipleSelected = selectedEntities.size() > 1; std::string menuText = multipleSelected ? - "Delete Selected Lights (" + std::to_string(selectedEntities.size()) + ")" : + "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : "Delete Light"; if (ImGui::MenuItem(menuText.c_str())) { if (multipleSelected) { - // Delete all selected lights + // Delete all selected entities + auto actionGroup = ActionManager::get().createActionGroup(); for (const auto& entityId : selectedEntities) { + auto deleteAction = std::make_unique(entityId); + actionGroup->addAction(std::move(deleteAction)); app.deleteEntity(entityId); } + ActionManager::get().recordAction(std::move(actionGroup)); selector.clearSelection(); } else { - // Delete just this light + // Delete just this entity selector.clearSelection(); + auto action = std::make_unique(obj.data.entity); + ActionManager::get().recordAction(std::move(action)); app.deleteEntity(obj.data.entity); } } From 87523abd24f0c08a73d93d9aea88a6d6be58a675 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 22:46:10 +0900 Subject: [PATCH 332/450] feat(undo-redo): add func to duplicate an entity --- engine/src/ecs/ComponentArray.hpp | 10 ++++++++++ engine/src/ecs/Components.hpp | 32 +++++++++++++++++++++++++++++++ engine/src/ecs/Coordinator.cpp | 16 ++++++++++++++++ engine/src/ecs/Coordinator.hpp | 2 ++ 4 files changed, 60 insertions(+) diff --git a/engine/src/ecs/ComponentArray.hpp b/engine/src/ecs/ComponentArray.hpp index ec572caa5..52559659e 100644 --- a/engine/src/ecs/ComponentArray.hpp +++ b/engine/src/ecs/ComponentArray.hpp @@ -49,6 +49,8 @@ namespace nexo::ecs { * @param entity The entity being destroyed */ virtual void entityDestroyed(Entity entity) = 0; + + virtual void duplicateComponent(Entity sourceEntity, Entity destEntity) = 0; }; /** @@ -196,6 +198,14 @@ namespace nexo::ecs { return m_componentArray[m_sparse[entity]]; } + void duplicateComponent(Entity sourceEntity, Entity destEntity) override + { + if (!hasComponent(sourceEntity)) + THROW_EXCEPTION(ComponentNotFound, sourceEntity); + + insert(destEntity, get(sourceEntity)); + } + /** * @brief Checks if an entity has a component in this array * diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index ecf182df5..71aefdecc 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -349,6 +349,38 @@ namespace nexo::ecs { return getComponentArray()->get(entity); } + template + void duplicateComponent( + Entity sourceEntity, + Entity destEntity, + const Signature oldSignature, + const Signature newSignature + ) { + const ComponentType typeID = getComponentTypeID(); + 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 + ) { + 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 * diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index 43cadeaa7..3a5c844e6 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -76,6 +76,22 @@ namespace nexo::ecs { return components; } + Entity Coordinator::duplicateEntity(Entity sourceEntity) + { + Entity newEntity = createEntity(); + Signature signature = m_entityManager->getSignature(sourceEntity); + Signature destSignature = m_entityManager->getSignature(newEntity); + for (ComponentType type = 0; type < MAX_COMPONENT_TYPE; ++type) { + if (signature.test(type) && m_typeIDtoTypeIndex.contains(type)) { + Signature previousSignature = destSignature; + destSignature.set(type, true); + m_componentManager->duplicateComponent(type, sourceEntity, newEntity, previousSignature, destSignature); + } + } + m_entityManager->setSignature(newEntity, destSignature); + m_systemManager->entitySignatureChanged(newEntity, Signature{}, destSignature); + return newEntity; + } bool Coordinator::supportsMementoPattern(const std::type_index typeIndex) const { diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index bfac7276a..6a0990f4f 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -449,6 +449,8 @@ namespace nexo::ecs { bool supportsMementoPattern(const std::type_index typeIndex) const; + Entity duplicateEntity(Entity sourceEntity); + private: template void processComponentSignature(Signature& required, Signature& excluded) const { From e0c6c05a82f63c0acf6a189c9ce5bd123ba51f08 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 22:46:34 +0900 Subject: [PATCH 333/450] feat(undo-redo): add undo/redo on scene tree entity duplication --- .../SceneTreeWindow/Shortcuts.cpp | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp index 1f99dae3c..e09bab057 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp @@ -13,8 +13,10 @@ /////////////////////////////////////////////////////////////////////////////// #include "SceneTreeWindow.hpp" +#include "components/SceneComponents.hpp" #include "context/ActionManager.hpp" #include "components/Uuid.hpp" +#include "context/actions/EntityActions.hpp" namespace nexo::editor { void SceneTreeWindow::setupShortcuts() @@ -186,15 +188,13 @@ namespace nexo::editor { } void SceneTreeWindow::collapseAllCallback() { - // Implementation would depend on your tree structure - // This could reset the expanded state of all nodes - // For now, we'll just log it m_forceCollapseAll = true; m_forceExpandAll = false; - LOG(NEXO_INFO, "Collapse all nodes in scene tree"); } - void SceneTreeWindow::renameSelectedCallback() { + void SceneTreeWindow::renameSelectedCallback() + { + //TODO: Implement rename callback } void SceneTreeWindow::duplicateSelectedCallback() { @@ -209,17 +209,23 @@ namespace nexo::editor { if (currentSceneId == -1) return; - // Clear current selection to prepare for selecting the new duplicates - selector.clearSelection(); - std::vector newEntities; + newEntities.reserve(selectedEntities.size()); auto actionGroup = actionManager.createActionGroup(); + selector.clearSelection(); for (const auto entity : selectedEntities) { - LOG(NEXO_INFO, "Duplicating entity {}", entity); + ecs::Entity newEntity = app.m_coordinator->duplicateEntity(entity); + components::UuidComponent uuidComponent; + app.m_coordinator->getComponent(newEntity) = uuidComponent; + app.m_coordinator->removeComponent(newEntity); + app.getSceneManager().getScene(currentSceneId).addEntity(newEntity); + auto action = std::make_unique(newEntity); + actionGroup->addAction(std::move(action)); + newEntities.push_back(newEntity); } - //actionManager.recordAction(std::move(actionGroup)); + actionManager.recordAction(std::move(actionGroup)); // Select all the newly created entities for (const auto newEntity : newEntities) { From 56a75b18b4fe15e3189ac1dc0e81539fd5c3df21 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 23:21:19 +0900 Subject: [PATCH 334/450] refactor(undo-redo): remove useless func --- .../SceneTreeWindow/SceneTreeWindow.hpp | 6 - .../SceneTreeWindow/Shortcuts.cpp | 113 ++---------------- 2 files changed, 10 insertions(+), 109 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp index 149f906e4..e4f07c77b 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp @@ -397,14 +397,8 @@ namespace nexo::editor { void collapseAllCallback(); void renameSelectedCallback(); void duplicateSelectedCallback(); - void focusOnSelectedCallback(); void selectAllCallback(); - void deselectAllCallback(); - void groupEntitiesCallback(); - void ungroupEntitiesCallback(); - void invertSelectionCallback(); void hideSelectedCallback(); - void showSelectedCallback(); void showAllCallback(); }; } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp index e09bab057..f2a0c84cd 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp @@ -155,7 +155,8 @@ namespace nexo::editor { } } - void SceneTreeWindow::deleteSelectedCallback() { + void SceneTreeWindow::deleteSelectedCallback() + { auto& selector = Selector::get(); const auto& selectedEntities = selector.getSelectedEntities(); @@ -187,7 +188,8 @@ namespace nexo::editor { m_forceExpandAll = true; } - void SceneTreeWindow::collapseAllCallback() { + void SceneTreeWindow::collapseAllCallback() + { m_forceCollapseAll = true; m_forceExpandAll = false; } @@ -197,7 +199,8 @@ namespace nexo::editor { //TODO: Implement rename callback } - void SceneTreeWindow::duplicateSelectedCallback() { + void SceneTreeWindow::duplicateSelectedCallback() + { auto& selector = Selector::get(); const auto& selectedEntities = selector.getSelectedEntities(); @@ -236,79 +239,8 @@ namespace nexo::editor { } } - void SceneTreeWindow::focusOnSelectedCallback() { - auto& selector = Selector::get(); - const auto& selectedEntities = selector.getSelectedEntities(); - - if (selectedEntities.empty()) return; - - // Implementation would focus the editor camera on the selected entity/entities - // This depends on your camera system - // For now, just log it - LOG(NEXO_INFO, "Focusing on {} selected entities", selectedEntities.size()); - } - - void SceneTreeWindow::deselectAllCallback() { - auto& selector = Selector::get(); - selector.clearSelection(); - m_windowState = m_defaultState; - } - - void SceneTreeWindow::groupEntitiesCallback() { - auto& selector = Selector::get(); - const auto& selectedEntities = selector.getSelectedEntities(); - - if (selectedEntities.size() <= 1) return; // Need at least 2 entities to group - - // Implementation would create a parent entity and reparent all selected entities to it - // This depends on your scene hierarchy system - // For now, just log it - LOG(NEXO_INFO, "Grouping {} entities", selectedEntities.size()); - } - - void SceneTreeWindow::ungroupEntitiesCallback() { - auto& selector = Selector::get(); - const auto& selectedEntities = selector.getSelectedEntities(); - - if (selectedEntities.empty()) return; - - // Implementation would unparent all children of the selected groups - // This depends on your scene hierarchy system - // For now, just log it - LOG(NEXO_INFO, "Ungrouping {} entities", selectedEntities.size()); - } - - void SceneTreeWindow::invertSelectionCallback() { - auto& selector = Selector::get(); - int currentSceneId = selector.getSelectedScene(); - - if (currentSceneId == -1) return; - - auto& app = nexo::getApp(); - auto& scene = app.getSceneManager().getScene(currentSceneId); - - // Get all entities in the scene - const std::set &allEntities = scene.getEntities(); - - // Get currently selected entities - const auto& selectedEntities = selector.getSelectedEntities(); - std::unordered_set selectedSet(selectedEntities.begin(), selectedEntities.end()); - - // Clear current selection - selector.clearSelection(); - - // Select entities that weren't selected before - for (const auto entity : allEntities) { - if (selectedSet.find(entity) == selectedSet.end()) { - const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); - if (uuidComponent) { - selector.addToSelection(uuidComponent->get().uuid, entity); - } - } - } - } - - void SceneTreeWindow::hideSelectedCallback() { + void SceneTreeWindow::hideSelectedCallback() + { auto& selector = Selector::get(); const auto& selectedEntities = selector.getSelectedEntities(); @@ -334,33 +266,8 @@ namespace nexo::editor { actionManager.recordAction(std::move(actionGroup)); } - void SceneTreeWindow::showSelectedCallback() { - auto& selector = Selector::get(); - const auto& selectedEntities = selector.getSelectedEntities(); - - if (selectedEntities.empty()) return; - - auto& app = nexo::getApp(); - auto& actionManager = ActionManager::get(); - auto actionGroup = actionManager.createActionGroup(); - - for (const auto entity : selectedEntities) { - if (app.m_coordinator->entityHasComponent(entity)) { - auto& renderComponent = app.m_coordinator->getComponent(entity); - if (!renderComponent.isRendered) { - auto beforeState = renderComponent.save(); - renderComponent.isRendered = true; - auto afterState = renderComponent.save(); - actionGroup->addAction(std::make_unique>( - entity, beforeState, afterState)); - } - } - } - - actionManager.recordAction(std::move(actionGroup)); - } - - void SceneTreeWindow::showAllCallback() { + void SceneTreeWindow::showAllCallback() + { auto& selector = Selector::get(); int currentSceneId = selector.getSelectedScene(); From 1840c2df0da1dc95920499cfe1a614898150e104 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 23:25:10 +0900 Subject: [PATCH 335/450] feat(uno-redo): add undo/redo on entity creation in the scene tree --- editor/src/DocumentWindows/SceneTreeWindow/Show.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp index acf5aa22f..bac0ea2be 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -16,8 +16,10 @@ #include "components/Uuid.hpp" #include "EntityFactory3D.hpp" #include "LightFactory.hpp" +#include "context/actions/EntityActions.hpp" #include "utils/EditorProps.hpp" #include "ImNexo/Panels.hpp" +#include "context/ActionManager.hpp" namespace nexo::editor { @@ -43,6 +45,8 @@ namespace nexo::editor { const ecs::Entity newCube = EntityFactory3D::createCube({0.0f, 0.0f, -5.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 action = std::make_unique(newCube); + ActionManager::get().recordAction(std::move(action)); } ImGui::EndMenu(); } @@ -57,16 +61,22 @@ namespace nexo::editor { 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")) { const ecs::Entity pointLight = LightFactory::createPointLight({0.0f, 0.5f, 0.0f}); utils::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")) { const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, 0.0f}, {0.0f, -1.0f, 0.0f}); utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT); sceneManager.getScene(sceneId).addEntity(spotLight); + auto action = std::make_unique(spotLight); + ActionManager::get().recordAction(std::move(action)); } ImGui::EndMenu(); } From aa36261323581e34cdb6928fb65ef8d185cf4b8e Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 23:58:05 +0900 Subject: [PATCH 336/450] feat(undo-redo): add function to retrieve stack size --- editor/src/context/ActionHistory.cpp | 5 +++++ editor/src/context/ActionHistory.hpp | 1 + editor/src/context/ActionManager.cpp | 5 +++++ editor/src/context/ActionManager.hpp | 1 + 4 files changed, 12 insertions(+) diff --git a/editor/src/context/ActionHistory.cpp b/editor/src/context/ActionHistory.cpp index 21705574b..553b70626 100644 --- a/editor/src/context/ActionHistory.cpp +++ b/editor/src/context/ActionHistory.cpp @@ -72,4 +72,9 @@ namespace nexo::editor { for (unsigned int i = 0; i < elementsToRemove; ++i) undoStack.pop_back(); } + + unsigned int ActionHistory::getUndoStackSize() const + { + return undoStack.size(); + } } diff --git a/editor/src/context/ActionHistory.hpp b/editor/src/context/ActionHistory.hpp index 77e4fbcf8..cbb326b0d 100644 --- a/editor/src/context/ActionHistory.hpp +++ b/editor/src/context/ActionHistory.hpp @@ -36,6 +36,7 @@ namespace nexo::editor { void setMaxUndoLevels(size_t levels); void clear(unsigned int count = 0); + unsigned int getUndoStackSize() const; private: std::deque> undoStack; diff --git a/editor/src/context/ActionManager.cpp b/editor/src/context/ActionManager.cpp index 74f0055b1..66c446031 100644 --- a/editor/src/context/ActionManager.cpp +++ b/editor/src/context/ActionManager.cpp @@ -60,4 +60,9 @@ namespace nexo::editor { { history.clear(); } + + unsigned int ActionManager::getUndoStackSize() const + { + return history.getUndoStackSize(); + } } diff --git a/editor/src/context/ActionManager.hpp b/editor/src/context/ActionManager.hpp index 4c2e73d6d..a4ce0c770 100644 --- a/editor/src/context/ActionManager.hpp +++ b/editor/src/context/ActionManager.hpp @@ -52,6 +52,7 @@ namespace nexo::editor { bool canUndo() const; bool canRedo() const; void clearHistory(unsigned int count = 0); + unsigned int getUndoStackSize() const; static ActionManager& get() { static ActionManager instance; From 594fa7e069c6accc9c0f5e2db204a2bdbc9b9513 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 23:58:25 +0900 Subject: [PATCH 337/450] feat(undo-redo): add component add/remove actions --- editor/src/context/actions/EntityActions.hpp | 54 ++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/editor/src/context/actions/EntityActions.hpp b/editor/src/context/actions/EntityActions.hpp index 3f8532d74..f1dffab98 100644 --- a/editor/src/context/actions/EntityActions.hpp +++ b/editor/src/context/actions/EntityActions.hpp @@ -32,6 +32,60 @@ namespace nexo::editor { typename ComponentType::Memento m_memento; }; + template + class ComponentAddAction : public Action { + public: + ComponentAddAction(ecs::Entity entity) + : m_entity(entity) {} + + void undo() override + { + auto &app = getApp(); + m_memento = app.m_coordinator->getComponent(m_entity).save(); + app.m_coordinator->removeComponent(m_entity); + } + + void redo() override + { + auto &app = getApp(); + ComponentType target; + target.restore(m_memento); + app.m_coordinator->addComponent(m_entity, target); + } + + private: + ecs::Entity m_entity; + typename ComponentType::Memento m_memento; + }; + + template + class ComponentRemoveAction : public Action { + public: + ComponentRemoveAction(ecs::Entity entity) : m_entity(entity) + { + auto &app = getApp(); + m_memento = app.m_coordinator->getComponent(m_entity).save(); + } + + void undo() override + { + auto &app = getApp(); + ComponentType target; + target.restore(m_memento); + app.m_coordinator->addComponent(m_entity, target); + } + + void redo() override + { + auto &app = getApp(); + app.m_coordinator->removeComponent(m_entity); + } + + private: + ecs::Entity m_entity; + typename ComponentType::Memento m_memento; + }; + template class ComponentChangeAction : public Action { public: From 2c401975bd1909c18c7fd9f0e4c61a41da98b598 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Thu, 1 May 2025 23:58:45 +0900 Subject: [PATCH 338/450] feat(undo-redo): add undo redo for the camera inspector --- editor/src/ImNexo/Panels.cpp | 63 ++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index 27bc238b7..d1f2abce4 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -12,6 +12,7 @@ // /////////////////////////////////////////////////////////////////////////////// +#include "ImNexo/ImNexo.hpp" #include "Nexo.hpp" #include "Panels.hpp" #include "Elements.hpp" @@ -22,9 +23,13 @@ #include "IconsFontAwesome.h" #include "assets/AssetCatalog.hpp" #include "components/Camera.hpp" +#include "components/Transform.hpp" #include "components/Uuid.hpp" #include "context/Selector.hpp" +#include "context/actions/Action.hpp" +#include "context/actions/EntityActions.hpp" #include "utils/EditorProps.hpp" +#include "context/ActionManager.hpp" namespace ImNexo { bool MaterialInspector(nexo::components::Material *material) @@ -145,6 +150,9 @@ namespace ImNexo { bool CameraInspector(const nexo::scene::SceneId sceneId, ImVec2 sceneViewportSize) { auto &app = nexo::getApp(); + static int undoStackSize = -1; + if (undoStackSize == -1) + undoStackSize = nexo::editor::ActionManager::get().getUndoStackSize(); const ImVec2 availSize = ImGui::GetContentRegionAvail(); const float totalWidth = availSize.x; @@ -174,8 +182,9 @@ namespace ImNexo { camera = nexo::ecs::MAX_ENTITIES; cameraName[0] = '\0'; nameIsEmpty = false; + undoStackSize = -1; closingPopup = false; - + ImGui::CloseCurrentPopup(); return true; } ImGui::Columns(2, "CameraCreatorColumns", false); @@ -211,7 +220,17 @@ namespace ImNexo { { auto &cameraComponent = app.m_coordinator->getComponent(camera); cameraComponent.render = true; + static nexo::components::CameraComponent::Memento beforeState; + auto cameraComponentCopy = cameraComponent; + resetItemStates(); Camera(cameraComponent); + if (isItemActivated()) { + beforeState = cameraComponentCopy.save(); + } else if (isItemDeactivated()) { + auto afterState = cameraComponent.save(); + auto action = std::make_unique>(camera, beforeState, afterState); + nexo::editor::ActionManager::get().recordAction(std::move(action)); + } ImGui::TreePop(); } @@ -223,7 +242,17 @@ namespace ImNexo { { static glm::vec3 lastDisplayedEuler(0.0f); auto &transformComponent = app.m_coordinator->getComponent(camera); + static nexo::components::TransformComponent::Memento beforeState; + resetItemStates(); + auto transformComponentCopy = transformComponent; Transform(transformComponent, lastDisplayedEuler); + if (isItemActivated()) { + beforeState = transformComponentCopy.save(); + } else if (isItemDeactivated()) { + auto afterState = transformComponent.save(); + auto action = std::make_unique>(camera, beforeState, afterState); + nexo::editor::ActionManager::get().recordAction(std::move(action)); + } ImGui::TreePop(); } @@ -231,7 +260,17 @@ namespace ImNexo { Header("##PerspectiveCameraTarget", "Camera Target Component")) { auto &cameraTargetComponent = app.m_coordinator->getComponent(camera); + nexo::components::PerspectiveCameraTarget::Memento beforeState; + resetItemStates(); + auto cameraTargetComponentCopy = cameraTargetComponent; CameraTarget(cameraTargetComponent); + if (isItemActivated()) { + beforeState = cameraTargetComponentCopy.save(); + } else if (isItemDeactivated()) { + auto afterState = cameraTargetComponent.save(); + auto action = std::make_unique>(camera, beforeState, afterState); + nexo::editor::ActionManager::get().recordAction(std::move(action)); + } ImGui::TreePop(); } @@ -239,7 +278,17 @@ namespace ImNexo { Header("##PerspectiveCameraController", "Camera Controller Component")) { auto &cameraControllerComponent = app.m_coordinator->getComponent(camera); + nexo::components::PerspectiveCameraController::Memento beforeState; + auto cameraControllerComponentCopy = cameraControllerComponent; + resetItemStates(); CameraController(cameraControllerComponent); + if (isItemActivated()) { + beforeState = cameraControllerComponentCopy.save(); + } else if (isItemDeactivated()) { + auto afterState = cameraControllerComponent.save(); + auto action = std::make_unique>(camera, beforeState, afterState); + nexo::editor::ActionManager::get().recordAction(std::move(action)); + } ImGui::TreePop(); } @@ -302,6 +351,8 @@ namespace ImNexo { !app.m_coordinator->entityHasComponent(camera) && ButtonWithIconAndText("camera_target", ICON_FA_CAMERA, "Camera target", ImVec2(75.0f, 75.0f))) { + auto action = std::make_unique>(camera); + nexo::editor::ActionManager::get().recordAction(std::move(action)); nexo::components::PerspectiveCameraTarget cameraTarget{}; app.m_coordinator->addComponent(camera, cameraTarget); showComponentSelector = false; @@ -311,6 +362,8 @@ namespace ImNexo { !app.m_coordinator->entityHasComponent(camera) && ButtonWithIconAndText("camera_controller", ICON_FA_GAMEPAD, "Camera Controller", ImVec2(75.0f, 75.0f))) { + auto action = std::make_unique>(camera); + nexo::editor::ActionManager::get().recordAction(std::move(action)); nexo::components::PerspectiveCameraController cameraController{}; app.m_coordinator->addComponent(camera, cameraController); showComponentSelector = false; @@ -368,15 +421,21 @@ namespace ImNexo { auto &cameraComponent = app.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; cameraName[0] = '\0'; + undoStackSize = -1; ImGui::CloseCurrentPopup(); return true; } ImGui::SameLine(); if (ImGui::Button("Cancel", ImVec2(buttonWidth, 0))) { - ImGui::CloseCurrentPopup(); + unsigned int stackSize = nexo::editor::ActionManager::get().getUndoStackSize() - undoStackSize; + nexo::editor::ActionManager::get().clearHistory(stackSize); closingPopup = true; return false; } From 9bc1bb0d7ca5d0166d1e051d122ba71577145166 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 2 May 2025 05:03:45 +0900 Subject: [PATCH 339/450] fix(undo-redo): fix sonar cloud issues --- editor/main.cpp | 4 +- editor/src/ADocumentWindow.cpp | 13 +- editor/src/ADocumentWindow.hpp | 8 +- editor/src/DockingRegistry.cpp | 2 +- .../AssetManager/AssetManagerWindow.hpp | 3 - .../src/DocumentWindows/AssetManager/Show.cpp | 26 +- .../ConsoleWindow/ConsoleWindow.hpp | 7 +- .../DocumentWindows/ConsoleWindow/Utils.cpp | 3 +- .../EditorScene/EditorScene.hpp | 60 ++--- .../src/DocumentWindows/EditorScene/Gizmo.cpp | 38 ++- .../src/DocumentWindows/EditorScene/Init.cpp | 19 +- .../DocumentWindows/EditorScene/Shortcuts.cpp | 33 ++- .../src/DocumentWindows/EditorScene/Show.cpp | 13 +- .../DocumentWindows/EditorScene/Toolbar.cpp | 38 ++- .../DocumentWindows/EditorScene/Update.cpp | 31 ++- .../EntityProperties/AmbientLightProperty.cpp | 1 + .../EntityProperties/AmbientLightProperty.hpp | 2 +- .../EntityProperties/CameraController.cpp | 2 +- .../EntityProperties/CameraController.hpp | 2 +- .../EntityProperties/CameraProperty.cpp | 2 +- .../EntityProperties/CameraProperty.hpp | 2 +- .../EntityProperties/CameraTarget.cpp | 2 +- .../EntityProperties/CameraTarget.hpp | 2 +- .../DirectionalLightProperty.cpp | 2 +- .../DirectionalLightProperty.hpp | 2 +- .../EntityProperties/PointLightProperty.cpp | 3 +- .../EntityProperties/PointLightProperty.hpp | 2 +- .../EntityProperties/RenderProperty.cpp | 6 +- .../EntityProperties/RenderProperty.hpp | 4 +- .../EntityProperties/SpotLightProperty.cpp | 3 +- .../EntityProperties/SpotLightProperty.hpp | 2 +- .../EntityProperties/TransformProperty.cpp | 2 +- .../EntityProperties/TransformProperty.hpp | 2 +- .../InspectorWindow/InspectorWindow.hpp | 5 +- .../DocumentWindows/InspectorWindow/Show.cpp | 4 +- .../MaterialInspector/MaterialInspector.hpp | 2 +- .../MaterialInspector/Show.cpp | 44 +-- editor/src/DocumentWindows/PopupManager.cpp | 22 +- editor/src/DocumentWindows/PopupManager.hpp | 6 +- .../SceneTreeWindow/Hovering.cpp | 9 +- .../SceneTreeWindow/NodeHandling.cpp | 10 +- .../SceneTreeWindow/SceneCreation.cpp | 18 +- .../SceneTreeWindow/SceneTreeWindow.hpp | 43 +-- .../SceneTreeWindow/Selection.cpp | 40 ++- .../SceneTreeWindow/Shortcuts.cpp | 53 ++-- .../DocumentWindows/SceneTreeWindow/Show.cpp | 8 +- .../SceneTreeWindow/Update.cpp | 8 +- editor/src/Editor.cpp | 74 +++-- editor/src/Editor.hpp | 13 +- editor/src/ImNexo/Components.cpp | 20 +- editor/src/ImNexo/Components.hpp | 8 +- editor/src/ImNexo/Elements.cpp | 56 ++-- editor/src/ImNexo/EntityProperties.cpp | 17 +- editor/src/ImNexo/EntityProperties.hpp | 5 - editor/src/ImNexo/Guard.hpp | 252 +++++++++--------- editor/src/ImNexo/ImNexo.hpp | 1 - editor/src/ImNexo/Panels.cpp | 73 +++-- editor/src/ImNexo/Panels.hpp | 2 +- editor/src/ImNexo/Utils.cpp | 8 +- editor/src/ImNexo/Utils.hpp | 2 +- editor/src/ImNexo/Widgets.cpp | 13 +- editor/src/ImNexo/Widgets.hpp | 9 +- editor/src/WindowRegistry.hpp | 6 +- editor/src/context/ActionGroup.cpp | 8 +- editor/src/context/ActionGroup.hpp | 4 +- editor/src/context/ActionHistory.cpp | 4 +- editor/src/context/ActionHistory.hpp | 7 +- editor/src/context/ActionManager.cpp | 4 +- editor/src/context/ActionManager.hpp | 13 +- editor/src/context/Selector.cpp | 33 ++- editor/src/context/Selector.hpp | 4 +- .../actions/ComponentRestoreFactory.cpp | 20 +- .../actions/ComponentRestoreFactory.hpp | 2 +- editor/src/context/actions/EntityActions.cpp | 12 +- editor/src/context/actions/EntityActions.hpp | 56 ++-- editor/src/inputs/Command.cpp | 13 +- editor/src/inputs/Command.hpp | 30 +-- editor/src/inputs/InputManager.cpp | 47 ++-- editor/src/inputs/InputManager.hpp | 64 ++--- editor/src/inputs/WindowState.cpp | 1 + editor/src/inputs/WindowState.hpp | 9 +- editor/src/utils/Config.cpp | 4 +- editor/src/utils/EditorProps.cpp | 41 ++- editor/src/utils/FileSystem.cpp | 2 +- editor/src/utils/ScenePreview.cpp | 37 +-- editor/src/utils/ScenePreview.hpp | 6 +- editor/src/utils/String.cpp | 2 +- editor/src/utils/TransparentStringHash.hpp | 1 + 88 files changed, 766 insertions(+), 830 deletions(-) diff --git a/editor/main.cpp b/editor/main.cpp index 1f119adb9..db56aaa1a 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -21,7 +21,7 @@ #include "src/DocumentWindows/MaterialInspector/MaterialInspector.hpp" #include -#include +#include #include int main(int argc, char **argv) @@ -37,7 +37,7 @@ try { editor.registerWindow(NEXO_WND_USTRID_MATERIAL_INSPECTOR); editor.registerWindow(NEXO_WND_USTRID_ASSET_MANAGER); - if (auto defaultScene = editor.getWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)).lock()) + if (const auto defaultScene = editor.getWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)).lock()) defaultScene->setDefault(); editor.init(); diff --git a/editor/src/ADocumentWindow.cpp b/editor/src/ADocumentWindow.cpp index be2ce2263..5a31ddba8 100644 --- a/editor/src/ADocumentWindow.cpp +++ b/editor/src/ADocumentWindow.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "ADocumentWindow.hpp" +#include namespace nexo::editor { @@ -25,7 +26,7 @@ namespace nexo::editor { void ADocumentWindow::dockingUpdate(const std::string &windowName) { - if (ImGuiWindow* currentWindow = ImGui::GetCurrentWindow(); currentWindow) + if (const ImGuiWindow* currentWindow = ImGui::GetCurrentWindow(); currentWindow) { const bool isDocked = currentWindow->DockIsActive; const ImGuiID currentDockID = currentWindow->DockId; @@ -52,13 +53,10 @@ namespace nexo::editor { void ADocumentWindow::visibilityUpdate() { m_focused = ImGui::IsWindowFocused(); - bool isDocked = ImGui::IsWindowDocked(); - ImGuiWindow* window = ImGui::GetCurrentWindow(); + const bool isDocked = ImGui::IsWindowDocked(); + const ImGuiWindow* window = ImGui::GetCurrentWindow(); if (isDocked) { - ImGuiID dock_id = ImGui::GetWindowDockID(); - ImGuiDockNode* node = ImGui::DockBuilderGetNode(dock_id); - // 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; @@ -67,13 +65,12 @@ namespace nexo::editor { // Not docked windows are visible if we've reached this point m_isVisibleInDock = true; } - m_hovered = ImGui::IsWindowHovered(); } void ADocumentWindow::sizeUpdate() { - ImGuiWindow* window = ImGui::GetCurrentWindow(); + const ImGuiWindow* window = ImGui::GetCurrentWindow(); m_windowPos = window->Pos; m_windowSize = window->Size; m_contentSizeMin = ImGui::GetWindowContentRegionMin(); diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index a925972ae..325c2ab3a 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -14,13 +14,13 @@ #pragma once +#include + #include "IDocumentWindow.hpp" #include "Nexo.hpp" #include "WindowRegistry.hpp" #include "inputs/WindowState.hpp" -#include - namespace nexo::editor { #define NEXO_WND_USTRID_INSPECTOR "###Inspector" @@ -39,7 +39,7 @@ namespace nexo::editor { * 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(const std::string &windowName, WindowRegistry &windowRegistry) : m_windowName(windowName), m_windowRegistry(windowRegistry) + explicit ADocumentWindow(std::string windowName, WindowRegistry &windowRegistry) : m_windowName(std::move(windowName)), m_windowRegistry(windowRegistry) { windowId = nextWindowId++; }; @@ -73,7 +73,7 @@ namespace nexo::editor { bool m_opened = true; bool m_focused = false; bool m_hovered = false; // TODO: make these update without user intervention - bool m_wasVisibleLastFrame; + bool m_wasVisibleLastFrame = false; bool m_isVisibleInDock = true; ImVec2 m_windowPos; diff --git a/editor/src/DockingRegistry.cpp b/editor/src/DockingRegistry.cpp index 481da5eee..7cb210dd6 100644 --- a/editor/src/DockingRegistry.cpp +++ b/editor/src/DockingRegistry.cpp @@ -18,7 +18,7 @@ namespace nexo::editor { - void DockingRegistry::setDockId(const std::string& name, ImGuiID id) + void DockingRegistry::setDockId(const std::string& name, const ImGuiID id) { dockIds[name] = id; } diff --git a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp index e09f3274b..ad6a51d5e 100644 --- a/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp +++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp @@ -14,11 +14,8 @@ #pragma once #include -#include #include -#include #include -#include #include namespace nexo::editor { diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 50e4c813e..54cec1e24 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -29,12 +29,12 @@ namespace nexo::editor { } } - void AssetManagerWindow::calculateLayout(float availWidth) + void AssetManagerWindow::calculateLayout(const float availWidth) { // Sizes - m_layout.size.columnCount = std::max(static_cast(availWidth / (m_layout.size.iconSize + m_layout.size.iconSpacing)), 1); + m_layout.size.columnCount = std::max(static_cast(availWidth / (m_layout.size.iconSize + static_cast(m_layout.size.iconSpacing))), 1); m_layout.size.itemSize = ImVec2(m_layout.size.iconSize + ImGui::GetFontSize() * 1.5f, m_layout.size.iconSize + ImGui::GetFontSize() * 1.7f); - m_layout.size.itemStep = ImVec2(m_layout.size.itemSize.x + m_layout.size.iconSpacing, m_layout.size.itemSize.y + m_layout.size.iconSpacing); + m_layout.size.itemStep = ImVec2(m_layout.size.itemSize.x + static_cast(m_layout.size.iconSpacing), m_layout.size.itemSize.y + static_cast(m_layout.size.iconSpacing)); // Colors m_layout.color.thumbnailBg = ImGui::GetColorU32(ImGuiCol_Button); @@ -52,7 +52,7 @@ namespace nexo::editor { m_layout.color.titleText = ImGui::GetColorU32(ImGuiCol_Text); } - void AssetManagerWindow::handleSelection(int index, bool isSelected) + void AssetManagerWindow::handleSelection(int index, const bool isSelected) { LOG(NEXO_INFO, "Asset {} {}", index, isSelected ? "deselected" : "selected"); if (ImGui::GetIO().KeyCtrl) { @@ -77,7 +77,7 @@ namespace nexo::editor { } } - static ImU32 getAssetTypeOverlayColor(assets::AssetType type) + static ImU32 getAssetTypeOverlayColor(const assets::AssetType type) { switch (type) { case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255); @@ -88,7 +88,7 @@ namespace nexo::editor { void AssetManagerWindow::drawAsset( const assets::GenericAssetRef& asset, - int index, + const int index, const ImVec2& itemPos, const ImVec2& itemSize ) { @@ -96,14 +96,14 @@ namespace nexo::editor { if (!assetData) return; ImDrawList* drawList = ImGui::GetWindowDrawList(); - ImVec2 itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); + const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y); ImGui::PushID(index); // Highlight selection - bool isSelected = std::find(m_selectedAssets.begin(), m_selectedAssets.end(), index) != m_selectedAssets.end(); - ImU32 bgColor = isSelected ? m_layout.color.thumbnailBgSelected : m_layout.color.thumbnailBg; + const bool isSelected = std::ranges::find(m_selectedAssets, index) != m_selectedAssets.end(); + const ImU32 bgColor = isSelected ? m_layout.color.thumbnailBgSelected : m_layout.color.thumbnailBg; drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.cornerRadius); // Add selection border @@ -120,17 +120,17 @@ namespace nexo::editor { } // Draw thumbnail - ImVec2 thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.thumbnailHeightRatio); + const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.thumbnailHeightRatio); drawList->AddRectFilled(itemPos, thumbnailEnd, m_layout.color.thumbnailBg); // Draw type overlay - ImVec2 overlayPos = ImVec2(thumbnailEnd.x - m_layout.size.overlayPadding, itemPos.y + m_layout.size.overlayPadding); - ImU32 overlayColor = getAssetTypeOverlayColor(assetData->getType()); + const auto overlayPos = ImVec2(thumbnailEnd.x - m_layout.size.overlayPadding, itemPos.y + m_layout.size.overlayPadding); + const ImU32 overlayColor = getAssetTypeOverlayColor(assetData->getType()); drawList->AddRectFilled(overlayPos, ImVec2(overlayPos.x + m_layout.size.overlaySize, overlayPos.y + m_layout.size.overlaySize), overlayColor); // Draw title const char *assetName = assetData->getMetadata().location.getName().c_str(); - ImVec2 textPos = ImVec2(itemPos.x + (itemSize.x - ImGui::CalcTextSize(assetName).x) * 0.5f, + const auto textPos = ImVec2(itemPos.x + (itemSize.x - ImGui::CalcTextSize(assetName).x) * 0.5f, thumbnailEnd.y + m_layout.size.titlePadding); // Background rectangle for text drawList->AddRectFilled(ImVec2(itemPos.x, thumbnailEnd.y), ImVec2(itemEnd.x, itemEnd.y), m_layout.color.titleBg); diff --git a/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp index 304564b21..fc2ae28cb 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp +++ b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp @@ -16,11 +16,12 @@ #include "ADocumentWindow.hpp" #include "Editor.hpp" +#include namespace nexo::editor { - std::string verbosityToString(const loguru::Verbosity level); - loguru::Verbosity nexoLevelToLoguruLevel(const LogLevel level); + std::string verbosityToString(loguru::Verbosity level); + loguru::Verbosity nexoLevelToLoguruLevel(LogLevel level); const ImVec4 getVerbosityColor(loguru::Verbosity level); std::string generateLogFilePath(); @@ -112,7 +113,7 @@ namespace nexo::editor { char m_inputBuf[512] = {}; std::vector m_commands; // History of executed commands. - std::string m_logFilePath = ""; + std::string m_logFilePath; bool m_exportLog = true; bool m_scrollToBottom = true; diff --git a/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp index 426d23f3e..b755fa355 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp @@ -63,7 +63,6 @@ namespace nexo::editor { case LogLevel::DEV: return loguru::Verbosity_3; default: return loguru::Verbosity_INVALID; } - return loguru::Verbosity_INVALID; } /** @@ -79,7 +78,7 @@ namespace nexo::editor { * @param level The verbosity level for which the corresponding color is computed. * @return ImVec4 The color associated with the specified verbosity level. */ - const ImVec4 getVerbosityColor(loguru::Verbosity level) + const ImVec4 getVerbosityColor(const loguru::Verbosity level) { ImVec4 color; diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp index 3e0b4ab98..3511d7084 100644 --- a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -15,18 +15,15 @@ #include "ADocumentWindow.hpp" #include "inputs/WindowState.hpp" -#include "IDocumentWindow.hpp" #include "core/scene/SceneManager.hpp" -#include "inputs/WindowState.hpp" #include "../PopupManager.hpp" #include #include #include "ImNexo/Widgets.hpp" -#include "inputs/InputManager.hpp" namespace nexo::editor { - class EditorScene : public ADocumentWindow { + class EditorScene final : public ADocumentWindow { public: using ADocumentWindow::ADocumentWindow; @@ -66,22 +63,22 @@ namespace nexo::editor { */ 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. - */ - void setCamera(ecs::Entity cameraId); + * @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); /** * @brief Marks this scene as the default scene. @@ -136,9 +133,9 @@ namespace nexo::editor { */ void setupScene(); - void hideAllButSelectionCallback(); + void hideAllButSelectionCallback() const; void selectAllCallback(); - void unhideAllCallback(); + void unhideAllCallback() const; void deleteCallback(); void setupGlobalState(); @@ -174,7 +171,7 @@ namespace nexo::editor { * @param buttonWidth Standard width for toolbar buttons * @param buttonHeight Standard height for toolbar buttons */ - void initialToolbarSetup(const float buttonWidth, const float buttonHeight); + void initialToolbarSetup(float buttonWidth) const; /** * @brief Renders the editor camera button in the toolbar. @@ -196,7 +193,7 @@ namespace nexo::editor { * @return true if the button was clicked */ bool renderGizmoModeToolbarButton( - const bool showGizmoModeMenu, + bool showGizmoModeMenu, ImNexo::ButtonProps &activeGizmoMode, ImNexo::ButtonProps &inactiveGizmoMode ); @@ -211,7 +208,7 @@ namespace nexo::editor { * @param buttonSize Size of buttons in the dropdown * @param showPrimitiveMenu Reference to the flag controlling menu visibility */ - void renderPrimitiveSubMenu(const ImVec2 &primitiveButtonPos, const ImVec2 &buttonSize, bool &showPrimitiveMenu); + void renderPrimitiveSubMenu(const ImVec2 &primitiveButtonPos, const ImVec2 &buttonSize, bool &showPrimitiveMenu) const; /** * @brief Renders the snap settings dropdown menu. @@ -246,7 +243,7 @@ namespace nexo::editor { * @param gradientStop Color gradient for the button background * @return true if the button was clicked */ - bool renderToolbarButton( + static bool renderToolbarButton( const std::string &uniqueId, const std::string &icon, const std::string &tooltip, @@ -268,16 +265,15 @@ namespace nexo::editor { * If the gizmo is actively manipulated, the entity's transform component is updated with the new values. */ void renderGizmo(); - void setupGizmoContext(const components::TransformComponent& cameraTransform, - const components::CameraComponent& camera); + void setupGizmoContext(const components::CameraComponent& camera) const; float* getSnapSettingsForOperation(ImGuizmo::OPERATION operation); - void captureInitialTransformStates(const std::vector& entities); + static void captureInitialTransformStates(const std::vector& entities); void applyTransformToEntities( ecs::Entity sourceEntity, const components::TransformComponent& sourceTransform, const components::TransformComponent& newTransform, - const std::vector& targetEntities); - void createTransformUndoActions(const std::vector& entities); + const std::vector& targetEntities) const; + static void createTransformUndoActions(const std::vector& entities); static bool s_wasUsingGizmo; static ImGuizmo::OPERATION s_lastOperation; static std::unordered_map s_initialTransformStates; @@ -289,11 +285,11 @@ namespace nexo::editor { * rendered scene, and updates viewport bounds for input handling. */ void renderView(); - void renderNoActiveCamera(); + void renderNoActiveCamera() const; void renderNewEntityPopup(); void handleSelection(); - int sampleEntityTexture(float mx, float my); + int sampleEntityTexture(float mx, float my) const; void updateSelection(int entityId, bool isShiftPressed, bool isCtrlPressed); void updateWindowState(); diff --git a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp index 7ef1339f6..1aa465cfe 100644 --- a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp +++ b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp @@ -29,7 +29,7 @@ namespace nexo::editor { { for (int bitPos = 0; bitPos <= 13; bitPos++) { - ImGuizmo::OPERATION op = static_cast(1u << bitPos); + auto op = static_cast(1u << bitPos); if (ImGuizmo::IsOver(op)) return op; } @@ -49,8 +49,7 @@ namespace nexo::editor { return std::nullopt; } - void EditorScene::setupGizmoContext(const components::TransformComponent& cameraTransform, - const components::CameraComponent& camera) + void EditorScene::setupGizmoContext(const components::CameraComponent& camera) const { ImGuizmo::SetOrthographic(camera.type == components::CameraType::ORTHOGRAPHIC); ImGuizmo::SetDrawlist(); @@ -66,11 +65,11 @@ namespace nexo::editor { glm::scale(glm::mat4(1.0f), transform.size); } - float* EditorScene::getSnapSettingsForOperation(ImGuizmo::OPERATION operation) + float* EditorScene::getSnapSettingsForOperation(const ImGuizmo::OPERATION operation) { - if (m_snapTranslateOn && operation & ImGuizmo::OPERATION::TRANSLATE) { + if (m_snapTranslateOn && operation & ImGuizmo::OPERATION::TRANSLATE) return &m_snapTranslate.x; - } else if (m_snapRotateOn && operation & ImGuizmo::OPERATION::ROTATE) { + if (m_snapRotateOn && operation & ImGuizmo::OPERATION::ROTATE) { return &m_angleSnap; } return nullptr; @@ -90,17 +89,17 @@ namespace nexo::editor { } void EditorScene::applyTransformToEntities( - ecs::Entity sourceEntity, + const ecs::Entity sourceEntity, const components::TransformComponent& sourceTransform, const components::TransformComponent& newTransform, - const std::vector& targetEntities) + const std::vector& targetEntities) const { const auto& coord = nexo::Application::m_coordinator; // Calculate transformation deltas - glm::vec3 positionDelta = newTransform.pos - sourceTransform.pos; - glm::vec3 scaleFactor = newTransform.size / sourceTransform.size; - glm::quat rotationDelta = newTransform.quat * glm::inverse(sourceTransform.quat); + const glm::vec3 positionDelta = newTransform.pos - sourceTransform.pos; + const glm::vec3 scaleFactor = newTransform.size / sourceTransform.size; + const glm::quat rotationDelta = newTransform.quat * glm::inverse(sourceTransform.quat); // Apply transforms to all selected entities except the source for (const auto& entity : targetEntities) { @@ -142,7 +141,7 @@ namespace nexo::editor { // If multiple entities selected, create a group action if (entities.size() > 1) { - auto groupAction = actionManager.createActionGroup(); + auto groupAction = ActionManager::createActionGroup(); bool anyChanges = false; for (const auto& entity : entities) { @@ -173,7 +172,7 @@ namespace nexo::editor { auto entity = entities[0]; auto transform = coord->tryGetComponent(entity); - if (transform && s_initialTransformStates.count(entity)) { + if (s_initialTransformStates.contains(entity)) { auto beforeState = s_initialTransformStates[entity]; auto afterState = transform->get().save(); @@ -206,7 +205,7 @@ namespace nexo::editor { auto primaryTransform = coord->tryGetComponent(primaryEntity); if (!primaryTransform) { - auto entityWithTransform = findEntityWithTransform(selectedEntities); + const auto entityWithTransform = findEntityWithTransform(selectedEntities); if (!entityWithTransform) return; // No entity with transform found primaryEntity = *entityWithTransform; @@ -218,7 +217,7 @@ namespace nexo::editor { auto& camera = coord->getComponent(m_activeCamera); // Configure ImGuizmo - setupGizmoContext(cameraTransform, camera); + setupGizmoContext(camera); ImGuizmo::SetID(static_cast(primaryEntity)); // Prepare matrices @@ -232,10 +231,7 @@ namespace nexo::editor { } // Get snap settings if applicable - float* snap = getSnapSettingsForOperation(s_lastOperation); - - // Track ImGuizmo usage state - bool isUsingGizmo = ImGuizmo::IsUsing(); + const float* snap = getSnapSettingsForOperation(s_lastOperation); // Capture initial state when starting to use gizmo if (!s_wasUsingGizmo && ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGuizmo::IsOver()) { @@ -254,14 +250,14 @@ namespace nexo::editor { ); // Update isUsingGizmo after manipulation - isUsingGizmo = ImGuizmo::IsUsing(); + bool isUsingGizmo = ImGuizmo::IsUsing(); if (isUsingGizmo) { // Disable camera movement during manipulation camera.active = false; // Extract the original transform values - components::TransformComponent originalTransform = primaryTransform->get(); + const components::TransformComponent originalTransform = primaryTransform->get(); // Extract the new transform values from the matrix glm::vec3 newPos; diff --git a/editor/src/DocumentWindows/EditorScene/Init.cpp b/editor/src/DocumentWindows/EditorScene/Init.cpp index 2e08fbae2..2db883bec 100644 --- a/editor/src/DocumentWindows/EditorScene/Init.cpp +++ b/editor/src/DocumentWindows/EditorScene/Init.cpp @@ -39,13 +39,13 @@ namespace nexo::editor { framebufferSpecs.width = static_cast(m_contentSize.x); framebufferSpecs.height = static_cast(m_contentSize.y); const auto renderTarget = renderer::NxFramebuffer::create(framebufferSpecs); - m_editorCamera = CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast(m_contentSize.x), static_cast(m_contentSize.y), renderTarget); - auto &cameraComponent = app.m_coordinator->getComponent(m_editorCamera); + m_editorCamera = static_cast(CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast(m_contentSize.x), static_cast(m_contentSize.y), renderTarget)); + auto &cameraComponent = Application::m_coordinator->getComponent(m_editorCamera); cameraComponent.render = true; app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_editorCamera)); - components::PerspectiveCameraController controller; + const components::PerspectiveCameraController controller; Application::m_coordinator->addComponent(static_cast(m_editorCamera), controller); - components::EditorCameraTag editorCameraTag; + constexpr components::EditorCameraTag editorCameraTag; Application::m_coordinator->addComponent(m_editorCamera, editorCameraTag); m_activeCamera = m_editorCamera; @@ -78,14 +78,13 @@ namespace nexo::editor { m_contentSize = ImVec2(1280, 720); } - void EditorScene::setCamera(ecs::Entity cameraId) + void EditorScene::setCamera(const ecs::Entity cameraId) { - auto &app = getApp(); - auto &oldCameraComponent = app.m_coordinator->getComponent(m_activeCamera); + auto &oldCameraComponent = Application::m_coordinator->getComponent(m_activeCamera); oldCameraComponent.active = false; oldCameraComponent.render = false; - m_activeCamera = cameraId; - auto &newCameraComponent = app.m_coordinator->getComponent(cameraId); - newCameraComponent.resize(m_contentSize.x, m_contentSize.y); + m_activeCamera = static_cast(cameraId); + auto &newCameraComponent = Application::m_coordinator->getComponent(cameraId); + newCameraComponent.resize(static_cast(m_contentSize.x), static_cast(m_contentSize.y)); } } diff --git a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp index edf04b131..f3564d889 100644 --- a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp +++ b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp @@ -23,11 +23,10 @@ namespace nexo::editor { { auto &selector = Selector::get(); const auto &selectedEntities = selector.getSelectedEntities(); - auto &app = nexo::getApp(); auto& actionManager = ActionManager::get(); - auto actionGroup = actionManager.createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : selectedEntities) { - auto &renderComponent = app.m_coordinator->getComponent(entity); + auto &renderComponent = Application::m_coordinator->getComponent(entity); auto beforeState = renderComponent.save(); renderComponent.isRendered = !renderComponent.isRendered; auto afterState = renderComponent.save(); @@ -49,23 +48,23 @@ namespace nexo::editor { for (const auto entity : scene.getEntities()) { if (entity == m_editorCamera) continue; // Skip editor camera - const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); + const auto uuidComponent = Application::m_coordinator->tryGetComponent(entity); if (uuidComponent) - selector.addToSelection(uuidComponent->get().uuid, entity); + selector.addToSelection(uuidComponent->get().uuid, static_cast(entity)); } m_windowState = m_gizmoState; } - void EditorScene::hideAllButSelectionCallback() + void EditorScene::hideAllButSelectionCallback() const { auto &app = getApp(); const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); - auto &selector = Selector::get(); + const auto &selector = Selector::get(); auto &actionManager = ActionManager::get(); - auto actionGroup = actionManager.createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : entities) { - if (app.m_coordinator->entityHasComponent(entity) && !selector.isEntitySelected(entity)) { - auto &renderComponent = app.m_coordinator->getComponent(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(); renderComponent.isRendered = false; @@ -85,14 +84,14 @@ namespace nexo::editor { auto &app = nexo::getApp(); auto& actionManager = ActionManager::get(); if (selectedEntities.size() > 1) { - auto actionGroup = actionManager.createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : selectedEntities) { - actionGroup->addAction(actionManager.prepareEntityDeletion(entity)); + actionGroup->addAction(ActionManager::prepareEntityDeletion(entity)); app.deleteEntity(entity); } actionManager.recordAction(std::move(actionGroup)); } else { - auto deleteAction = actionManager.prepareEntityDeletion(selectedEntities[0]); + auto deleteAction = ActionManager::prepareEntityDeletion(selectedEntities[0]); app.deleteEntity(selectedEntities[0]); actionManager.recordAction(std::move(deleteAction)); } @@ -100,15 +99,15 @@ namespace nexo::editor { this->m_windowState = m_globalState; } - void EditorScene::unhideAllCallback() + void EditorScene::unhideAllCallback() const { auto &app = getApp(); const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities(); auto &actionManager = ActionManager::get(); - auto actionGroup = actionManager.createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : entities) { - if (app.m_coordinator->entityHasComponent(entity)) { - auto &renderComponent = app.m_coordinator->getComponent(entity); + if (Application::m_coordinator->entityHasComponent(entity)) { + auto &renderComponent = Application::m_coordinator->getComponent(entity); if (!renderComponent.isRendered) { auto beforeState = renderComponent.save(); renderComponent.isRendered = true; diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index 70826fb68..f2d14f005 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -24,11 +24,11 @@ namespace nexo::editor { - void EditorScene::renderNoActiveCamera() + void EditorScene::renderNoActiveCamera() const { // No active camera, render the text at the center of the screen - ImVec2 textSize = ImGui::CalcTextSize("No active camera"); - auto textPos = ImVec2((m_contentSize.x - textSize.x) / 2, (m_contentSize.y - textSize.y) / 2); + const ImVec2 textSize = ImGui::CalcTextSize("No active camera"); + const auto textPos = ImVec2((m_contentSize.x - textSize.x) / 2, (m_contentSize.y - textSize.y) / 2); ImGui::SetCursorScreenPos(textPos); ImGui::Text("No active camera"); @@ -85,7 +85,7 @@ namespace nexo::editor { // --- Camera item --- if (ImGui::MenuItem("Camera")) { m_popupManager.openPopupWithCallback("Popup camera inspector", [this]() { - ImNexo::CameraInspector(this->m_sceneId, this->m_contentSize); + ImNexo::CameraInspector(this->m_sceneId); }, ImVec2(1440,900)); } m_popupManager.closePopup(); @@ -93,7 +93,6 @@ namespace nexo::editor { void EditorScene::renderView() { - const auto viewPortOffset = ImGui::GetCursorPos(); auto &cameraComponent = Application::m_coordinator->getComponent(m_activeCamera); if (!cameraComponent.m_renderTarget) return; @@ -111,8 +110,8 @@ namespace nexo::editor { const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); ImNexo::Image(static_cast(static_cast(textureId)), m_contentSize); - ImVec2 viewportMin = ImGui::GetItemRectMin(); - ImVec2 viewportMax = ImGui::GetItemRectMax(); + const ImVec2 viewportMin = ImGui::GetItemRectMin(); + const ImVec2 viewportMax = ImGui::GetItemRectMax(); m_viewportBounds[0] = viewportMin; m_viewportBounds[1] = viewportMax; } diff --git a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp index cd07b0754..b6764198a 100644 --- a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp +++ b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp @@ -22,7 +22,7 @@ namespace nexo::editor { - void EditorScene::initialToolbarSetup(const float buttonWidth, const float buttonHeight) + void EditorScene::initialToolbarSetup(const float buttonWidth) const { ImVec2 toolbarPos = m_windowPos; toolbarPos.x += 10.0f; @@ -30,7 +30,7 @@ namespace nexo::editor { ImGui::SetCursorScreenPos(toolbarPos); - ImVec2 toolbarSize = ImVec2(m_contentSize.x - buttonWidth, 50.0f); + const auto toolbarSize = ImVec2(m_contentSize.x - buttonWidth, 50.0f); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.1f, 0.1f, 0.1f, 0.0f)); ImGui::BeginChild("##ToolbarOverlay", toolbarSize, 0, @@ -47,7 +47,7 @@ namespace nexo::editor { { constexpr float buttonWidth = 35.0f; constexpr float buttonHeight = 35.0f; - bool clicked = ImNexo::IconGradientButton(uniqueId, icon, ImVec2(buttonWidth, buttonHeight), gradientStop); + 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) @@ -55,7 +55,7 @@ namespace nexo::editor { return clicked; } - void EditorScene::renderPrimitiveSubMenu(const ImVec2 &primitiveButtonPos, const ImVec2 &buttonSize, bool &showPrimitiveMenu) + void EditorScene::renderPrimitiveSubMenu(const ImVec2 &primitiveButtonPos, const ImVec2 &buttonSize, bool &showPrimitiveMenu) const { auto &app = getApp(); static const std::vector buttonProps = @@ -161,8 +161,8 @@ namespace nexo::editor { ImGui::Spacing(); ImGui::Spacing(); - float buttonWidth = 120.0f; - float windowWidth = ImGui::GetWindowSize().x; + const float buttonWidth = 120.0f; + const float windowWidth = ImGui::GetWindowSize().x; ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) @@ -179,9 +179,8 @@ namespace nexo::editor { { if (m_popupManager.showPopupModal("Grid settings")) { - auto &app = getApp(); components::RenderContext::GridParams &gridSettings = - app.m_coordinator->getSingletonComponent().gridParams; + Application::m_coordinator->getSingletonComponent().gridParams; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); ImGui::Indent(10.0f); @@ -206,8 +205,8 @@ namespace nexo::editor { ImGui::Spacing(); ImGui::Spacing(); - float buttonWidth = 120.0f; - float windowWidth = ImGui::GetWindowSize().x; + const float buttonWidth = 120.0f; + const float windowWidth = ImGui::GetWindowSize().x; ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) @@ -222,21 +221,19 @@ namespace nexo::editor { void EditorScene::renderEditorCameraToolbarButton() { - auto &app = getApp(); auto &selector = Selector::get(); - bool editorMode = m_activeCamera == m_editorCamera; if (m_activeCamera == m_editorCamera) { if (renderToolbarButton("editor_camera", ICON_FA_CAMERA, "Edit Editor Camera Setting", m_buttonGradient)) { - const auto &uuidComponent = app.m_coordinator->getComponent(m_editorCamera); + 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 = app.m_coordinator->getComponent(m_activeCamera); + auto &oldCameraComponent = Application::m_coordinator->getComponent(m_activeCamera); oldCameraComponent.active = false; oldCameraComponent.render = false; m_activeCamera = m_editorCamera; - auto &editorCameraComponent = app.m_coordinator->getComponent(m_activeCamera); + auto &editorCameraComponent = Application::m_coordinator->getComponent(m_activeCamera); editorCameraComponent.render = true; editorCameraComponent.active = true; } @@ -266,7 +263,7 @@ namespace nexo::editor { constexpr ImVec2 buttonSize{buttonWidth, buttonHeight}; ImVec2 originalCursorPos = ImGui::GetCursorPos(); - initialToolbarSetup(buttonWidth, buttonHeight); + initialToolbarSetup(buttonWidth); // -------------------------------- BUTTONS ------------------------------- // -------- Add primitve button -------- @@ -287,10 +284,10 @@ namespace nexo::editor { ImGui::SameLine(); // -------- Gizmo operation button -------- - static const ImNexo::ButtonProps gizmoTranslateButtonProps = ImNexo::ButtonProps{"translate", ICON_FA_ARROWS, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE;}, nullptr, "Translate"}; - static const ImNexo::ButtonProps gizmoRotateButtonProps = ImNexo::ButtonProps{"rotate", ICON_FA_REFRESH, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE;}, nullptr, "Rotate"}; - static const ImNexo::ButtonProps gizmoScaleButtonProps = ImNexo::ButtonProps{"scale", ICON_FA_EXPAND, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE;}, nullptr, "Scale"}; - static const ImNexo::ButtonProps gizmoUniversalButtonProps = ImNexo::ButtonProps{"universal", ICON_FA_ARROWS_ALT, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::UNIVERSAL;}, nullptr, "Universal"}; + static const auto gizmoTranslateButtonProps = ImNexo::ButtonProps{"translate", ICON_FA_ARROWS, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::TRANSLATE;}, nullptr, "Translate"}; + static const auto gizmoRotateButtonProps = ImNexo::ButtonProps{"rotate", ICON_FA_REFRESH, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::ROTATE;}, nullptr, "Rotate"}; + static const auto gizmoScaleButtonProps = ImNexo::ButtonProps{"scale", ICON_FA_EXPAND, [this]() {this->m_currentGizmoOperation = ImGuizmo::OPERATION::SCALE;}, nullptr, "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, @@ -358,7 +355,6 @@ namespace nexo::editor { if (renderToolbarButton("grid_enabled", ICON_FA_TH_LARGE, "Enable / Disable grid", gridParams.enabled ? m_selectedGradient : m_buttonGradient, &rightClicked)) { gridParams.enabled = !gridParams.enabled; - } if (rightClicked) m_popupManager.openPopup("Grid settings", ImVec2(300, 180)); diff --git a/editor/src/DocumentWindows/EditorScene/Update.cpp b/editor/src/DocumentWindows/EditorScene/Update.cpp index f913ea6db..c47e52dd5 100644 --- a/editor/src/DocumentWindows/EditorScene/Update.cpp +++ b/editor/src/DocumentWindows/EditorScene/Update.cpp @@ -18,21 +18,21 @@ #include "components/Uuid.hpp" namespace nexo::editor { - int EditorScene::sampleEntityTexture(float mx, float my) + int EditorScene::sampleEntityTexture(const float mx, const float my) const { - const auto &coord = getApp().m_coordinator; + const auto &coord = Application::m_coordinator; const auto &cameraComponent = coord->getComponent(static_cast(m_activeCamera)); cameraComponent.m_renderTarget->bind(); - 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; } - static SelectionType getSelectionType(int entityId) + static SelectionType getSelectionType(const int entityId) { - const auto &coord = getApp().m_coordinator; - SelectionType selType = SelectionType::ENTITY; + const auto &coord = Application::m_coordinator; + auto selType = SelectionType::ENTITY; if (coord->entityHasComponent(entityId)) { selType = SelectionType::CAMERA; } else if (coord->entityHasComponent(entityId)) { @@ -62,15 +62,15 @@ namespace nexo::editor { } } - void EditorScene::updateSelection(int entityId, bool isShiftPressed, bool isCtrlPressed) + void EditorScene::updateSelection(const int entityId, const bool isShiftPressed, const bool isCtrlPressed) { - const auto &coord = getApp().m_coordinator; + const auto &coord = Application::m_coordinator; const auto uuid = coord->tryGetComponent(entityId); if (!uuid) return; // Determine selection type - SelectionType selType = getSelectionType(entityId); + const SelectionType selType = getSelectionType(entityId); auto &selector = Selector::get(); // Handle different selection modes @@ -97,11 +97,11 @@ namespace nexo::editor { // Check if mouse is inside viewport if (!(mx >= 0 && my >= 0 && mx < m_contentSize.x && my < m_contentSize.y)) return; - int entityId = sampleEntityTexture(mx, my); + const int entityId = sampleEntityTexture(mx, my); // Check for multi-selection key modifiers - bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); - bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + 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(); if (entityId == -1) { @@ -118,12 +118,11 @@ namespace nexo::editor { void EditorScene::update() { - auto &selector = Selector::get(); - bool is_currently_visible = m_isVisibleInDock || m_wasVisibleLastFrame; + const bool isCurrentlyVisible = m_isVisibleInDock || m_wasVisibleLastFrame; - if (!m_opened || m_activeCamera == -1 || !is_currently_visible) + if (!m_opened || m_activeCamera == -1 || !isCurrentlyVisible) return; - SceneType sceneType = m_activeCamera == m_editorCamera ? SceneType::EDITOR : SceneType::GAME; + 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.viewportBounds[0] = glm::vec2{m_viewportBounds[0].x, m_viewportBounds[0].y}; diff --git a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp index 121fd95c1..b53fc5f5e 100644 --- a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp @@ -18,6 +18,7 @@ #include "context/actions/EntityActions.hpp" #include "context/ActionManager.hpp" #include "ImNexo/EntityProperties.hpp" +#include "ImNexo/ImNexo.hpp" #include namespace nexo::editor { diff --git a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.hpp b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.hpp index 45f279d89..f59cac886 100644 --- a/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.hpp @@ -16,7 +16,7 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class AmbientLightProperty : public AEntityProperty { + class AmbientLightProperty final : public AEntityProperty { public: using AEntityProperty::AEntityProperty; diff --git a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp index f06daa688..d869a6d95 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraController.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraController.cpp @@ -30,7 +30,7 @@ namespace nexo::editor { if (ImNexo::Header("##ControllerNode", "Camera Controller")) { ImGui::Spacing(); - auto controllerComponentCopy = controllerComponent; + const auto controllerComponentCopy = controllerComponent; ImNexo::resetItemStates(); ImNexo::CameraController(controllerComponent); if (ImNexo::isItemActivated()) { diff --git a/editor/src/DocumentWindows/EntityProperties/CameraController.hpp b/editor/src/DocumentWindows/EntityProperties/CameraController.hpp index b263c1ece..18a620016 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraController.hpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraController.hpp @@ -16,7 +16,7 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class CameraController : public AEntityProperty { + class CameraController final : public AEntityProperty { public: using AEntityProperty::AEntityProperty; diff --git a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp index 20d95a848..f58efc729 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp @@ -29,7 +29,7 @@ namespace nexo::editor { if (ImNexo::Header("##CameraNode", "Camera")) { - auto cameraComponentCopy = cameraComponent; + const auto cameraComponentCopy = cameraComponent; ImNexo::resetItemStates(); ImNexo::Camera(cameraComponent); if (ImNexo::isItemActivated()) { diff --git a/editor/src/DocumentWindows/EntityProperties/CameraProperty.hpp b/editor/src/DocumentWindows/EntityProperties/CameraProperty.hpp index d80c49fdb..aa771fc6c 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraProperty.hpp @@ -16,7 +16,7 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class CameraProperty : public AEntityProperty { + class CameraProperty final : public AEntityProperty { public: using AEntityProperty::AEntityProperty; diff --git a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp index 4116ca293..0ec702f32 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp @@ -29,7 +29,7 @@ namespace nexo::editor { if (ImNexo::Header("##TargetNode", "Camera Target")) { - auto targetComponentCopy = targetComponent; + const auto targetComponentCopy = targetComponent; ImGui::Spacing(); ImNexo::resetItemStates(); ImNexo::CameraTarget(targetComponent); diff --git a/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp b/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp index bf207a495..30598254c 100644 --- a/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp +++ b/editor/src/DocumentWindows/EntityProperties/CameraTarget.hpp @@ -16,7 +16,7 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class CameraTarget : public AEntityProperty { + class CameraTarget final : public AEntityProperty { public: using AEntityProperty::AEntityProperty; diff --git a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp index ced02b3ef..f972cba18 100644 --- a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.cpp @@ -28,7 +28,7 @@ namespace nexo::editor { if (ImNexo::Header("##DirectionalNode", "Directional light")) { - auto directionalComponentCopy = directionalComponent; + const auto directionalComponentCopy = directionalComponent; ImNexo::resetItemStates(); ImNexo::DirectionalLight(directionalComponent); if (ImNexo::isItemActivated()) { diff --git a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.hpp b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.hpp index 143319525..9aa41f16c 100644 --- a/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/DirectionalLightProperty.hpp @@ -16,7 +16,7 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class DirectionalLightProperty : public AEntityProperty { + class DirectionalLightProperty final : public AEntityProperty { public: using AEntityProperty::AEntityProperty; diff --git a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp index e861cb61f..5264c1f12 100644 --- a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp @@ -18,7 +18,6 @@ #include "components/Light.hpp" #include "components/Transform.hpp" #include "context/actions/EntityActions.hpp" -#include "math/Light.hpp" #include "ImNexo/Widgets.hpp" #include "context/ActionManager.hpp" @@ -26,7 +25,7 @@ namespace nexo::editor { void PointLightProperty::show(const ecs::Entity entity) { - auto& pointComponent = nexo::Application::getEntityComponent(entity); + auto& pointComponent = Application::getEntityComponent(entity); auto &transform = Application::getEntityComponent(entity); static components::PointLightComponent::Memento beforeStatePoint; diff --git a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.hpp b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.hpp index decacab67..07baa7547 100644 --- a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.hpp @@ -16,7 +16,7 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class PointLightProperty : public AEntityProperty { + class PointLightProperty final : public AEntityProperty { public: using AEntityProperty::AEntityProperty; diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index 3a8d87c7d..f6ad6b4b2 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -32,7 +32,7 @@ namespace nexo::editor { - void RenderProperty::createMaterialPopup(ecs::Entity entity) const + void RenderProperty::createMaterialPopup(const ecs::Entity entity) { ImGui::Text("Create New Material"); ImGui::Separator(); @@ -74,7 +74,7 @@ namespace nexo::editor { ImGui::BeginChild("MaterialPreview", ImVec2(previewWidth - 4, totalHeight), true); auto &app = getApp(); - Application::SceneInfo sceneInfo{static_cast(scenePreviewInfo.sceneId), nexo::RenderingType::FRAMEBUFFER}; + const Application::SceneInfo sceneInfo{scenePreviewInfo.sceneId, nexo::RenderingType::FRAMEBUFFER}; app.run(sceneInfo); auto const &cameraComponent = Application::m_coordinator->getComponent(scenePreviewInfo.cameraId); const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); @@ -175,7 +175,7 @@ namespace nexo::editor { utils::genScenePreview("Modify material inspector", {64, 64}, entity, previewParams); auto &app = nexo::getApp(); app.getSceneManager().getScene(previewParams.sceneId).setActiveStatus(false); - Application::SceneInfo sceneInfo{static_cast(previewParams.sceneId), nexo::RenderingType::FRAMEBUFFER}; + const Application::SceneInfo sceneInfo{previewParams.sceneId, nexo::RenderingType::FRAMEBUFFER}; app.run(sceneInfo); const auto &cameraComponent = Application::m_coordinator->getComponent(previewParams.cameraId); framebuffer = cameraComponent.m_renderTarget; diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.hpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.hpp index a31bca12f..b83ea1ebb 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.hpp @@ -18,7 +18,7 @@ #include "DocumentWindows/PopupManager.hpp" namespace nexo::editor { - class RenderProperty : public nexo::editor::AEntityProperty { + class RenderProperty final : public AEntityProperty { public: using AEntityProperty::AEntityProperty; @@ -46,7 +46,7 @@ namespace nexo::editor { * * @param entity The entity associated with the material being created. */ - void createMaterialPopup(ecs::Entity entity) const; + static void createMaterialPopup(ecs::Entity entity); private: PopupManager m_popupManager; }; diff --git a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp index ec56a0a02..7630696fb 100644 --- a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp @@ -18,7 +18,6 @@ #include "components/Light.hpp" #include "components/Transform.hpp" #include "context/actions/EntityActions.hpp" -#include "math/Light.hpp" #include "ImNexo/Widgets.hpp" #include "context/ActionManager.hpp" @@ -44,7 +43,7 @@ namespace nexo::editor { } else if (ImNexo::isItemDeactivated()) { auto afterStateSpot = spotComponent.save(); auto afterStateTransform = transformComponent.save(); - auto actionGroup = ActionManager::get().createActionGroup(); + 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)); diff --git a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.hpp b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.hpp index 99667a40b..cf451c3b1 100644 --- a/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/SpotLightProperty.hpp @@ -16,7 +16,7 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class SpotLightProperty : public AEntityProperty { + class SpotLightProperty final : public AEntityProperty { public: using AEntityProperty::AEntityProperty; diff --git a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp index 50203a7cc..daefd6a58 100644 --- a/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp @@ -35,7 +35,7 @@ namespace nexo::editor { if (ImNexo::Header("##TransformNode", "Transform Component")) { - auto transformComponentCopy = transformComponent; + const auto transformComponentCopy = transformComponent; ImNexo::resetItemStates(); ImNexo::Transform(transformComponent, lastDisplayedEuler); if (ImNexo::isItemActivated()) { diff --git a/editor/src/DocumentWindows/EntityProperties/TransformProperty.hpp b/editor/src/DocumentWindows/EntityProperties/TransformProperty.hpp index a4a3f6076..9f844dded 100644 --- a/editor/src/DocumentWindows/EntityProperties/TransformProperty.hpp +++ b/editor/src/DocumentWindows/EntityProperties/TransformProperty.hpp @@ -17,7 +17,7 @@ #include "AEntityProperty.hpp" namespace nexo::editor { - class TransformProperty : public nexo::editor::AEntityProperty { + class TransformProperty final : public AEntityProperty { public: using AEntityProperty::AEntityProperty; diff --git a/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp index c3d7d5f2e..f745ed7a5 100644 --- a/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp +++ b/editor/src/DocumentWindows/InspectorWindow/InspectorWindow.hpp @@ -17,7 +17,6 @@ #include "DocumentWindows/EntityProperties/AEntityProperty.hpp" #include "core/scene/SceneManager.hpp" -#include #include namespace nexo::editor { @@ -63,7 +62,7 @@ namespace nexo::editor { * @param visible The desired visibility state (true for visible, false for hidden). */ template - void setSubInspectorVisibility(bool visible) + void setSubInspectorVisibility(const bool visible) { m_subInspectorVisibility[std::type_index(typeid(T))] = visible; } @@ -155,7 +154,7 @@ namespace nexo::editor { * * @param sceneId The identifier of the scene whose properties are to be displayed. */ - void showSceneProperties(scene::SceneId sceneId) const; + static void showSceneProperties(scene::SceneId sceneId); /** * @brief Renders the UI for the properties of an entity's components. diff --git a/editor/src/DocumentWindows/InspectorWindow/Show.cpp b/editor/src/DocumentWindows/InspectorWindow/Show.cpp index a617d643e..e95d90566 100644 --- a/editor/src/DocumentWindows/InspectorWindow/Show.cpp +++ b/editor/src/DocumentWindows/InspectorWindow/Show.cpp @@ -19,7 +19,7 @@ namespace nexo::editor { - void InspectorWindow::showSceneProperties(const scene::SceneId sceneId) const + void InspectorWindow::showSceneProperties(const scene::SceneId sceneId) { auto &app = getApp(); auto &selector = Selector::get(); @@ -58,7 +58,7 @@ namespace nexo::editor { void InspectorWindow::showEntityProperties(const ecs::Entity entity) { - const std::vector componentsType = nexo::Application::getAllEntityComponentTypes(entity); + const std::vector componentsType = Application::getAllEntityComponentTypes(entity); for (auto& type : componentsType) { if (m_entityProperties.contains(type)) diff --git a/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp b/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp index 27b93db47..5357301fa 100644 --- a/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp +++ b/editor/src/DocumentWindows/MaterialInspector/MaterialInspector.hpp @@ -17,7 +17,7 @@ namespace nexo::editor { - class MaterialInspector : public ADocumentWindow { + class MaterialInspector final : public ADocumentWindow { public: using ADocumentWindow::ADocumentWindow; void setup() override; diff --git a/editor/src/DocumentWindows/MaterialInspector/Show.cpp b/editor/src/DocumentWindows/MaterialInspector/Show.cpp index 2e80ed43b..2c3998c4d 100644 --- a/editor/src/DocumentWindows/MaterialInspector/Show.cpp +++ b/editor/src/DocumentWindows/MaterialInspector/Show.cpp @@ -21,7 +21,7 @@ namespace nexo::editor { - void MaterialInspector::renderMaterialInspector(int selectedEntity) + void MaterialInspector::renderMaterialInspector(const int selectedEntity) { bool &materialModified = m_materialModified; static utils::ScenePreviewOut previewParams; @@ -38,7 +38,7 @@ namespace nexo::editor { { m_ecsEntity = -1; } - } + } if (m_ecsEntity == -1) return; @@ -46,10 +46,10 @@ namespace nexo::editor { if (materialModified) { utils::genScenePreview("Modify material inspector", {64, 64}, m_ecsEntity, previewParams); - auto &app = nexo::getApp(); - Application::SceneInfo sceneInfo{static_cast(previewParams.sceneId), nexo::RenderingType::FRAMEBUFFER}; + auto &app = getApp(); + const Application::SceneInfo sceneInfo{previewParams.sceneId, RenderingType::FRAMEBUFFER}; app.run(sceneInfo); - const auto &cameraComponent = nexo::Application::m_coordinator->getComponent(previewParams.cameraId); + const auto &cameraComponent = Application::m_coordinator->getComponent(previewParams.cameraId); m_framebuffer = cameraComponent.m_renderTarget; materialModified = false; app.getSceneManager().deleteScene(previewParams.sceneId); @@ -62,7 +62,7 @@ namespace nexo::editor { ImNexo::Image(static_cast(static_cast(m_framebuffer->getColorAttachmentId(0))), {64, 64}); ImGui::SameLine(); - auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); + const auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); if (!inspectorWindow) return; auto materialVariant = inspectorWindow->getSubInspectorData(); @@ -72,23 +72,23 @@ namespace nexo::editor { void MaterialInspector::show() { - auto const &selector = Selector::get(); - const int selectedEntity = selector.getPrimaryEntity(); - auto inspectorWindow = m_windowRegistry.getWindow(NEXO_WND_USTRID_INSPECTOR).lock(); - if (!inspectorWindow) - return; + auto const &selector = Selector::get(); + const int selectedEntity = selector.getPrimaryEntity(); + 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; + { + 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(selectedEntity); - } - ImGui::End(); - } + if (ImGui::Begin("Material Inspector" NEXO_WND_USTRID_MATERIAL_INSPECTOR, &inspectorWindow->getSubInspectorVisibility(), window_flags)) + { + beginRender(NEXO_WND_USTRID_MATERIAL_INSPECTOR); + renderMaterialInspector(selectedEntity); + } + ImGui::End(); + } } } diff --git a/editor/src/DocumentWindows/PopupManager.cpp b/editor/src/DocumentWindows/PopupManager.cpp index 73f911d1a..ef856f6f9 100644 --- a/editor/src/DocumentWindows/PopupManager.cpp +++ b/editor/src/DocumentWindows/PopupManager.cpp @@ -18,10 +18,12 @@ #include +#include + namespace nexo::editor { void PopupManager::openPopup(const std::string &popupName, const ImVec2 &popupSize) { - PopupProps props{ + const PopupProps props{ .open = true, .callback = nullptr, .size = popupSize @@ -31,20 +33,20 @@ namespace nexo::editor { void PopupManager::openPopupWithCallback(const std::string &popupName, PopupCallback callback, const ImVec2 &popupSize) { - PopupProps props{ + const PopupProps props{ .open = true, - .callback = callback, + .callback = std::move(callback), .size = popupSize }; m_popups[popupName] = props; } - void PopupManager::closePopup() const + void PopupManager::closePopup() { ImGui::EndPopup(); } - void PopupManager::closePopupInContext() const + void PopupManager::closePopupInContext() { ImGui::CloseCurrentPopup(); } @@ -87,9 +89,9 @@ namespace nexo::editor { | ImGuiWindowFlags_NoTitleBar; if (!ImGui::BeginPopupModal(popupModalName.c_str(), nullptr, flags)) return false; - ImVec2 pMin = ImGui::GetWindowPos(); - ImVec2 size = ImGui::GetWindowSize(); - ImVec2 pMax = ImVec2(pMin.x + size.x, pMin.y + size.y); + 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 = { @@ -98,14 +100,14 @@ namespace nexo::editor { { 0.50f, IM_COL32(88 / 3, 87 / 3, 154 / 3, 255) }, { 0.73f, IM_COL32(58 / 3, 124 / 3, 161 / 3, 255) }, }; - float angle = 148.0f; + constexpr float angle = 148.0f; ImNexo::RectFilledLinearGradient(pMin, pMax, angle, stops, drawList); return true; } - void PopupManager::runPopupCallback(const std::string &popupName) + void PopupManager::runPopupCallback(const std::string &popupName) const { if (m_popups.contains(popupName) && m_popups.at(popupName).callback != nullptr) { diff --git a/editor/src/DocumentWindows/PopupManager.hpp b/editor/src/DocumentWindows/PopupManager.hpp index 495746d12..cbd4e848e 100644 --- a/editor/src/DocumentWindows/PopupManager.hpp +++ b/editor/src/DocumentWindows/PopupManager.hpp @@ -91,14 +91,14 @@ namespace nexo::editor { * * Ends the current ImGui popup. */ - void closePopup() const; + static void closePopup() ; /** * @brief Closes the current popup in its context. * * Requests ImGui to close the current popup. */ - void closePopupInContext() const; + static void closePopupInContext() ; /** * @brief Executes the callback associated with a popup. @@ -108,7 +108,7 @@ namespace nexo::editor { * * @param popupName The name of the popup whose callback should be executed. */ - void runPopupCallback(const std::string &popupName); + void runPopupCallback(const std::string &popupName) const; private: struct TransparentHasher { diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp index 2335d54ea..218f6ffcd 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp @@ -16,10 +16,9 @@ namespace nexo::editor { - void SceneTreeWindow::cameraHovered(const SceneObject &obj) const + void SceneTreeWindow::cameraHovered(const SceneObject &obj) { - auto &app = Application::getInstance(); - auto &cameraComponent = app.m_coordinator->getComponent(obj.data.entity); + auto &cameraComponent = Application::m_coordinator->getComponent(obj.data.entity); if (cameraComponent.m_renderTarget) { @@ -37,7 +36,7 @@ namespace nexo::editor { } } - void SceneTreeWindow::handleHovering(const SceneObject &obj) const + void SceneTreeWindow::handleHovering(const SceneObject &obj) { if (obj.type == SelectionType::CAMERA) { static bool cameraHoveredLastFrame = false; @@ -46,7 +45,7 @@ namespace nexo::editor { cameraHoveredLastFrame = true; } else if (cameraHoveredLastFrame) { cameraHoveredLastFrame = false; - auto &cameraComponent = Application::getInstance().m_coordinator->getComponent(obj.data.entity); + auto &cameraComponent = Application::m_coordinator->getComponent(obj.data.entity); cameraComponent.render = false; } } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp b/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp index d12d8f4e0..dc256f3bd 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp @@ -18,7 +18,7 @@ namespace nexo::editor { // Node creation methods - SceneObject SceneTreeWindow::newSceneNode(const std::string &sceneName, const scene::SceneId sceneId, const WindowId uiId) const + 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; @@ -32,7 +32,7 @@ namespace nexo::editor { return sceneNode; } - void SceneTreeWindow::newLightNode(SceneObject &lightNode, const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity, const std::string &uiName) const + 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; @@ -47,7 +47,7 @@ namespace nexo::editor { lightNode.uiName = uiName; } - SceneObject SceneTreeWindow::newAmbientLightNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity) const + SceneObject SceneTreeWindow::newAmbientLightNode(const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity lightEntity) { SceneObject lightNode; lightNode.type = SelectionType::AMBIENT_LIGHT; @@ -84,7 +84,7 @@ namespace nexo::editor { } SceneObject SceneTreeWindow::newCameraNode(const scene::SceneId sceneId, const WindowId uiId, - const ecs::Entity cameraEntity) const + const ecs::Entity cameraEntity) { SceneObject cameraNode; const std::string uiName = ObjectTypeToIcon.at(SelectionType::CAMERA) + std::string("Camera"); @@ -104,7 +104,7 @@ namespace nexo::editor { } SceneObject SceneTreeWindow::newEntityNode(const scene::SceneId sceneId, const WindowId uiId, - const ecs::Entity entity) const + const ecs::Entity entity) { auto &selector = Selector::get(); SceneObject entityNode; diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp index aa8b5cbb0..8c86ed706 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp @@ -17,22 +17,22 @@ namespace nexo::editor { - bool SceneTreeWindow::setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName) + bool SceneTreeWindow::setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName) const { - ImGuiWindow* floatingWindow = ImGui::FindWindowByName(floatingWindowName.c_str()); + const ImGuiWindow* floatingWindow = ImGui::FindWindowByName(floatingWindowName.c_str()); if (!floatingWindow) return false; // Create a new docking node - auto newDockId = ImGui::GetID("##DockNode"); + const auto newDockId = ImGui::GetID("##DockNode"); // Configure the docking node ImGui::DockBuilderRemoveNode(newDockId); ImGui::DockBuilderAddNode(newDockId, ImGuiDockNodeFlags_None); // Set node size and position based on the floating window - ImVec2 windowPos = floatingWindow->Pos; - ImVec2 windowSize = floatingWindow->Size; + const ImVec2 windowPos = floatingWindow->Pos; + const ImVec2 windowSize = floatingWindow->Size; ImGui::DockBuilderSetNodeSize(newDockId, windowSize); ImGui::DockBuilderSetNodePos(newDockId, windowPos); @@ -46,14 +46,14 @@ namespace nexo::editor { return true; } - bool SceneTreeWindow::handleSceneCreation(const std::string &newSceneName) + bool SceneTreeWindow::handleSceneCreation(const std::string &newSceneName) const { if (newSceneName.empty()) { LOG(NEXO_WARN, "Scene name is empty !"); return false; } - auto newScene = std::make_shared(newSceneName, m_windowRegistry); + const auto newScene = std::make_shared(newSceneName, m_windowRegistry); newScene->setDefault(); newScene->setup(); m_windowRegistry.registerWindow(newScene); @@ -63,7 +63,7 @@ namespace nexo::editor { if (currentEditorSceneWindow.empty()) { const std::vector &editorSceneInConfig = findAllEditorScenes(); if (!editorSceneInConfig.empty()) { - auto dockId = m_windowRegistry.getDockId(editorSceneInConfig[0]); + const auto dockId = m_windowRegistry.getDockId(editorSceneInConfig[0]); if (!dockId) return false; m_windowRegistry.setDockId(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId()), *dockId); @@ -75,7 +75,7 @@ namespace nexo::editor { // Else we retrieve the first active editor scene const std::string windowName = std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(currentEditorSceneWindow[0]->getSceneId()); - auto dockId = m_windowRegistry.getDockId(windowName); + 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 (!dockId) { setupNewDockSpaceNode(windowName, std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId())); diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp index e4f07c77b..0d06fd971 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp @@ -78,7 +78,7 @@ namespace nexo::editor { explicit SceneObject(std::string name = "", std::vector children = {}, SelectionType type = SelectionType::NONE, EntityProperties data = {}) - : uiName(std::move(name)), type(type), data(std::move(data)), children(std::move(children)) {} + : uiName(std::move(name)), type(type), data(data), children(std::move(children)) {} }; /** @@ -87,7 +87,7 @@ namespace nexo::editor { * The SceneTreeWindow class is responsible for drawing the scene tree, handling selection, * renaming, context menus, and scene/node creation. */ - class SceneTreeWindow : public ADocumentWindow { + class SceneTreeWindow final : public ADocumentWindow { public: using ADocumentWindow::ADocumentWindow; ~SceneTreeWindow() override = default; @@ -141,12 +141,12 @@ namespace nexo::editor { * @param nodeCreator Function that creates a SceneObject given a scene ID, UI window ID, and entity. */ template - void generateNodes(std::map &scenes, NodeCreator nodeCreator) + static void generateNodes(std::map &scenes, NodeCreator nodeCreator) { - const std::vector entities = nexo::Application::m_coordinator->getAllEntitiesWith(); + const std::vector entities = Application::m_coordinator->getAllEntitiesWith(); for (const ecs::Entity entity : entities) { - const auto& sceneTag = nexo::Application::m_coordinator->getComponent(entity); + const auto& sceneTag = Application::m_coordinator->getComponent(entity); if (auto it = scenes.find(sceneTag.id); it != scenes.end()) { SceneObject newNode = nodeCreator(it->second.data.sceneProperties.sceneId, it->second.data.sceneProperties.windowId, entity); @@ -168,7 +168,7 @@ namespace nexo::editor { * @param uiId The identifier for the scene's UI element. * @return SceneObject The newly created scene node with initialized properties. */ - SceneObject newSceneNode(const std::string &sceneName, const scene::SceneId sceneId, const WindowId uiId) const; + static SceneObject newSceneNode(const std::string &sceneName, scene::SceneId sceneId, WindowId uiId); /** * @brief Creates a new light node and adds properties to it. @@ -179,7 +179,7 @@ namespace nexo::editor { * @param lightEntity The light entity. * @param uiName The UI display name. */ - void newLightNode(SceneObject &lightNode, scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity, const std::string &uiName) const; + static void newLightNode(SceneObject &lightNode, scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity, const std::string &uiName); /** * @brief Creates a new ambient light node. @@ -189,7 +189,7 @@ namespace nexo::editor { * @param lightEntity The ambient light entity. * @return A SceneObject representing the ambient light. */ - SceneObject newAmbientLightNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity) const; + static SceneObject newAmbientLightNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity lightEntity); /** * @brief Creates a new directional light node. * @@ -228,7 +228,7 @@ namespace nexo::editor { * @param cameraEntity The camera entity. * @return A SceneObject representing the camera. */ - SceneObject newCameraNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity cameraEntity) const; + static SceneObject newCameraNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity cameraEntity) ; /** * @brief Creates a new entity node. @@ -238,7 +238,7 @@ namespace nexo::editor { * @param entity The entity. * @return A SceneObject representing the entity. */ - SceneObject newEntityNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity entity) const; + static SceneObject newEntityNode(scene::SceneId sceneId, WindowId uiId, ecs::Entity entity) ; /** * @brief Handles the renaming of a scene object. @@ -261,7 +261,7 @@ namespace nexo::editor { * @param baseFlags ImGui tree node flags to customize the node's appearance. * @return true if the tree node is open (expanded); false otherwise. */ - bool handleSelection(const SceneObject &obj, const std::string &uniqueLabel, ImGuiTreeNodeFlags baseFlags) const; + static bool handleSelection(const SceneObject &obj, const std::string &uniqueLabel, ImGuiTreeNodeFlags baseFlags) ; /** * @brief Handles camera preview tooltips when hovering over camera nodes. @@ -271,7 +271,7 @@ namespace nexo::editor { * * @param obj The scene object (camera) being hovered over. */ - void handleHovering(const SceneObject &obj) const; + static void handleHovering(const SceneObject &obj) ; /** * @brief Displays a context menu option to delete a scene. @@ -291,7 +291,7 @@ namespace nexo::editor { * * @param obj The scene object representing the light node to delete. */ - void lightSelected(const SceneObject &obj) const; + static void lightSelected(const SceneObject &obj) ; /** * @brief Displays a context menu option for deleting a camera. @@ -312,7 +312,7 @@ namespace nexo::editor { * * @param obj The scene object representing the camera being hovered. */ - void cameraHovered(const SceneObject &obj) const; + static void cameraHovered(const SceneObject &obj); /** * @brief Displays context menu options for an entity. @@ -322,7 +322,7 @@ namespace nexo::editor { * * @param obj The scene object representing the entity. */ - void entitySelected(const SceneObject &obj) const; + static void entitySelected(const SceneObject &obj) ; /** * @brief Renders a node and its children in the scene tree. @@ -363,7 +363,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); + bool handleSceneCreation(const std::string &newSceneName) const; /** * @brief Sets up docking for a new scene window. @@ -375,7 +375,7 @@ 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); + bool setupNewDockSpaceNode(const std::string &floatingWindowName, const std::string &newSceneName) const; enum class SceneTreeState { GLOBAL, @@ -396,9 +396,10 @@ namespace nexo::editor { void expandAllCallback(); void collapseAllCallback(); void renameSelectedCallback(); - void duplicateSelectedCallback(); - void selectAllCallback(); - void hideSelectedCallback(); - void showAllCallback(); + static void duplicateSelectedCallback(); + + static void selectAllCallback(); + static void hideSelectedCallback(); + static void showAllCallback(); }; } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp index fb93115b4..abdb55c21 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp @@ -14,9 +14,7 @@ #include "SceneTreeWindow.hpp" #include "EntityFactory3D.hpp" -#include "LightFactory.hpp" #include "utils/EditorProps.hpp" -#include "ImNexo/Panels.hpp" #include "context/ActionManager.hpp" #include "context/actions/EntityActions.hpp" @@ -24,16 +22,16 @@ namespace nexo::editor { - void SceneTreeWindow::entitySelected(const SceneObject &obj) const + void SceneTreeWindow::entitySelected(const SceneObject &obj) { auto &selector = Selector::get(); auto &app = nexo::getApp(); // Check if we're operating on a single item or multiple items const auto& selectedEntities = selector.getSelectedEntities(); - bool multipleSelected = selectedEntities.size() > 1; + const bool multipleSelected = selectedEntities.size() > 1; - std::string menuText = multipleSelected ? + const std::string menuText = multipleSelected ? "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : "Delete Entity"; @@ -41,7 +39,7 @@ namespace nexo::editor { { if (multipleSelected) { // Delete all selected entities - auto actionGroup = ActionManager::get().createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto& entityId : selectedEntities) { auto deleteAction = std::make_unique(entityId); actionGroup->addAction(std::move(deleteAction)); @@ -66,9 +64,9 @@ namespace nexo::editor { // Check if we're operating on a single item or multiple items const auto& selectedEntities = selector.getSelectedEntities(); - bool multipleSelected = selectedEntities.size() > 1; + const bool multipleSelected = selectedEntities.size() > 1; - std::string deleteMenuText = multipleSelected ? + const std::string deleteMenuText = multipleSelected ? "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : "Delete Camera"; @@ -76,7 +74,7 @@ namespace nexo::editor { { if (multipleSelected) { // Delete all selected entities - auto actionGroup = ActionManager::get().createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto& entityId : selectedEntities) { auto deleteAction = std::make_unique(entityId); actionGroup->addAction(std::move(deleteAction)); @@ -96,7 +94,7 @@ namespace nexo::editor { // Switch to camera only makes sense for a single camera if (!multipleSelected && ImGui::MenuItem("Switch to")) { - auto &cameraComponent = app.m_coordinator->getComponent(obj.data.entity); + auto &cameraComponent = Application::m_coordinator->getComponent(obj.data.entity); cameraComponent.render = true; cameraComponent.active = true; const auto &scenes = m_windowRegistry.getWindows(); @@ -109,16 +107,16 @@ namespace nexo::editor { } } - void SceneTreeWindow::lightSelected(const SceneObject &obj) const + void SceneTreeWindow::lightSelected(const SceneObject &obj) { 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(); - bool multipleSelected = selectedEntities.size() > 1; + const bool multipleSelected = selectedEntities.size() > 1; - std::string menuText = multipleSelected ? + const std::string menuText = multipleSelected ? "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : "Delete Light"; @@ -126,7 +124,7 @@ namespace nexo::editor { { if (multipleSelected) { // Delete all selected entities - auto actionGroup = ActionManager::get().createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto& entityId : selectedEntities) { auto deleteAction = std::make_unique(entityId); actionGroup->addAction(std::move(deleteAction)); @@ -153,7 +151,7 @@ namespace nexo::editor { } bool SceneTreeWindow::handleSelection(const SceneObject &obj, const std::string &uniqueLabel, - const ImGuiTreeNodeFlags baseFlags) const + const ImGuiTreeNodeFlags baseFlags) { const bool nodeOpen = ImGui::TreeNodeEx(uniqueLabel.c_str(), baseFlags); if (!nodeOpen) @@ -162,16 +160,16 @@ namespace nexo::editor { if (ImGui::IsItemClicked()) { auto &selector = Selector::get(); - bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); - bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + const bool isShiftPressed = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); + const bool isCtrlPressed = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); if (isCtrlPressed) - selector.toggleSelection(obj.uuid, obj.data.entity, obj.type); + selector.toggleSelection(obj.uuid, static_cast(obj.data.entity), obj.type); else if (isShiftPressed) - selector.addToSelection(obj.uuid, obj.data.entity, obj.type); + selector.addToSelection(obj.uuid, static_cast(obj.data.entity), obj.type); else - selector.selectEntity(obj.uuid, obj.data.entity, obj.type); - selector.setSelectedScene(obj.data.sceneProperties.sceneId); + selector.selectEntity(obj.uuid, static_cast(obj.data.entity), obj.type); + selector.setSelectedScene(static_cast(obj.data.sceneProperties.sceneId)); } return nodeOpen; } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp index f2a0c84cd..e5181eb03 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp @@ -40,21 +40,21 @@ namespace nexo::editor { .description("Select all") .key("A") .onPressed([this](){ - this->selectAllCallback(); }) + selectAllCallback(); }) .build() ) .addChild( Command::create() .description("Duplicate") .key("D") - .onPressed([this](){ this->duplicateSelectedCallback(); }) + .onPressed([this](){ duplicateSelectedCallback(); }) .build() ) .addChild( Command::create() .description("Unhide all") .key("H") - .onPressed([this](){ this->showAllCallback(); }) + .onPressed([this](){ showAllCallback(); }) .build() ) .addChild( @@ -117,7 +117,7 @@ namespace nexo::editor { Command::create() .description("Hide") .key("H") - .onPressed([this](){ this->hideSelectedCallback(); }) + .onPressed([this](){ hideSelectedCallback(); }) .build() ); } @@ -147,9 +147,9 @@ namespace nexo::editor { selector.clearSelection(); for (const auto entity : scene.getEntities()) { - const auto uuidComponent = app.m_coordinator->tryGetComponent(entity); + const auto uuidComponent = Application::m_coordinator->tryGetComponent(entity); if (uuidComponent) { - selector.addToSelection(uuidComponent->get().uuid, entity); + selector.addToSelection(uuidComponent->get().uuid, static_cast(entity)); } } } @@ -162,18 +162,18 @@ namespace nexo::editor { if (selectedEntities.empty()) return; - auto& app = nexo::getApp(); + auto& app = getApp(); auto& actionManager = ActionManager::get(); if (selectedEntities.size() > 1) { - auto actionGroup = actionManager.createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : selectedEntities) { - actionGroup->addAction(actionManager.prepareEntityDeletion(entity)); + actionGroup->addAction(ActionManager::prepareEntityDeletion(entity)); app.deleteEntity(entity); } actionManager.recordAction(std::move(actionGroup)); } else { - auto deleteAction = actionManager.prepareEntityDeletion(selectedEntities[0]); + auto deleteAction = ActionManager::prepareEntityDeletion(selectedEntities[0]); app.deleteEntity(selectedEntities[0]); actionManager.recordAction(std::move(deleteAction)); } @@ -214,14 +214,14 @@ namespace nexo::editor { std::vector newEntities; newEntities.reserve(selectedEntities.size()); - auto actionGroup = actionManager.createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); selector.clearSelection(); for (const auto entity : selectedEntities) { - ecs::Entity newEntity = app.m_coordinator->duplicateEntity(entity); - components::UuidComponent uuidComponent; - app.m_coordinator->getComponent(newEntity) = uuidComponent; - app.m_coordinator->removeComponent(newEntity); + ecs::Entity newEntity = Application::m_coordinator->duplicateEntity(entity); + const components::UuidComponent uuidComponent; + Application::m_coordinator->getComponent(newEntity) = uuidComponent; + Application::m_coordinator->removeComponent(newEntity); app.getSceneManager().getScene(currentSceneId).addEntity(newEntity); auto action = std::make_unique(newEntity); actionGroup->addAction(std::move(action)); @@ -232,27 +232,26 @@ namespace nexo::editor { // Select all the newly created entities for (const auto newEntity : newEntities) { - const auto uuidComponent = app.m_coordinator->tryGetComponent(newEntity); + const auto uuidComponent = Application::m_coordinator->tryGetComponent(newEntity); if (uuidComponent) { - selector.addToSelection(uuidComponent->get().uuid, newEntity); + selector.addToSelection(uuidComponent->get().uuid, static_cast(newEntity)); } } } void SceneTreeWindow::hideSelectedCallback() { - auto& selector = Selector::get(); + const auto& selector = Selector::get(); const auto& selectedEntities = selector.getSelectedEntities(); if (selectedEntities.empty()) return; - auto& app = nexo::getApp(); auto& actionManager = ActionManager::get(); - auto actionGroup = actionManager.createActionGroup(); + auto actionGroup = ActionManager::createActionGroup(); for (const auto entity : selectedEntities) { - if (app.m_coordinator->entityHasComponent(entity)) { - auto& renderComponent = app.m_coordinator->getComponent(entity); + if (Application::m_coordinator->entityHasComponent(entity)) { + auto& renderComponent = Application::m_coordinator->getComponent(entity); if (renderComponent.isRendered) { auto beforeState = renderComponent.save(); renderComponent.isRendered = !renderComponent.isRendered; @@ -268,19 +267,19 @@ namespace nexo::editor { void SceneTreeWindow::showAllCallback() { - auto& selector = Selector::get(); + const auto& selector = Selector::get(); int currentSceneId = selector.getSelectedScene(); if (currentSceneId == -1) return; - auto& app = nexo::getApp(); + auto& app = getApp(); 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 (app.m_coordinator->entityHasComponent(entity)) { - auto& renderComponent = app.m_coordinator->getComponent(entity); + if (Application::m_coordinator->entityHasComponent(entity)) { + auto& renderComponent = Application::m_coordinator->getComponent(entity); if (!renderComponent.isRendered) { auto beforeState = renderComponent.save(); renderComponent.isRendered = true; diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp index bac0ea2be..7c589957b 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -25,7 +25,7 @@ namespace nexo::editor { void SceneTreeWindow::showSceneSelectionContextMenu(scene::SceneId sceneId, const std::string &uuid, const std::string &uiName) { - if (uuid != "" && uiName != "" &&ImGui::MenuItem("Delete Scene")) { + if (!uuid.empty() && !uiName.empty() &&ImGui::MenuItem("Delete Scene")) { auto &app = Application::getInstance(); auto &selector = Selector::get(); selector.clearSelection(); @@ -87,7 +87,7 @@ namespace nexo::editor { const auto &editorScenes = m_windowRegistry.getWindows(); for (const auto &scene : editorScenes) { if (scene->getSceneId() == sceneId) { - ImNexo::CameraInspector(sceneId, scene->getContentSize()); + ImNexo::CameraInspector(sceneId); break; } } @@ -154,7 +154,7 @@ namespace nexo::editor { // Check if this object is selected auto const &selector = Selector::get(); - bool isSelected = selector.isEntitySelected(object.data.entity); + const bool isSelected = selector.isEntitySelected(static_cast(object.data.entity)); if (isSelected) baseFlags |= ImGuiTreeNodeFlags_Selected; @@ -222,7 +222,7 @@ namespace nexo::editor { m_focused = ImGui::IsWindowFocused(); m_hovered = ImGui::IsWindowHovered(); - auto &selector = Selector::get(); + const auto &selector = Selector::get(); // Opens the right click popup when no items are hovered if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered( diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp index 6700d5e39..7bd22e009 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp @@ -42,7 +42,7 @@ namespace nexo::editor { generateNodes( sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newAmbientLightNode(sceneId, uiId, entity); + return newAmbientLightNode(sceneId, uiId, entity); }); generateNodes( sceneNodes, @@ -63,7 +63,7 @@ namespace nexo::editor { generateNodes>( sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newCameraNode(sceneId, uiId, entity); + return newCameraNode(sceneId, uiId, entity); }); generateNodes< @@ -75,10 +75,10 @@ namespace nexo::editor { ecs::Exclude>( sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newEntityNode(sceneId, uiId, entity); + return newEntityNode(sceneId, uiId, entity); }); - for (const auto &[_, sceneNode] : sceneNodes) + for (const auto &sceneNode: sceneNodes | std::views::values) { root_.children.push_back(sceneNode); } diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index d88d602db..dd9fdce25 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -13,7 +13,6 @@ ////////////////////////////////////////////////////////////////////////////// #include "ADocumentWindow.hpp" -#define IMGUI_DEFINE_MATH_OPERATORS #include "utils/Config.hpp" #include "Nexo.hpp" @@ -25,13 +24,12 @@ #include "ImNexo/Elements.hpp" #include "context/ActionManager.hpp" +#define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" #include #include #include -ImGuiID g_materialInspectorDockID = 0; - namespace nexo::editor { void Editor::shutdown() const @@ -82,8 +80,8 @@ namespace nexo::editor { float scaleFactorX = 0.0f; float scaleFactorY = 0.0f; - nexo::getApp().getWindow()->getDpiScale(&scaleFactorX, &scaleFactorY); - nexo::getApp().getWindow()->setWindowIcon(Path::resolvePathRelativeToExe( + getApp().getWindow()->getDpiScale(&scaleFactorX, &scaleFactorY); + getApp().getWindow()->setWindowIcon(Path::resolvePathRelativeToExe( "../resources/nexo.png")); if (scaleFactorX > 1.0f || scaleFactorY > 1.0f) { @@ -95,8 +93,8 @@ namespace nexo::editor { LOG(NEXO_INFO, "ImGui version: {}", IMGUI_VERSION); ImGuiIO &io = ImGui::GetIO(); - io.DisplaySize = ImVec2(static_cast(nexo::getApp().getWindow()->getWidth()), - static_cast(nexo::getApp().getWindow()->getHeight())); + 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; @@ -112,19 +110,19 @@ namespace nexo::editor { // Setup NEXO Color Scheme ImVec4* colors = style->Colors; - const ImVec4 colWindowBg = ImVec4(0.02f, 0.02f, 0.04f, 0.59f); // Every color above it will depend on it because of the alpha - const ImVec4 colTitleBg = ImVec4(0.00f, 0.00f, 0.00f, 0.28f); - const ImVec4 colTitleBgActive = ImVec4(0.00f, 0.00f, 0.00f, 0.31f); - const ImVec4 colTabSelectedOverline = ImVec4(0.30f, 0.12f, 0.45f, 0.85f); - const ImVec4 colTabDimmedSelectedOverline = ImVec4(0.29f, 0.12f, 0.43f, 0.15f); + 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); + constexpr auto colTabDimmedSelectedOverline = ImVec4(0.29f, 0.12f, 0.43f, 0.15f); // Dependent colors // We want the tabs to have the same color as colWindowBg, but titleBg is under tabs, so we subtract titleBg - const ImVec4 colTab = ImVec4(0, 0, 0, (colWindowBg.w - colTitleBgActive.w) * 0.60f); - const ImVec4 colTabDimmed = ImVec4(0, 0, 0, colTab.w * 0.90f); - const ImVec4 colTabSelected = ImVec4(0, 0, 0, colWindowBg.w - colTitleBg.w); - const ImVec4 colTabDimmedSelected = ImVec4(0, 0, 0, colTabSelected.w); - const ImVec4 colTabHovered = ImVec4(0.33f, 0.25f, 0.40f, colWindowBg.w - colTitleBg.w); + constexpr auto colTab = ImVec4(0, 0, 0, (colWindowBg.w - colTitleBgActive.w) * 0.60f); + constexpr auto colTabDimmed = ImVec4(0, 0, 0, colTab.w * 0.90f); + constexpr auto colTabSelected = ImVec4(0, 0, 0, colWindowBg.w - colTitleBg.w); + constexpr auto colTabDimmedSelected = ImVec4(0, 0, 0, colTabSelected.w); + constexpr auto colTabHovered = ImVec4(0.33f, 0.25f, 0.40f, colWindowBg.w - colTitleBg.w); // Depending definitions colors[ImGuiCol_WindowBg] = colWindowBg; @@ -140,7 +138,7 @@ namespace nexo::editor { colors[ImGuiCol_TabHovered] = colTabHovered; // Static definitions - ImVec4 whiteText = colors[ImGuiCol_Text]; + 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); @@ -225,7 +223,7 @@ namespace nexo::editor { bool Editor::isOpen() const { - return !m_quit && nexo::getApp().isWindowOpen() && nexo::getApp().isRunning(); + return !m_quit && getApp().isWindowOpen() && getApp().isRunning(); } void Editor::drawMenuBar() @@ -296,7 +294,7 @@ namespace nexo::editor { ImGui::DockBuilderDockWindow(NEXO_WND_USTRID_MATERIAL_INSPECTOR, materialInspectorNode); ImGui::DockBuilderDockWindow(NEXO_WND_USTRID_ASSET_MANAGER, consoleNode); - m_windowRegistry.setDockId(defaultSceneUniqueStrId.c_str(), mainSceneTop); + m_windowRegistry.setDockId(defaultSceneUniqueStrId, mainSceneTop); m_windowRegistry.setDockId(NEXO_WND_USTRID_CONSOLE, consoleNode); m_windowRegistry.setDockId(NEXO_WND_USTRID_SCENE_TREE, sceneTreeNode); m_windowRegistry.setDockId(NEXO_WND_USTRID_INSPECTOR, inspectorNode); @@ -304,8 +302,6 @@ namespace nexo::editor { m_windowRegistry.setDockId(NEXO_WND_USTRID_ASSET_MANAGER, consoleNode); dockingRegistryFilled = true; - g_materialInspectorDockID = materialInspectorNode; - // Finish building the dock layout. ImGui::DockBuilderFinish(dockspaceID); } @@ -338,18 +334,18 @@ namespace nexo::editor { 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 - const float commandPersistTime = 2.0f; // Show last commands for 2 seconds auto focusedWindow = m_windowRegistry.getFocusedWindow(); if (focusedWindow) { - WindowState currentState = m_windowRegistry.getFocusedWindow()->getWindowState(); + const WindowState currentState = m_windowRegistry.getFocusedWindow()->getWindowState(); m_inputManager.processInputs(currentState); possibleCommands = m_inputManager.getPossibleCommands(currentState); // Update the last valid commands if we have any if (!possibleCommands.empty()) { + constexpr float commandPersistTime = 2.0f; lastValidCommands = possibleCommands; commandDisplayTimer = commandPersistTime; // Reset timer } @@ -377,10 +373,10 @@ namespace nexo::editor { void Editor::drawShortcutBar(const std::vector &possibleCommands) { - const float bottomBarHeight = 38.0f; + constexpr float bottomBarHeight = 38.0f; ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.05f, 0.06f, 0.12f, 0.85f)); // Matches your dark blue theme ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.0f)); - auto viewport = ImGui::GetMainViewport(); + const auto viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - bottomBarHeight)); ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, bottomBarHeight)); @@ -399,12 +395,12 @@ namespace nexo::editor { if (ImGui::Begin(NEXO_WND_USTRID_BOTTOM_BAR, nullptr, bottomBarFlags)) { - const float textScaleFactor = 0.90f; // 15% larger text + constexpr float textScaleFactor = 0.90f; // 15% larger text ImGui::SetWindowFontScale(textScaleFactor); // Vertically center the content - float windowHeight = ImGui::GetWindowHeight(); - float textHeight = ImGui::GetTextLineHeight(); - float paddingY = (windowHeight - textHeight) * 0.5f; + const float windowHeight = ImGui::GetWindowHeight(); + const float textHeight = ImGui::GetTextLineHeight(); + const float paddingY = (windowHeight - textHeight) * 0.5f; // Apply the vertical padding ImGui::SetCursorPosY(paddingY); @@ -425,19 +421,19 @@ namespace nexo::editor { ImVec2 descSize = ImGui::CalcTextSize(cmd.description.c_str()); // Position of the start of this command - ImVec2 commandStart = ImGui::GetCursorScreenPos(); + const ImVec2 commandStart = ImGui::GetCursorScreenPos(); // Total size of command group with padding - float commandWidth = keySize.x + colonSize.x + 5.0f + descSize.x; - float commandHeight = std::max(keySize.y, std::max(colonSize.y, descSize.y)); + 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 - float borderPadding = 6.0f; - float borderCornerRadius = 3.0f; + constexpr float borderPadding = 6.0f; + constexpr float borderCornerRadius = 3.0f; // Draw the gradient border rectangle - ImVec2 rectMin = ImVec2(commandStart.x - borderPadding, commandStart.y - borderPadding); - ImVec2 rectMax = ImVec2(commandStart.x + commandWidth + borderPadding, + const auto rectMin = ImVec2(commandStart.x - borderPadding, commandStart.y - borderPadding); + const auto rectMax = ImVec2(commandStart.x + commandWidth + borderPadding, commandStart.y + commandHeight + borderPadding); // Draw gradient border rectangle @@ -478,7 +474,7 @@ namespace nexo::editor { void Editor::drawBackground() { - auto viewport = ImGui::GetMainViewport(); + const auto viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->Pos); ImGui::SetNextWindowSize(viewport->Size); ImGui::SetNextWindowViewport(viewport->ID); @@ -525,7 +521,7 @@ namespace nexo::editor { handleGlobalCommands(); // Get the commands to display in the bottom bar - std::vector possibleCommands = handleFocusedWindowCommands(); + const std::vector possibleCommands = handleFocusedWindowCommands(); drawShortcutBar(possibleCommands); drawBackground(); diff --git a/editor/src/Editor.hpp b/editor/src/Editor.hpp index c7c46134a..0d687d8e2 100644 --- a/editor/src/Editor.hpp +++ b/editor/src/Editor.hpp @@ -14,14 +14,9 @@ #pragma once -#include "ADocumentWindow.hpp" -#include "Exception.hpp" #include "IDocumentWindow.hpp" -#include "exceptions/Exceptions.hpp" #define L_DEBUG 1 -#include #include -#include #include "WindowRegistry.hpp" #include "inputs/InputManager.hpp" @@ -29,11 +24,6 @@ namespace nexo::editor { class Editor { - private: - // Singleton: private constructor and destructor - Editor() = default; - ~Editor() = default; - public: // Singleton: Meyers' Singleton Pattern static Editor& getInstance() @@ -120,6 +110,9 @@ namespace nexo::editor { return m_windowRegistry.getWindow(windowName); } private: + // Singleton: private constructor and destructor + Editor() = default; + ~Editor() = default; /** * @brief Initializes the core engine and configures ImGui components. diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp index 4f740a904..c1b024053 100644 --- a/editor/src/ImNexo/Components.cpp +++ b/editor/src/ImNexo/Components.cpp @@ -21,8 +21,6 @@ #include #include -#include -#include namespace ImNexo { @@ -109,7 +107,7 @@ namespace ImNexo { const ImTextureID textureId = texture ? static_cast(static_cast(texture->getId())) : 0; const std::string textureButton = std::string("##TextureButton") + label; - if (ImNexo::ImageButton(textureButton.c_str(), textureId, previewSize)) { + if (ImageButton(textureButton.c_str(), textureId, previewSize)) { const char* filePath = tinyfd_openFileDialog( "Open Texture", "", @@ -136,7 +134,7 @@ namespace ImNexo { const std::string& icon, const ImVec2& size, const std::vector& gradientStops, - float gradientAngle, + const float gradientAngle, const ImU32 borderColor, const ImU32 borderColorHovered, const ImU32 borderColorActive, @@ -146,7 +144,7 @@ namespace ImNexo { IdGuard idGuard(uniqueId); // Create invisible button for interaction - bool clicked = ImGui::InvisibleButton(("##" + uniqueId).c_str(), size); + const bool clicked = ImGui::InvisibleButton(("##" + uniqueId).c_str(), size); // Get button rectangle coordinates auto [p_min, p_max] = utils::getItemRect(); @@ -224,7 +222,7 @@ namespace ImNexo { } // Add a "None" option if we want to allow null selection - std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; + const std::string currentItemName = currentIndex >= 0 ? entityNamePairs[currentIndex].second : "None"; // Draw the combo box ImGui::SetNextItemWidth(-FLT_MIN); // Use all available width @@ -279,7 +277,7 @@ namespace ImNexo { // Draw the drag float control const auto &slider = channels.sliders[i]; - bool changed = DragFloat( + const bool changed = DragFloat( slider.label, slider.value, slider.speed, @@ -319,8 +317,8 @@ namespace ImNexo { RowLabel(chanLabel); // Create channels structure for a single value - std::string labelId = "##X" + std::string(uniqueLabel); - std::string badgeId = badgeLabel.empty() ? "" : badgeLabel + "##" + uniqueLabel; + const std::string labelId = "##X" + std::string(uniqueLabel); + const std::string badgeId = badgeLabel.empty() ? "" : badgeLabel + "##" + uniqueLabel; // Setup single badge and control Channels channels; @@ -376,7 +374,7 @@ namespace ImNexo { } // Base ID for controls - std::string baseId = uniqueLabel; + const std::string baseId = uniqueLabel; // Set up channels structure Channels channels; @@ -490,7 +488,7 @@ namespace ImNexo { // Get button bounds and draw the arrow auto [p_min, p_max] = utils::getItemRect(); - ImVec2 center((p_min.x + p_max.x) * 0.5f, (p_min.y + p_max.y) * 0.5f); + 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); diff --git a/editor/src/ImNexo/Components.hpp b/editor/src/ImNexo/Components.hpp index 19755e277..7a1037d04 100644 --- a/editor/src/ImNexo/Components.hpp +++ b/editor/src/ImNexo/Components.hpp @@ -89,10 +89,10 @@ namespace ImNexo { {1.0f, IM_COL32(30, 30, 40, 255)} }, float gradientAngle = 45.0f, - const ImU32 borderColor = IM_COL32(100, 100, 120, 255), - const ImU32 borderColorHovered = IM_COL32(150, 150, 200, 255), - const ImU32 borderColorActive = IM_COL32(200, 200, 255, 255), - const ImU32 iconColor = IM_COL32(255, 255, 255, 255) + 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) ); /** diff --git a/editor/src/ImNexo/Elements.cpp b/editor/src/ImNexo/Elements.cpp index 9f217c2d5..a334907fb 100644 --- a/editor/src/ImNexo/Elements.cpp +++ b/editor/src/ImNexo/Elements.cpp @@ -36,10 +36,10 @@ namespace ImNexo { const std::string& icon, const ImVec2& p_min, const ImVec2& p_max, - ImU32 color, - float scale, - float verticalPosition, - float horizontalPosition, + const ImU32 color, + const float scale, + const float verticalPosition, + const float horizontalPosition, ImFont* font ) { ImDrawList* drawList = ImGui::GetWindowDrawList(); @@ -51,11 +51,11 @@ namespace ImNexo { // Calculate icon size with scale ImGui::SetWindowFontScale(scale); - ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str()); + const ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str()); ImGui::SetWindowFontScale(1.0f); // Calculate position - ImVec2 iconPos = ImVec2( + const auto iconPos = ImVec2( p_min.x + (p_max.x - p_min.x - iconSize.x) * horizontalPosition, p_min.y + (p_max.y - p_min.y) * verticalPosition - iconSize.y * 0.5f ); @@ -85,33 +85,33 @@ namespace ImNexo { const std::string& text, const ImVec2& p_min, const ImVec2& p_max, - ImU32 color, - float verticalPosition + const ImU32 color, + const float verticalPosition ) { ImDrawList* drawList = ImGui::GetWindowDrawList(); - float textHeight = ImGui::GetFontSize(); - float wrapWidth = p_max.x - p_min.x - 10.0f; // 5px padding on each side - float textY = p_min.y + (p_max.y - p_min.y) * verticalPosition; + const float textHeight = ImGui::GetFontSize(); + const float wrapWidth = p_max.x - p_min.x - 10.0f; // 5px padding on each side + const float textY = p_min.y + (p_max.y - p_min.y) * verticalPosition; // Calculate text size to determine if wrapping is needed - ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); + const ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); if (textSize.x > wrapWidth) { // Try to find a space to split the text - size_t splitPos = text.find(" "); + size_t splitPos = text.find(' '); if (splitPos != std::string::npos) { // Split text into two lines - std::string line1 = text.substr(0, splitPos); - std::string line2 = text.substr(splitPos + 1); + const std::string line1 = text.substr(0, splitPos); + const std::string line2 = text.substr(splitPos + 1); // Calculate positions for both lines - ImVec2 line1Pos = ImVec2( + const auto line1Pos = ImVec2( p_min.x + (p_max.x - p_min.x - ImGui::CalcTextSize(line1.c_str()).x) * 0.5f, textY - textHeight * 0.5f ); - ImVec2 line2Pos = ImVec2( + const auto line2Pos = ImVec2( p_min.x + (p_max.x - p_min.x - ImGui::CalcTextSize(line2.c_str()).x) * 0.5f, textY + textHeight * 0.5f ); @@ -121,7 +121,7 @@ namespace ImNexo { drawList->AddText(line2Pos, color, line2.c_str()); } else { // No space to split, draw single line (might be clipped) - ImVec2 textPos = ImVec2( + const auto textPos = ImVec2( p_min.x + (p_max.x - p_min.x - textSize.x) * 0.5f, textY - textHeight * 0.5f ); @@ -129,7 +129,7 @@ namespace ImNexo { } } else { // No wrapping needed, draw centered - ImVec2 textPos = ImVec2( + const auto textPos = ImVec2( p_min.x + (p_max.x - p_min.x - textSize.x) * 0.5f, textY - textHeight * 0.5f ); @@ -201,10 +201,10 @@ namespace ImNexo { return; // Sort stops by position if needed - std::sort(stops.begin(), stops.end(), - [](const GradientStop& a, const GradientStop& b) { - return a.pos < b.pos; - }); + std::ranges::sort(stops, + [](const GradientStop& a, const GradientStop& b) { + return a.pos < b.pos; + }); // Clamp positions to valid range float stop_max = 0.0f; @@ -306,7 +306,7 @@ namespace ImNexo { } } - bool Header(const std::string &label, std::string_view headerText) + bool Header(const std::string &label, const std::string_view headerText) { StyleVarGuard styleGuard(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, 3.0f)); @@ -321,7 +321,7 @@ namespace ImNexo { // Get the bounding box and draw centered text auto [p_min, p_max] = utils::getItemRect(); - ImVec2 textPos = utils::calculateCenteredTextPosition(headerText.data(), p_min, p_max); + const ImVec2 textPos = utils::calculateCenteredTextPosition(headerText.data(), p_min, p_max); ImGui::GetWindowDrawList()->AddText( ImGui::GetFont(), @@ -342,7 +342,7 @@ namespace ImNexo { } // Helper method to draw arrow indicators - void Arrow(const ImVec2& center, bool isExpanded, ImU32 color, float size) + void Arrow(const ImVec2& center, const bool isExpanded, const ImU32 color, const float size) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -368,8 +368,8 @@ namespace ImNexo { const float textPadding, const float leftSpacing, const float thickness, - ImU32 lineColor, - ImU32 textColor + const ImU32 lineColor, + const ImU32 textColor ) { const ImVec2 pos = ImGui::GetCursorScreenPos(); diff --git a/editor/src/ImNexo/EntityProperties.cpp b/editor/src/ImNexo/EntityProperties.cpp index 9eee42f5f..cfc058943 100644 --- a/editor/src/ImNexo/EntityProperties.cpp +++ b/editor/src/ImNexo/EntityProperties.cpp @@ -15,7 +15,6 @@ #include "ImNexo/ImNexo.hpp" #include "Widgets.hpp" #include "Guard.hpp" -#include "Utils.hpp" #include "EntityProperties.hpp" #include "IconsFontAwesome.h" #include "Nexo.hpp" @@ -38,7 +37,7 @@ namespace ImNexo { ImGui::Text("Color"); ImGui::SameLine(); glm::vec4 color = {ambientComponent.color, 1.0f}; - ImNexo::ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker); + ColorEditor("##ColorEditor Ambient light", &color, &colorPickerMode, &showColorPicker); ambientComponent.color = color; } @@ -50,7 +49,7 @@ namespace ImNexo { ImGui::Text("Color"); ImGui::SameLine(); glm::vec4 color = {directionalComponent.color, 1.0f}; - ImNexo::ColorEditor("##ColorEditor Directional light", &color, &colorPickerMode, &showColorPicker); + ColorEditor("##ColorEditor Directional light", &color, &colorPickerMode, &showColorPicker); directionalComponent.color = color; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); @@ -62,7 +61,7 @@ namespace ImNexo { ImGui::TableSetupColumn("##Y", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##Z", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - ImNexo::RowDragFloat3("Direction", "X", "Y", "Z", &directionalComponent.direction.x); + RowDragFloat3("Direction", "X", "Y", "Z", &directionalComponent.direction.x); ImGui::EndTable(); } @@ -79,7 +78,7 @@ namespace ImNexo { ImGui::Text("Color"); ImGui::SameLine(); glm::vec4 color = {pointComponent.color, 1.0f}; - ImNexo::ColorEditor("##ColorEditor Point light", &color, &colorPickerMode, &showColorPicker); + ColorEditor("##ColorEditor Point light", &color, &colorPickerMode, &showColorPicker); pointComponent.color = color; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(5.0f, 10.0f)); @@ -195,7 +194,6 @@ namespace ImNexo { const glm::quat deltaQuat = glm::radians(deltaEuler); quat = glm::normalize(deltaQuat * quat); lastDisplayedEuler = nexo::math::customQuatToEuler(quat); - rotation = lastDisplayedEuler; } RowDragFloat3("Scale", "X", "Y", "Z", &size.x); @@ -265,12 +263,11 @@ namespace ImNexo { if (ImGui::BeginTable("InspectorControllerTable", 2, ImGuiTableFlags_SizingStretchProp)) { - auto &app = nexo::getApp(); auto &selector = nexo::editor::Selector::get(); ImGui::TableSetupColumn("##Label", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); ImGui::TableSetupColumn("##X", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_NoHeaderLabel); - const std::vector &entities = app.m_coordinator->getAllEntitiesWith< + const std::vector &entities = nexo::Application::m_coordinator->getAllEntitiesWith< nexo::components::TransformComponent, nexo::ecs::Exclude, nexo::ecs::Exclude, @@ -283,9 +280,9 @@ namespace ImNexo { RowEntityDropdown( "Target Entity", cameraTargetComponent.targetEntity, entities, - [&app, &selector](nexo::ecs::Entity e) { + [&selector](const nexo::ecs::Entity e) { return selector.getUiHandle( - app.m_coordinator->getComponent(e).uuid, + nexo::Application::m_coordinator->getComponent(e).uuid, std::to_string(e) ); } diff --git a/editor/src/ImNexo/EntityProperties.hpp b/editor/src/ImNexo/EntityProperties.hpp index da4401373..3296b65d2 100644 --- a/editor/src/ImNexo/EntityProperties.hpp +++ b/editor/src/ImNexo/EntityProperties.hpp @@ -13,14 +13,9 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include -#include -#include - #include "components/Light.hpp" #include "components/Transform.hpp" #include "components/Camera.hpp" -#include "ImNexo.hpp" namespace ImNexo { diff --git a/editor/src/ImNexo/Guard.hpp b/editor/src/ImNexo/Guard.hpp index cbd7aaee2..2ee2e35c2 100644 --- a/editor/src/ImNexo/Guard.hpp +++ b/editor/src/ImNexo/Guard.hpp @@ -27,45 +27,48 @@ namespace ImNexo { * 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(ImGuiCol_ col, ImU32 color) { - if (color != 0) { - m_colorIndices.push_back(col); - ImGui::PushStyleColor(col, color); + 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 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(ImGuiCol_ col, ImU32 color) { - if (color != 0) { - m_colorIndices.push_back(col); - ImGui::PushStyleColor(col, color); + /** + * @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; } - return *this; - } - /** - * @brief Destructor that automatically pops all pushed style colors. - */ - ~StyleGuard() { - if (!m_colorIndices.empty()) - ImGui::PopStyleColor(static_cast(m_colorIndices.size())); - } + /** + * @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 + private: + std::vector m_colorIndices; ///< Tracks which color indices were pushed }; /** @@ -76,65 +79,70 @@ namespace ImNexo { * 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(ImGuiStyleVar_ var, ImVec2 value) { - m_varCount++; - ImGui::PushStyleVar(var, value); - } + 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(ImGuiStyleVar_ var, float 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 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(ImGuiStyleVar_ var, ImVec2 value) { - m_varCount++; - ImGui::PushStyleVar(var, value); - 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(ImGuiStyleVar_ var, float 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); - } + /** + * @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 + private: + int m_varCount = 0; ///< Counts how many style variables were pushed }; /** @@ -145,22 +153,24 @@ namespace ImNexo { * 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()); - } + 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()); + } - /** - * @brief Destructor that automatically pops the pushed ID. - */ - ~IdGuard() { - ImGui::PopID(); - } + /** + * @brief Destructor that automatically pops the pushed ID. + */ + ~IdGuard() + { + ImGui::PopID(); + } }; /** @@ -170,23 +180,25 @@ namespace ImNexo { * 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(float scale) : m_scale(scale) { - ImGui::SetWindowFontScale(scale); - } + 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) : m_scale(scale) + { + ImGui::SetWindowFontScale(scale); + } - /** - * @brief Destructor that automatically resets the font scale to default. - */ - ~FontScaleGuard() { - ImGui::SetWindowFontScale(1.0f); - } - private: - float m_scale; ///< Stores the applied scale factor + /** + * @brief Destructor that automatically resets the font scale to default. + */ + ~FontScaleGuard() + { + ImGui::SetWindowFontScale(1.0f); + } + private: + float m_scale; ///< Stores the applied scale factor }; } diff --git a/editor/src/ImNexo/ImNexo.hpp b/editor/src/ImNexo/ImNexo.hpp index f927b2976..a7df78188 100644 --- a/editor/src/ImNexo/ImNexo.hpp +++ b/editor/src/ImNexo/ImNexo.hpp @@ -13,7 +13,6 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include namespace ImNexo { inline bool g_isItemActive = false; diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index d1f2abce4..d83685964 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -26,7 +26,6 @@ #include "components/Transform.hpp" #include "components/Uuid.hpp" #include "context/Selector.hpp" -#include "context/actions/Action.hpp" #include "context/actions/EntityActions.hpp" #include "utils/EditorProps.hpp" #include "context/ActionManager.hpp" @@ -59,7 +58,7 @@ namespace ImNexo { ImGui::SameLine(); static int currentRenderingModeIndex = 0; const char* renderingModeOptions[] = { "Opaque", "Transparent", "Refraction" }; - float availableWidth = ImGui::GetContentRegionAvail().x; + const float availableWidth = ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(availableWidth); if (ImGui::Combo("##RenderingModeCombo", ¤tRenderingModeIndex, renderingModeOptions, IM_ARRAYSIZE(renderingModeOptions))) @@ -73,7 +72,7 @@ namespace ImNexo { static ImGuiColorEditFlags colorPickerModeAlbedo = ImGuiColorEditFlags_PickerHueBar; static bool showColorPickerAlbedo = false; const auto asset = material->albedoTexture.lock(); - auto albedoTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; + const auto albedoTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; std::filesystem::path newTexturePath; if (TextureButton("Albedo texture", albedoTexture, newTexturePath) @@ -95,7 +94,7 @@ namespace ImNexo { static ImGuiColorEditFlags colorPickerModeSpecular = ImGuiColorEditFlags_PickerHueBar; static bool showColorPickerSpecular = false; const auto asset = material->metallicMap.lock(); - auto metallicTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; + const auto metallicTexture = asset && asset->isLoaded() ? asset->getData()->texture : nullptr; std::filesystem::path newTexturePath; if (TextureButton("Specular texture", metallicTexture, newTexturePath) @@ -124,35 +123,30 @@ namespace ImNexo { * @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, ImVec2 sceneViewportSize) + 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 }; - const ImVec2 availSize = ImGui::GetContentRegionAvail(); - const float totalWidth = availSize.x; - 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 panel framebufferSpecs.width = static_cast(sceneViewportSize.x); framebufferSpecs.height = static_cast(sceneViewportSize.y); const auto renderTarget = nexo::renderer::NxFramebuffer::create(framebufferSpecs); - 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(static_cast(defaultCamera)); + 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, ImVec2 sceneViewportSize) + bool CameraInspector(const nexo::scene::SceneId sceneId) { auto &app = nexo::getApp(); static int undoStackSize = -1; if (undoStackSize == -1) - undoStackSize = nexo::editor::ActionManager::get().getUndoStackSize(); + undoStackSize = static_cast(nexo::editor::ActionManager::get().getUndoStackSize()); const ImVec2 availSize = ImGui::GetContentRegionAvail(); const float totalWidth = availSize.x; @@ -170,13 +164,11 @@ namespace ImNexo { static char cameraName[128] = ""; static bool nameIsEmpty = false; static bool closingPopup = false; - static bool result = 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 if (closingPopup) { // Now it's safe to delete the entity - auto &app = nexo::getApp(); app.deleteEntity(camera); camera = nexo::ecs::MAX_ENTITIES; @@ -218,7 +210,7 @@ namespace ImNexo { if (Header("##CameraNode", "Camera")) { - auto &cameraComponent = app.m_coordinator->getComponent(camera); + auto &cameraComponent = nexo::Application::m_coordinator->getComponent(camera); cameraComponent.render = true; static nexo::components::CameraComponent::Memento beforeState; auto cameraComponentCopy = cameraComponent; @@ -241,7 +233,7 @@ namespace ImNexo { if (Header("##TransformNode", "Transform Component")) { static glm::vec3 lastDisplayedEuler(0.0f); - auto &transformComponent = app.m_coordinator->getComponent(camera); + auto &transformComponent = nexo::Application::m_coordinator->getComponent(camera); static nexo::components::TransformComponent::Memento beforeState; resetItemStates(); auto transformComponentCopy = transformComponent; @@ -256,11 +248,11 @@ namespace ImNexo { ImGui::TreePop(); } - if (app.m_coordinator->entityHasComponent(camera) && + if (nexo::Application::m_coordinator->entityHasComponent(camera) && Header("##PerspectiveCameraTarget", "Camera Target Component")) { - auto &cameraTargetComponent = app.m_coordinator->getComponent(camera); - nexo::components::PerspectiveCameraTarget::Memento beforeState; + auto &cameraTargetComponent = nexo::Application::m_coordinator->getComponent(camera); + nexo::components::PerspectiveCameraTarget::Memento beforeState{}; resetItemStates(); auto cameraTargetComponentCopy = cameraTargetComponent; CameraTarget(cameraTargetComponent); @@ -274,11 +266,11 @@ namespace ImNexo { ImGui::TreePop(); } - if (app.m_coordinator->entityHasComponent(camera) && + if (nexo::Application::m_coordinator->entityHasComponent(camera) && Header("##PerspectiveCameraController", "Camera Controller Component")) { - auto &cameraControllerComponent = app.m_coordinator->getComponent(camera); - nexo::components::PerspectiveCameraController::Memento beforeState; + auto &cameraControllerComponent = nexo::Application::m_coordinator->getComponent(camera); + nexo::components::PerspectiveCameraController::Memento beforeState{}; auto cameraControllerComponentCopy = cameraControllerComponent; resetItemStates(); CameraController(cameraControllerComponent); @@ -305,12 +297,12 @@ namespace ImNexo { // Static variables for state tracking static bool showComponentSelector = false; static float animProgress = 0.0f; - static float lastClickTime = 0.0f; + static double lastClickTime = 0.0f; // Button with arrow indicating state std::string buttonText = "Add Component " + std::string(showComponentSelector ? ICON_FA_CHEVRON_UP : ICON_FA_CHEVRON_DOWN); - if (Button(buttonText.c_str(), ImVec2(buttonWidth, 0))) + if (Button(buttonText, ImVec2(buttonWidth, 0))) { showComponentSelector = !showComponentSelector; if (showComponentSelector) { @@ -324,12 +316,12 @@ namespace ImNexo { if (showComponentSelector) { // Animation calculation - const float animDuration = 0.25f; - float timeSinceClick = ImGui::GetTime() - lastClickTime; + constexpr float animDuration = 0.25f; + auto timeSinceClick = static_cast(ImGui::GetTime() - lastClickTime); animProgress = std::min(timeSinceClick / animDuration, 1.0f); // Simplified component grid with compact layout - const float maxGridHeight = 90.0f; + constexpr float maxGridHeight = 90.0f; const float currentHeight = maxGridHeight * animProgress; // Create child window for components with animated height @@ -341,31 +333,29 @@ namespace ImNexo { if (animProgress > 0.5f) { - // Center elements horizontally with proper spacing - const float itemSize = 75.0f; // Draw component buttons side-by-side with controlled spacing ImGui::BeginGroup(); - if (!app.m_coordinator->entityHasComponent(camera) && - !app.m_coordinator->entityHasComponent(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>(camera); nexo::editor::ActionManager::get().recordAction(std::move(action)); nexo::components::PerspectiveCameraTarget cameraTarget{}; - app.m_coordinator->addComponent(camera, cameraTarget); + nexo::Application::m_coordinator->addComponent(camera, cameraTarget); showComponentSelector = false; } ImGui::SameLine(); - if (!app.m_coordinator->entityHasComponent(camera) && - !app.m_coordinator->entityHasComponent(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>(camera); nexo::editor::ActionManager::get().recordAction(std::move(action)); nexo::components::PerspectiveCameraController cameraController{}; - app.m_coordinator->addComponent(camera, cameraController); + nexo::Application::m_coordinator->addComponent(camera, cameraController); showComponentSelector = false; } ImGui::EndGroup(); @@ -387,8 +377,7 @@ namespace ImNexo { { ImGui::BeginChild("CameraPreview", ImVec2(previewWidth - 4, totalHeight), true); - auto &app = nexo::getApp(); - nexo::Application::SceneInfo sceneInfo{static_cast(sceneId), nexo::RenderingType::FRAMEBUFFER}; + nexo::Application::SceneInfo sceneInfo{sceneId, nexo::RenderingType::FRAMEBUFFER}; app.run(sceneInfo); auto const &cameraComponent = nexo::Application::m_coordinator->getComponent(camera); const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0); @@ -397,7 +386,7 @@ namespace ImNexo { const float displayWidth = displayHeight; ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + 4, ImGui::GetCursorPosY() + 4)); - ImNexo::Image(static_cast(static_cast(textureId)), + Image(static_cast(static_cast(textureId)), ImVec2(displayWidth, displayHeight)); ImGui::EndChild(); @@ -417,8 +406,8 @@ namespace ImNexo { } nameIsEmpty = false; auto &selector = nexo::editor::Selector::get(); - auto &uuid = app.m_coordinator->getComponent(camera); - auto &cameraComponent = app.m_coordinator->getComponent(camera); + auto &uuid = 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; diff --git a/editor/src/ImNexo/Panels.hpp b/editor/src/ImNexo/Panels.hpp index f01a81a41..8df0115d9 100644 --- a/editor/src/ImNexo/Panels.hpp +++ b/editor/src/ImNexo/Panels.hpp @@ -50,5 +50,5 @@ namespace ImNexo { * @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(const nexo::scene::SceneId sceneId, ImVec2 sceneViewportSize); + bool CameraInspector(nexo::scene::SceneId sceneId); } diff --git a/editor/src/ImNexo/Utils.cpp b/editor/src/ImNexo/Utils.cpp index 6cf36e499..1ddb207cb 100644 --- a/editor/src/ImNexo/Utils.cpp +++ b/editor/src/ImNexo/Utils.cpp @@ -27,7 +27,7 @@ namespace ImNexo::utils { return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); } - void clipPolygonWithLine(const std::vector& poly, const ImVec2& normal, 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(); @@ -89,10 +89,10 @@ namespace ImNexo::utils { */ ImVec2 calculateCenteredTextPosition(const std::string& text, const ImVec2& p_min, const ImVec2& p_max) { - ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); - return ImVec2( + 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 - ); + }; } } diff --git a/editor/src/ImNexo/Utils.hpp b/editor/src/ImNexo/Utils.hpp index d927ac052..75e3e26cd 100644 --- a/editor/src/ImNexo/Utils.hpp +++ b/editor/src/ImNexo/Utils.hpp @@ -27,7 +27,7 @@ namespace ImNexo::utils { * @param[in] t The interpolation factor (0.0 to 1.0). * @return The interpolated color (ARGB format). */ - ImU32 imLerpColor(const ImU32 colA, const ImU32 colB, const float t); + ImU32 imLerpColor(ImU32 colA, ImU32 colB, float t); /** * @brief Clip a convex polygon against a half-plane defined by: (dot(normal, v) >= offset) diff --git a/editor/src/ImNexo/Widgets.cpp b/editor/src/ImNexo/Widgets.cpp index 5b4ed7e9d..86d71fbae 100644 --- a/editor/src/ImNexo/Widgets.cpp +++ b/editor/src/ImNexo/Widgets.cpp @@ -15,20 +15,12 @@ #include "Widgets.hpp" #include -#include -#include -#include "Path.hpp" -#include "Definitions.hpp" #include "IconsFontAwesome.h" #include "Nexo.hpp" #include "Texture.hpp" #include "components/Camera.hpp" -#include "CameraFactory.hpp" -#include "components/Render3D.hpp" -#include "components/Transform.hpp" #include "components/Uuid.hpp" -#include "context/Selector.hpp" #include "ImNexo.hpp" namespace ImNexo { @@ -99,12 +91,11 @@ namespace ImNexo { { constexpr float buttonSpacing = 5.0f; constexpr float padding = 10.0f; - const auto &app = nexo::getApp(); // Calculate menu dimensions const float menuWidth = buttonSize.x + padding; // Add padding - const float menuHeight = buttonProps.size() * buttonSize.y + - (buttonProps.size() - 1) * buttonSpacing + 2 * buttonSpacing; + const float menuHeight =static_cast(buttonProps.size()) * buttonSize.y + + (static_cast(buttonProps.size()) - 1.0f) * buttonSpacing + 2 * buttonSpacing; // Calculate menu position based on orientation ImVec2 menuPos; diff --git a/editor/src/ImNexo/Widgets.hpp b/editor/src/ImNexo/Widgets.hpp index 2021b545a..a7afac28e 100644 --- a/editor/src/ImNexo/Widgets.hpp +++ b/editor/src/ImNexo/Widgets.hpp @@ -18,11 +18,8 @@ #include #include "Components.hpp" -#include "components/Camera.hpp" #include "components/Render3D.hpp" -#include "components/Transform.hpp" #include "renderer/Texture.hpp" -#include "core/scene/SceneManager.hpp" namespace ImNexo { @@ -59,7 +56,7 @@ namespace ImNexo { 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 @@ -95,9 +92,9 @@ namespace ImNexo { */ void ButtonDropDown( const ImVec2& buttonPos, - const ImVec2 buttonSize, + ImVec2 buttonSize, const std::vector &buttonProps, bool &closure, - const DropdownOrientation orientation = DropdownOrientation::DOWN + DropdownOrientation orientation = DropdownOrientation::DOWN ); } diff --git a/editor/src/WindowRegistry.hpp b/editor/src/WindowRegistry.hpp index 84a7b39aa..4441f5a9f 100644 --- a/editor/src/WindowRegistry.hpp +++ b/editor/src/WindowRegistry.hpp @@ -38,7 +38,8 @@ namespace nexo::editor { * @return std::shared_ptr The same pointer cast to the derived type */ template - std::shared_ptr castWindow(const std::shared_ptr& ptr) { + std::shared_ptr castWindow(const std::shared_ptr& ptr) + { return std::static_pointer_cast(ptr); } @@ -53,7 +54,8 @@ namespace nexo::editor { * @return std::shared_ptr The same pointer cast to the derived type */ template - std::shared_ptr castWindow(std::shared_ptr& ptr) { + std::shared_ptr castWindow(std::shared_ptr& ptr) + { return std::static_pointer_cast(ptr); } diff --git a/editor/src/context/ActionGroup.cpp b/editor/src/context/ActionGroup.cpp index 52aab5870..31dfa00fa 100644 --- a/editor/src/context/ActionGroup.cpp +++ b/editor/src/context/ActionGroup.cpp @@ -14,6 +14,8 @@ #include "ActionGroup.hpp" +#include + namespace nexo::editor { void ActionGroup::addAction(std::unique_ptr action) @@ -28,13 +30,13 @@ namespace nexo::editor { void ActionGroup::redo() { - for (auto& action : actions) + for (const auto &action : actions) action->redo(); } void ActionGroup::undo() { - for (auto it = actions.rbegin(); it != actions.rend(); ++it) - (*it)->undo(); + for (const auto &action : std::ranges::reverse_view(actions)) + action->undo(); } } diff --git a/editor/src/context/ActionGroup.hpp b/editor/src/context/ActionGroup.hpp index 09de9c9bd..a09e93243 100644 --- a/editor/src/context/ActionGroup.hpp +++ b/editor/src/context/ActionGroup.hpp @@ -22,12 +22,12 @@ namespace nexo::editor { /** * Groups multiple actions into a single undoable action */ - class ActionGroup : public Action { + class ActionGroup final : public Action { public: ActionGroup() = default; void addAction(std::unique_ptr action); - bool hasActions() const; + [[nodiscard]] bool hasActions() const; void redo() override; void undo() override; diff --git a/editor/src/context/ActionHistory.cpp b/editor/src/context/ActionHistory.cpp index 553b70626..61a1b3728 100644 --- a/editor/src/context/ActionHistory.cpp +++ b/editor/src/context/ActionHistory.cpp @@ -68,13 +68,13 @@ namespace nexo::editor { redoStack.clear(); return; } - unsigned int elementsToRemove = std::min(static_cast(undoStack.size()), count); + const unsigned int elementsToRemove = std::min(static_cast(undoStack.size()), count); for (unsigned int i = 0; i < elementsToRemove; ++i) undoStack.pop_back(); } unsigned int ActionHistory::getUndoStackSize() const { - return undoStack.size(); + return static_cast(undoStack.size()); } } diff --git a/editor/src/context/ActionHistory.hpp b/editor/src/context/ActionHistory.hpp index cbb326b0d..3cfcbc65b 100644 --- a/editor/src/context/ActionHistory.hpp +++ b/editor/src/context/ActionHistory.hpp @@ -16,7 +16,6 @@ #include "actions/Action.hpp" #include #include -#include namespace nexo::editor { @@ -28,15 +27,15 @@ namespace nexo::editor { // Add a action to history after it was already executed void addAction(std::unique_ptr action); - bool canUndo() const; - bool canRedo() const; + [[nodiscard]] bool canUndo() const; + [[nodiscard]] bool canRedo() const; void undo(); void redo(); void setMaxUndoLevels(size_t levels); void clear(unsigned int count = 0); - unsigned int getUndoStackSize() const; + [[nodiscard]] unsigned int getUndoStackSize() const; private: std::deque> undoStack; diff --git a/editor/src/context/ActionManager.cpp b/editor/src/context/ActionManager.cpp index 66c446031..54416b6b8 100644 --- a/editor/src/context/ActionManager.cpp +++ b/editor/src/context/ActionManager.cpp @@ -56,9 +56,9 @@ namespace nexo::editor { return history.canRedo(); } - void ActionManager::clearHistory(unsigned int count) + void ActionManager::clearHistory(const unsigned int count) { - history.clear(); + history.clear(count); } unsigned int ActionManager::getUndoStackSize() const diff --git a/editor/src/context/ActionManager.hpp b/editor/src/context/ActionManager.hpp index a4ce0c770..da8610151 100644 --- a/editor/src/context/ActionManager.hpp +++ b/editor/src/context/ActionManager.hpp @@ -27,7 +27,7 @@ namespace nexo::editor { // Record entity creation void recordEntityCreation(ecs::Entity entityId); // Record entity deletion (call before actually deleting) - std::unique_ptr prepareEntityDeletion(ecs::Entity entityId); + static std::unique_ptr prepareEntityDeletion(ecs::Entity entityId); // For component changes using memento pattern template @@ -35,8 +35,7 @@ namespace nexo::editor { const typename MementoComponent::Memento& beforeState, const typename MementoComponent::Memento& afterState) { - auto& app = nexo::getApp(); - auto& component = app.m_coordinator->getComponent(entityId); + auto& component = Application::m_coordinator->getComponent(entityId); auto action = std::make_unique>(entityId, beforeState, afterState); @@ -44,15 +43,15 @@ namespace nexo::editor { } // Action group for multiple operations - std::unique_ptr createActionGroup(); + static std::unique_ptr createActionGroup(); // Basic undo/redo operations void undo(); void redo(); - bool canUndo() const; - bool canRedo() const; + [[nodiscard]] bool canUndo() const; + [[nodiscard]] bool canRedo() const; void clearHistory(unsigned int count = 0); - unsigned int getUndoStackSize() const; + [[nodiscard]] unsigned int getUndoStackSize() const; static ActionManager& get() { static ActionManager instance; diff --git a/editor/src/context/Selector.cpp b/editor/src/context/Selector.cpp index 3e2c3624b..d35d5d018 100644 --- a/editor/src/context/Selector.cpp +++ b/editor/src/context/Selector.cpp @@ -59,15 +59,15 @@ namespace nexo::editor { return uuids; } - void Selector::selectEntity(std::string_view uuid, int entity, SelectionType type) + void Selector::selectEntity(const std::string_view uuid, const int entity, const SelectionType type) { clearSelection(); addToSelection(uuid, entity, type); } - bool Selector::addToSelection(std::string_view uuid, int entity, SelectionType type) + bool Selector::addToSelection(const std::string_view uuid, const int entity, const SelectionType type) { - if (m_selectedEntityIds.find(entity) != m_selectedEntityIds.end()) + if (m_selectedEntityIds.contains(entity)) return false; SelectionData data = { @@ -82,19 +82,18 @@ namespace nexo::editor { return true; } - bool Selector::toggleSelection(std::string_view uuid, int entity, SelectionType type) + bool Selector::toggleSelection(const std::string_view uuid, const int entity, const SelectionType type) { if (isEntitySelected(entity)) { removeFromSelection(entity); return false; - } else { - addToSelection(uuid, entity, type); - return true; } + addToSelection(uuid, entity, type); + return true; } - bool Selector::removeFromSelection(int entity) { - if (m_selectedEntityIds.find(entity) == m_selectedEntityIds.end()) + bool Selector::removeFromSelection(const int entity) { + if (!m_selectedEntityIds.contains(entity)) return false; m_selectedEntityIds.erase(entity); @@ -109,7 +108,7 @@ namespace nexo::editor { return true; } - void Selector::setSelectedScene(int scene) + void Selector::setSelectedScene(const int scene) { m_selectedScene = scene; } @@ -128,9 +127,9 @@ namespace nexo::editor { m_selectedEntityIds.clear(); } - bool Selector::isEntitySelected(int entity) const + bool Selector::isEntitySelected(const int entity) const { - return m_selectedEntityIds.find(entity) != m_selectedEntityIds.end(); + return m_selectedEntityIds.contains(entity); } bool Selector::hasSelection() const @@ -146,7 +145,7 @@ namespace nexo::editor { return m_selectedEntities.front().type; } - void Selector::setSelectionType(SelectionType type) + void Selector::setSelectionType(const SelectionType type) { m_defaultSelectionType = type; } @@ -161,18 +160,18 @@ namespace nexo::editor { return it->second; } - void Selector::setUiHandle(const std::string& uuid, std::string_view handle) + void Selector::setUiHandle(const std::string& uuid, const std::string_view handle) { m_uiHandles[uuid] = handle; } - void Selector::addSelectedTag(int entity) + void Selector::addSelectedTag(const int entity) { - components::SelectedTag selectTag{}; + constexpr components::SelectedTag selectTag{}; Application::m_coordinator->addComponent(entity, selectTag); } - void Selector::removeSelectedTag(int entity) + void Selector::removeSelectedTag(const int entity) { if (Application::m_coordinator->entityHasComponent(entity)) Application::m_coordinator->removeComponent(entity); diff --git a/editor/src/context/Selector.hpp b/editor/src/context/Selector.hpp index fce22f707..2a1b76d7a 100644 --- a/editor/src/context/Selector.hpp +++ b/editor/src/context/Selector.hpp @@ -217,7 +217,7 @@ namespace nexo::editor { std::unordered_map> m_uiHandles; - void addSelectedTag(int entity); - void removeSelectedTag(int entity); + static void addSelectedTag(int entity); + static void removeSelectedTag(int entity); }; } diff --git a/editor/src/context/actions/ComponentRestoreFactory.cpp b/editor/src/context/actions/ComponentRestoreFactory.cpp index 05f4cb426..237f0e28d 100644 --- a/editor/src/context/actions/ComponentRestoreFactory.cpp +++ b/editor/src/context/actions/ComponentRestoreFactory.cpp @@ -26,25 +26,25 @@ namespace nexo::editor { { if (typeIndex == typeid(components::TransformComponent)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::RenderComponent)) + if (typeIndex == typeid(components::RenderComponent)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::SceneTag)) + if (typeIndex == typeid(components::SceneTag)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::CameraComponent)) + if (typeIndex == typeid(components::CameraComponent)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::AmbientLightComponent)) + if (typeIndex == typeid(components::AmbientLightComponent)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::DirectionalLightComponent)) + if (typeIndex == typeid(components::DirectionalLightComponent)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::PointLightComponent)) + if (typeIndex == typeid(components::PointLightComponent)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::SpotLightComponent)) + if (typeIndex == typeid(components::SpotLightComponent)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::UuidComponent)) + if (typeIndex == typeid(components::UuidComponent)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::PerspectiveCameraController)) + if (typeIndex == typeid(components::PerspectiveCameraController)) return std::make_unique>(entity); - else if (typeIndex == typeid(components::PerspectiveCameraTarget)) + if (typeIndex == typeid(components::PerspectiveCameraTarget)) return std::make_unique>(entity); return nullptr; } diff --git a/editor/src/context/actions/ComponentRestoreFactory.hpp b/editor/src/context/actions/ComponentRestoreFactory.hpp index caf5a674b..412256163 100644 --- a/editor/src/context/actions/ComponentRestoreFactory.hpp +++ b/editor/src/context/actions/ComponentRestoreFactory.hpp @@ -21,6 +21,6 @@ namespace nexo::editor { class ComponentRestoreFactory { public: - static std::unique_ptr createRestoreComponent(ecs::Entity entity, const std::type_index typeIndex); + static std::unique_ptr createRestoreComponent(ecs::Entity entity, std::type_index typeIndex); }; } diff --git a/editor/src/context/actions/EntityActions.cpp b/editor/src/context/actions/EntityActions.cpp index a81dcbc82..0f9061f59 100644 --- a/editor/src/context/actions/EntityActions.cpp +++ b/editor/src/context/actions/EntityActions.cpp @@ -19,7 +19,7 @@ namespace nexo::editor { void EntityCreationAction::redo() { - auto& coordinator = Application::getInstance().m_coordinator; + const auto &coordinator = Application::m_coordinator; m_entityId = coordinator->createEntity(); for (const auto &action : m_componentRestoreActions) @@ -28,7 +28,7 @@ namespace nexo::editor { void EntityCreationAction::undo() { - auto &coordinator = Application::getInstance().m_coordinator; + const auto &coordinator = Application::m_coordinator; std::vector componentsTypeIndex = coordinator->getAllComponentTypes(m_entityId); for (const auto typeIndex : componentsTypeIndex) { if (!coordinator->supportsMementoPattern(typeIndex)) @@ -38,9 +38,9 @@ namespace nexo::editor { coordinator->destroyEntity(m_entityId); } - EntityDeletionAction::EntityDeletionAction(ecs::Entity entityId) : m_entityId(entityId) + EntityDeletionAction::EntityDeletionAction(const ecs::Entity entityId) : m_entityId(entityId) { - auto &coordinator = Application::getInstance().m_coordinator; + const auto &coordinator = Application::m_coordinator; std::vector componentsTypeIndex = coordinator->getAllComponentTypes(entityId); for (const auto typeIndex : componentsTypeIndex) { if (!coordinator->supportsMementoPattern(typeIndex)) @@ -52,13 +52,13 @@ namespace nexo::editor { void EntityDeletionAction::redo() { // Simply destroy the entity - auto& coordinator = Application::getInstance().m_coordinator; + const auto& coordinator = Application::m_coordinator; coordinator->destroyEntity(m_entityId); } void EntityDeletionAction::undo() { - auto& coordinator = Application::getInstance().m_coordinator; + const auto& coordinator = Application::m_coordinator; // This can cause problem is the entity is not the same, maybe in the future we would need another method m_entityId = coordinator->createEntity(); for (const auto &action : m_componentRestoreActions) diff --git a/editor/src/context/actions/EntityActions.hpp b/editor/src/context/actions/EntityActions.hpp index f1dffab98..659bba252 100644 --- a/editor/src/context/actions/EntityActions.hpp +++ b/editor/src/context/actions/EntityActions.hpp @@ -2,27 +2,24 @@ #pragma once #include "Action.hpp" -#include "core/scene/Scene.hpp" #include "Nexo.hpp" namespace nexo::editor { template - class ComponentRestoreAction : public Action { + class ComponentRestoreAction final : public Action { public: - ComponentRestoreAction(ecs::Entity entity) : m_entity(entity) + explicit ComponentRestoreAction(const ecs::Entity entity) : m_entity(entity) { - auto &app = getApp(); - ComponentType &target = app.m_coordinator->getComponent(m_entity); + ComponentType &target = Application::m_coordinator->getComponent(m_entity); m_memento = target.save(); }; void undo() override { - auto &app = getApp(); ComponentType target; target.restore(m_memento); - app.m_coordinator->addComponent(m_entity, target); + Application::m_coordinator->addComponent(m_entity, target); } void redo() override {} @@ -33,24 +30,22 @@ namespace nexo::editor { }; template - class ComponentAddAction : public Action { + class ComponentAddAction final : public Action { public: - ComponentAddAction(ecs::Entity entity) + explicit ComponentAddAction(const ecs::Entity entity) : m_entity(entity) {} void undo() override { - auto &app = getApp(); - m_memento = app.m_coordinator->getComponent(m_entity).save(); - app.m_coordinator->removeComponent(m_entity); + m_memento = Application::m_coordinator->getComponent(m_entity).save(); + Application::m_coordinator->removeComponent(m_entity); } void redo() override { - auto &app = getApp(); ComponentType target; target.restore(m_memento); - app.m_coordinator->addComponent(m_entity, target); + Application::m_coordinator->addComponent(m_entity, target); } private: @@ -59,26 +54,23 @@ namespace nexo::editor { }; template - class ComponentRemoveAction : public Action { + class ComponentRemoveAction final : public Action { public: - ComponentRemoveAction(ecs::Entity entity) : m_entity(entity) + explicit ComponentRemoveAction(const ecs::Entity entity) : m_entity(entity) { - auto &app = getApp(); - m_memento = app.m_coordinator->getComponent(m_entity).save(); + m_memento = Application::m_coordinator->getComponent(m_entity).save(); } void undo() override { - auto &app = getApp(); ComponentType target; target.restore(m_memento); - app.m_coordinator->addComponent(m_entity, target); + Application::m_coordinator->addComponent(m_entity, target); } void redo() override { - auto &app = getApp(); - app.m_coordinator->removeComponent(m_entity); + Application::m_coordinator->removeComponent(m_entity); } private: @@ -87,25 +79,23 @@ namespace nexo::editor { }; template - class ComponentChangeAction : public Action { + class ComponentChangeAction final : public Action { public: - ComponentChangeAction( - ecs::Entity entity, + 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 { - auto &app = getApp(); - ComponentType &target = app.m_coordinator->getComponent(m_entity); + ComponentType &target = Application::m_coordinator->getComponent(m_entity); target.restore(m_afterState); } void undo() override { - auto &app = getApp(); - ComponentType &target = app.m_coordinator->getComponent(m_entity); + ComponentType &target = Application::m_coordinator->getComponent(m_entity); target.restore(m_beforeState); } @@ -119,9 +109,9 @@ namespace nexo::editor { * Stores information needed to undo/redo entity creation * Relies on engine systems for actual creation/deletion logic */ - class EntityCreationAction : public Action { + class EntityCreationAction final : public Action { public: - EntityCreationAction(ecs::Entity entityId) + explicit EntityCreationAction(const ecs::Entity entityId) : m_entityId(entityId) {} void redo() override; @@ -136,9 +126,9 @@ namespace nexo::editor { * Stores information needed to undo/redo entity deletion * Relies on engine systems for actual deletion logic */ - class EntityDeletionAction : public Action { + class EntityDeletionAction final : public Action { public: - EntityDeletionAction(ecs::Entity entityId); + explicit EntityDeletionAction(ecs::Entity entityId); void redo() override; void undo() override; diff --git a/editor/src/inputs/Command.cpp b/editor/src/inputs/Command.cpp index 77b439d46..8906ec1f9 100644 --- a/editor/src/inputs/Command.cpp +++ b/editor/src/inputs/Command.cpp @@ -19,19 +19,18 @@ #include #include #include - -#include "String.hpp" +#include namespace nexo::editor { Command::Command( - const std::string &description, + 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(description), m_key(key), m_pressedCallback(pressedCallback), m_releaseCallback(releaseCallback), m_repeatCallback(repeatCallback), m_isModifier(isModifier), m_childrens(children) + 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) { // Create a mapping of key names to ImGuiKey values static const std::unordered_map keyMap = { @@ -114,8 +113,8 @@ namespace nexo::editor { segment.erase(segment.find_last_not_of(" \t") + 1); // Convert to lowercase for case-insensitive comparison - std::transform(segment.begin(), segment.end(), segment.begin(), - [](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 auto it = keyMap.find(segment); diff --git a/editor/src/inputs/Command.hpp b/editor/src/inputs/Command.hpp index 5f1308918..4aa722ce7 100644 --- a/editor/src/inputs/Command.hpp +++ b/editor/src/inputs/Command.hpp @@ -24,7 +24,7 @@ namespace nexo::editor { class Command { public: Command( - const std::string &description, + std::string description, const std::string &key, const std::function &pressedCallback, const std::function &releaseCallback, @@ -33,29 +33,29 @@ namespace nexo::editor { const std::vector &childrens = {} ); - bool exactMatch(const std::bitset &inputSignature) const; - bool partialMatch(const std::bitset &inputSignature) const; + [[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; - const std::span getChildren() const; - const std::bitset &getSignature() const; - const std::string &getKey() const; - const std::string &getDescription() const; - bool isModifier() const; + [[nodiscard]] const 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(std::function cb) { pressed = cb; return *this; } - Builder& onReleased(std::function cb) { released = cb; return *this; } - Builder& onRepeat(std::function cb) { repeat = cb; return *this; } - Builder& modifier(bool val) { mod = 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; } - Command build() const { - return Command(desc, k, pressed, released, repeat, mod, children); + [[nodiscard]] Command build() const { + return {desc, k, pressed, released, repeat, mod, children}; } private: @@ -68,7 +68,7 @@ namespace nexo::editor { std::vector children; }; - static Builder create() { return Builder(); } + static Builder create() { return {}; } private: std::bitset m_signature; diff --git a/editor/src/inputs/InputManager.cpp b/editor/src/inputs/InputManager.cpp index 1ace81529..c19b7c387 100644 --- a/editor/src/inputs/InputManager.cpp +++ b/editor/src/inputs/InputManager.cpp @@ -17,11 +17,10 @@ #include #include -// Implementation sketch for InputManager - namespace nexo::editor { - void InputManager::processInputs(const WindowState& windowState) { + void InputManager::processInputs(const WindowState& windowState) + { std::bitset pressedSignature; std::bitset releasedSignature; std::bitset repeatSignature; @@ -43,19 +42,18 @@ namespace nexo::editor { // Track multiple-press detection static std::vector keyLastPressTime(ImGuiKey_NamedKey_COUNT, -1.0f); static std::vector keyPressCount(ImGuiKey_NamedKey_COUNT, 0); - const float multiPressThreshold = 0.3f; // Time threshold for multiple press detection (seconds) - float currentTime = ImGui::GetTime(); + const double currentTime = ImGui::GetTime(); 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; - ImGuiKey imKey = static_cast(key); - size_t idx = static_cast(key - ImGuiKey_NamedKey_BEGIN); + const auto imKey = static_cast(key); + const auto idx = static_cast(key - ImGuiKey_NamedKey_BEGIN); - bool keyDown = ImGui::IsKeyDown(imKey); - bool keyPressed = ImGui::IsKeyPressed(imKey, false); + const bool keyDown = ImGui::IsKeyDown(imKey); // Update currently held keys if (keyDown) { @@ -73,7 +71,7 @@ namespace nexo::editor { } else { keyPressCount[idx] = 1; } - keyLastPressTime[idx] = currentTime; + keyLastPressTime[idx] = static_cast(currentTime); } } else { if (lastFrameHeldKeys[idx]) { @@ -183,15 +181,17 @@ namespace nexo::editor { } // Add this method implementation - std::vector InputManager::getAllPossibleCommands(const WindowState& windowState) const { + std::vector InputManager::getAllPossibleCommands(const WindowState& windowState) const + { std::vector allCommands; // Use an empty signature to get all commands - std::bitset emptySignature; + const std::bitset emptySignature; collectPossibleCommands(windowState.getCommands(), emptySignature, allCommands); return allCommands; } - std::vector InputManager::getPossibleCommands(const WindowState& windowState) const { + std::vector InputManager::getPossibleCommands(const WindowState& windowState) const + { std::bitset pressedSignature; static const std::set excludedKeys = { @@ -222,13 +222,13 @@ namespace nexo::editor { void InputManager::collectPossibleCommands( const std::span& commands, const std::bitset& pressedSignature, - std::vector& possibleCommands) const { - + std::vector& possibleCommands) const + { for (const auto& command : commands) { // If no keys are pressed, show all possible command chains if (pressedSignature.none()) { if (command.getChildren().empty() || !command.isModifier()) { - possibleCommands.push_back({command.getKey(), command.getDescription()}); + possibleCommands.emplace_back(command.getKey(), command.getDescription()); } else { // For modifiers with children, build combinations recursively std::vector childCombinations; @@ -254,27 +254,27 @@ 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; } // Child modifier is pressed, show only its children's keys for (const auto& grandchild : child.getChildren()) { - possibleCommands.push_back({grandchild.getKey(), grandchild.getDescription()}); + possibleCommands.emplace_back(grandchild.getKey(), grandchild.getDescription()); } } else { // No child modifiers are pressed, show all children if (child.isModifier() && !child.getChildren().empty()) { // Build combinations for this child modifier for (const auto& grandchild : child.getChildren()) { - possibleCommands.push_back({ + possibleCommands.emplace_back( child.getKey() + "+" + grandchild.getKey(), grandchild.getDescription() - }); + ); } } else { // Child is not a modifier - possibleCommands.push_back({child.getKey(), child.getDescription()}); + possibleCommands.emplace_back(child.getKey(), child.getDescription()); } } } @@ -298,13 +298,14 @@ namespace nexo::editor { void InputManager::buildCommandCombinations( const Command& command, const std::string& prefix, - std::vector& combinations) const { + std::vector& combinations) const + { std::string currentPrefix = prefix.empty() ? command.getKey() : prefix + "+" + command.getKey(); // If this is a leaf command or not a modifier, add the combination if (command.getChildren().empty() || !command.isModifier()) { - combinations.push_back({currentPrefix, command.getDescription()}); + combinations.emplace_back(currentPrefix, command.getDescription()); return; } diff --git a/editor/src/inputs/InputManager.hpp b/editor/src/inputs/InputManager.hpp index e1e78d364..565fa2216 100644 --- a/editor/src/inputs/InputManager.hpp +++ b/editor/src/inputs/InputManager.hpp @@ -16,45 +16,47 @@ #include "WindowState.hpp" #include "Command.hpp" #include +#include namespace nexo::editor { + struct CommandInfo { std::string key; std::string description; - CommandInfo(const std::string& k, const std::string& d) : key(k), description(d) {} + CommandInfo(std::string k, std::string d) : key(std::move(k)), description(std::move(d)) {} }; class InputManager { - public: - InputManager() = default; - ~InputManager() = default; - - // Process inputs based on current window state - void processInputs(const WindowState& windowState); - - // Update these method signatures: - std::vector getPossibleCommands(const WindowState& windowState) const; - 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 + 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; }; } diff --git a/editor/src/inputs/WindowState.cpp b/editor/src/inputs/WindowState.cpp index 47bc1b859..7c6991e29 100644 --- a/editor/src/inputs/WindowState.cpp +++ b/editor/src/inputs/WindowState.cpp @@ -15,6 +15,7 @@ #include "WindowState.hpp" namespace nexo::editor { + unsigned int WindowState::getId() const { return m_id; diff --git a/editor/src/inputs/WindowState.hpp b/editor/src/inputs/WindowState.hpp index 9b0c768a5..63d18a750 100644 --- a/editor/src/inputs/WindowState.hpp +++ b/editor/src/inputs/WindowState.hpp @@ -21,14 +21,15 @@ namespace nexo::editor { class WindowState { public: WindowState() = default; - WindowState(unsigned int id) : m_id(id) {} + + WindowState(const unsigned int id) : m_id(id) {} ~WindowState() = default; - unsigned int getId() const; + [[nodiscard]] unsigned int getId() const; void registerCommand(const Command &command); - const std::span getCommands() const; + [[nodiscard]] const std::span getCommands() const; private: - unsigned int m_id; + unsigned int m_id{}; std::vector m_commands; }; } diff --git a/editor/src/utils/Config.cpp b/editor/src/utils/Config.cpp index 59ec56ad4..038061475 100644 --- a/editor/src/utils/Config.cpp +++ b/editor/src/utils/Config.cpp @@ -80,7 +80,7 @@ namespace nexo::editor { } std::string line; - std::regex windowRegex("\\[Window\\]\\[(###Default Scene\\d+)\\]"); + std::regex windowRegex(R"(\[Window\]\[(###Default Scene\d+)\])"); while (std::getline(configFile, line)) { std::smatch match; @@ -110,7 +110,7 @@ namespace nexo::editor { bool inWindowSection = false; bool isHashedWindow = false; - std::regex windowHeaderRegex("\\[Window\\]\\[(.+)\\]"); + std::regex windowHeaderRegex(R"(\[Window\]\[(.+)\])"); std::regex dockIdRegex("DockId=(0x[0-9a-fA-F]+)"); while (std::getline(configFile, line)) { diff --git a/editor/src/utils/EditorProps.cpp b/editor/src/utils/EditorProps.cpp index 8bf3832c7..6d86c98eb 100644 --- a/editor/src/utils/EditorProps.cpp +++ b/editor/src/utils/EditorProps.cpp @@ -22,11 +22,10 @@ namespace nexo::editor::utils { - static void addCameraProps(ecs::Entity entity) + static void addCameraProps(const ecs::Entity entity) { - auto& app = getApp(); auto& catalog = assets::AssetCatalog::getInstance(); - nexo::components::Material billboardMat{}; + components::Material billboardMat{}; billboardMat.isOpaque = false; static const assets::AssetRef cameraIconTexture = catalog.createAsset( assets::AssetLocation("_internal::cameraIcon@_internal"), @@ -34,17 +33,16 @@ namespace nexo::editor::utils { ); billboardMat.albedoTexture = cameraIconTexture; billboardMat.shader = "Albedo unshaded transparent"; - auto billboard = std::make_shared(); - auto renderable = std::make_shared(billboardMat, billboard); - nexo::components::RenderComponent renderComponent(renderable, nexo::components::RenderType::RENDER_3D); - app.m_coordinator->addComponent(entity, renderComponent); + auto billboard = std::make_shared(); + auto renderable = std::make_shared(billboardMat, billboard); + const components::RenderComponent renderComponent(renderable, components::RenderType::RENDER_3D); + Application::m_coordinator->addComponent(entity, renderComponent); } - static void addPointLightProps(ecs::Entity entity) + static void addPointLightProps(const ecs::Entity entity) { - auto& app = getApp(); auto& catalog = assets::AssetCatalog::getInstance(); - nexo::components::Material billboardMat{}; + components::Material billboardMat{}; billboardMat.isOpaque = false; static const assets::AssetRef pointLightIconTexture = catalog.createAsset( assets::AssetLocation("_internal::pointLightIcon@_internal"), @@ -52,17 +50,16 @@ namespace nexo::editor::utils { ); billboardMat.albedoTexture = pointLightIconTexture; billboardMat.shader = "Albedo unshaded transparent"; - auto billboard = std::make_shared(); - auto renderable = std::make_shared(billboardMat, billboard); - nexo::components::RenderComponent renderComponent(renderable, nexo::components::RenderType::RENDER_3D); - app.m_coordinator->addComponent(entity, renderComponent); + auto billboard = std::make_shared(); + auto renderable = std::make_shared(billboardMat, billboard); + const components::RenderComponent renderComponent(renderable, components::RenderType::RENDER_3D); + Application::m_coordinator->addComponent(entity, renderComponent); } - static void addSpotLightProps(ecs::Entity entity) + static void addSpotLightProps(const ecs::Entity entity) { - auto& app = getApp(); auto& catalog = assets::AssetCatalog::getInstance(); - nexo::components::Material billboardMat{}; + components::Material billboardMat{}; billboardMat.isOpaque = false; static const assets::AssetRef spotLightIconTexture = catalog.createAsset( assets::AssetLocation("_internal::spotLightIcon@_internal"), @@ -70,13 +67,13 @@ namespace nexo::editor::utils { ); billboardMat.albedoTexture = spotLightIconTexture; billboardMat.shader = "Albedo unshaded transparent"; - auto billboard = std::make_shared(); - auto renderable = std::make_shared(billboardMat, billboard); - nexo::components::RenderComponent renderComponent(renderable, nexo::components::RenderType::RENDER_3D); - app.m_coordinator->addComponent(entity, renderComponent); + auto billboard = std::make_shared(); + auto renderable = std::make_shared(billboardMat, billboard); + const components::RenderComponent renderComponent(renderable, components::RenderType::RENDER_3D); + Application::m_coordinator->addComponent(entity, renderComponent); } - void addPropsTo(ecs::Entity entity, PropsType type) + void addPropsTo(const ecs::Entity entity, const PropsType type) { switch (type) { diff --git a/editor/src/utils/FileSystem.cpp b/editor/src/utils/FileSystem.cpp index 895f0c3df..0fa55a28d 100644 --- a/editor/src/utils/FileSystem.cpp +++ b/editor/src/utils/FileSystem.cpp @@ -20,7 +20,7 @@ namespace nexo::editor::utils { #ifdef _WIN32 ShellExecuteA(nullptr, "open", folderPath.c_str(), nullptr, nullptr, SW_SHOWDEFAULT); #else - std::string command = "xdg-open " + folderPath; + const std::string command = "xdg-open " + folderPath; std::system(command.c_str()); #endif } diff --git a/editor/src/utils/ScenePreview.cpp b/editor/src/utils/ScenePreview.cpp index 77a8c8124..517fd947d 100644 --- a/editor/src/utils/ScenePreview.cpp +++ b/editor/src/utils/ScenePreview.cpp @@ -19,6 +19,7 @@ #include "components/Camera.hpp" namespace nexo::editor::utils { + float computeBoundingSphereRadius(const components::TransformComponent &objectTransform) { const float halfX = objectTransform.size.x * 0.5f; @@ -37,17 +38,17 @@ namespace nexo::editor::utils { return atanf(radius / distance); } - static ecs::Entity copyEntity(ecs::Entity entity) + static ecs::Entity copyEntity(const ecs::Entity entity) { - const ecs::Entity entityCopy = nexo::Application::m_coordinator->createEntity(); - const auto renderComponentCopy = nexo::Application::m_coordinator->getComponent(entity).clone(); - const auto &transformComponentBase = nexo::Application::m_coordinator->getComponent(entity); + const ecs::Entity entityCopy = Application::m_coordinator->createEntity(); + const auto renderComponentCopy = Application::m_coordinator->getComponent(entity).clone(); + const auto &transformComponentBase = Application::m_coordinator->getComponent(entity); components::TransformComponent transformComponent; transformComponent.pos = {0.0f, 0.0f, -transformComponentBase.size.z * 2.0f}; transformComponent.quat = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); transformComponent.size = transformComponentBase.size; - nexo::Application::m_coordinator->addComponent(entityCopy, renderComponentCopy); - nexo::Application::m_coordinator->addComponent(entityCopy, transformComponent); + Application::m_coordinator->addComponent(entityCopy, renderComponentCopy); + Application::m_coordinator->addComponent(entityCopy, transformComponent); return entityCopy; } @@ -62,8 +63,8 @@ namespace nexo::editor::utils { }; framebufferSpecs.width = static_cast(previewSize.x); framebufferSpecs.height = static_cast(previewSize.y); - const auto &transformComponentBase = nexo::Application::m_coordinator->getComponent(entity); - const auto &transformComponent = nexo::Application::m_coordinator->getComponent(entityCopy); + const auto &transformComponentBase = Application::m_coordinator->getComponent(entity); + const auto &transformComponent = Application::m_coordinator->getComponent(entityCopy); auto framebuffer = renderer::NxFramebuffer::create(framebufferSpecs); @@ -96,9 +97,9 @@ namespace nexo::editor::utils { ecs::Entity cameraId = CameraFactory::createPerspectiveCamera(cameraPos, framebufferSpecs.width, framebufferSpecs.height, framebuffer, clearColor); - auto &cameraTransform = nexo::Application::m_coordinator->getComponent(cameraId); + auto &cameraTransform = Application::m_coordinator->getComponent(cameraId); cameraTransform.pos = cameraPos; - auto &cameraComponent = nexo::Application::m_coordinator->getComponent(cameraId); + auto &cameraComponent = Application::m_coordinator->getComponent(cameraId); cameraComponent.render = true; glm::vec3 newFront = glm::normalize(targetPos - cameraPos); @@ -107,28 +108,28 @@ namespace nexo::editor::utils { components::PerspectiveCameraTarget cameraTarget; cameraTarget.targetEntity = entityCopy; cameraTarget.distance = transformComponentBase.size.z * 2.0f; - nexo::Application::m_coordinator->addComponent(cameraId, cameraTarget); + Application::m_coordinator->addComponent(cameraId, cameraTarget); app.getSceneManager().getScene(sceneId).addEntity(cameraId); return cameraId; } - static void setupPreviewLights(scene::SceneId sceneId, ecs::Entity entityCopy) + static void setupPreviewLights(const scene::SceneId sceneId, const ecs::Entity entityCopy) { auto &app = getApp(); const auto &transformComponent = Application::m_coordinator->getComponent(entityCopy); app.getSceneManager().getScene(sceneId).addEntity(entityCopy); - ecs::Entity ambientLight = LightFactory::createAmbientLight({0.5f, 0.5f, 0.5f}); + const ecs::Entity ambientLight = LightFactory::createAmbientLight({0.5f, 0.5f, 0.5f}); app.getSceneManager().getScene(sceneId).addEntity(ambientLight); - ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.2f, -1.0f, -0.3f}); + const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.2f, -1.0f, -0.3f}); app.getSceneManager().getScene(sceneId).addEntity(directionalLight); - float spotLightHalfAngle = utils::computeSpotlightHalfAngle(transformComponent, {0.0, 2.0f, -5.0f}); - float margin = glm::radians(2.5f); - ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 2.0f, -5.0f}, {0.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, 0.0900000035F, 0.0320000015F, glm::cos(spotLightHalfAngle), glm::cos(spotLightHalfAngle + margin)); + const float spotLightHalfAngle = utils::computeSpotlightHalfAngle(transformComponent, {0.0, 2.0f, -5.0f}); + constexpr float margin = glm::radians(2.5f); + const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 2.0f, -5.0f}, {0.0f, -1.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, 0.0900000035F, 0.0320000015F, glm::cos(spotLightHalfAngle), glm::cos(spotLightHalfAngle + margin)); app.getSceneManager().getScene(sceneId).addEntity(spotLight); } - void genScenePreview(const std::string &uniqueSceneName, const glm::vec2 &previewSize, ecs::Entity entity, ScenePreviewOut &out, const glm::vec4 &clearColor) + void genScenePreview(const std::string &uniqueSceneName, const glm::vec2 &previewSize, const ecs::Entity entity, ScenePreviewOut &out, const glm::vec4 &clearColor) { auto &app = getApp(); diff --git a/editor/src/utils/ScenePreview.hpp b/editor/src/utils/ScenePreview.hpp index 8da380ad7..e6c09df36 100644 --- a/editor/src/utils/ScenePreview.hpp +++ b/editor/src/utils/ScenePreview.hpp @@ -27,9 +27,9 @@ 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. + 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. }; diff --git a/editor/src/utils/String.cpp b/editor/src/utils/String.cpp index 4cf33fae2..5a1db93cb 100644 --- a/editor/src/utils/String.cpp +++ b/editor/src/utils/String.cpp @@ -17,7 +17,7 @@ namespace nexo::editor::utils { std::string removeIconPrefix(const std::string &str) { - if (size_t pos = str.find(" "); pos != std::string::npos) + if (const size_t pos = str.find(' '); pos != std::string::npos) return str.substr(pos + 1); return str; } diff --git a/editor/src/utils/TransparentStringHash.hpp b/editor/src/utils/TransparentStringHash.hpp index 2b33b01ad..2cc6d0e6f 100644 --- a/editor/src/utils/TransparentStringHash.hpp +++ b/editor/src/utils/TransparentStringHash.hpp @@ -11,6 +11,7 @@ // Description: Header file containing the transparent string hash used for maps // /////////////////////////////////////////////////////////////////////////////// +#pragma once #include From 0ac99c75719d491bbea496243391e4a770a292b7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 2 May 2025 05:29:16 +0900 Subject: [PATCH 340/450] fix(undo-redo): fix hotspot issue sonar --- editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp index 40b2f2ad8..ed237191f 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp @@ -26,6 +26,8 @@ namespace nexo::editor { const std::string editableName = obj.uiName.substr(ObjectTypeToIcon.at(obj.type).size()); buffer[sizeof(buffer) - 1] = '\0'; strncpy(buffer, editableName.c_str(), sizeof(buffer)); + if (buffer[sizeof buffer - 1] != 0) + return; ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove border ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); // No rounding From 33d708303f980dd29684dc0074f5f754b9940bdd Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 2 May 2025 05:50:52 +0900 Subject: [PATCH 341/450] fix(undo-reo): better fix for sonar --- editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp index ed237191f..2294afb39 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp @@ -24,10 +24,7 @@ namespace nexo::editor { char buffer[256]; const std::string editableName = obj.uiName.substr(ObjectTypeToIcon.at(obj.type).size()); - buffer[sizeof(buffer) - 1] = '\0'; - strncpy(buffer, editableName.c_str(), sizeof(buffer)); - if (buffer[sizeof buffer - 1] != 0) - return; + std::snprintf(buffer, sizeof(buffer), "%s", editableName.c_str()); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove border ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); // No rounding From ee33047422941d1202881483d7f8c24baa8293df Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 2 May 2025 06:33:07 +0900 Subject: [PATCH 342/450] fix(undo-redo): fix sonar issues --- .../EditorScene/EditorScene.hpp | 1 - .../DocumentWindows/EditorScene/Toolbar.cpp | 19 +++++++++--------- .../DocumentWindows/SceneTreeWindow/Show.cpp | 20 +++++++++---------- editor/src/Editor.cpp | 11 +++++----- editor/src/Editor.hpp | 8 ++++---- editor/src/ImNexo/Components.cpp | 16 +++++++-------- editor/src/ImNexo/Panels.cpp | 2 +- editor/src/inputs/InputManager.cpp | 2 +- 8 files changed, 36 insertions(+), 43 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp index 3511d7084..cf2a4390e 100644 --- a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp +++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp @@ -169,7 +169,6 @@ namespace nexo::editor { * positions it at the top of the viewport, and configures spacing. * * @param buttonWidth Standard width for toolbar buttons - * @param buttonHeight Standard height for toolbar buttons */ void initialToolbarSetup(float buttonWidth) const; diff --git a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp index b6764198a..5b9685bcf 100644 --- a/editor/src/DocumentWindows/EditorScene/Toolbar.cpp +++ b/editor/src/DocumentWindows/EditorScene/Toolbar.cpp @@ -93,7 +93,7 @@ namespace nexo::editor { this->m_popupManager.openPopup("Snap settings popup", ImVec2(400, 140)); }, .tooltip = "Toggle Translate Snap", - .buttonGradient = (m_snapTranslateOn) ? m_selectedGradient : m_buttonGradient + .buttonGradient = m_snapTranslateOn ? m_selectedGradient : m_buttonGradient }, { .uniqueId = "toggle_rotate_snap", @@ -107,7 +107,7 @@ namespace nexo::editor { this->m_popupManager.openPopup("Snap settings popup", ImVec2(400, 140)); }, .tooltip = "Toggle Rotate Snap", - .buttonGradient = (m_snapRotateOn) ? m_selectedGradient : m_buttonGradient + .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 // { @@ -161,17 +161,17 @@ namespace nexo::editor { ImGui::Spacing(); ImGui::Spacing(); - const float buttonWidth = 120.0f; + constexpr float buttonWidth = 120.0f; const float windowWidth = ImGui::GetWindowSize().x; ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) { - m_popupManager.closePopupInContext(); + PopupManager::closePopupInContext(); } ImGui::Unindent(10.0f); ImGui::PopStyleVar(); - m_popupManager.closePopup(); + PopupManager::closePopup(); } } @@ -205,17 +205,17 @@ namespace nexo::editor { ImGui::Spacing(); ImGui::Spacing(); - const float buttonWidth = 120.0f; + constexpr float buttonWidth = 120.0f; const float windowWidth = ImGui::GetWindowSize().x; ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); if (ImNexo::Button("OK", ImVec2(buttonWidth, 0.0f))) { - m_popupManager.closePopupInContext(); + PopupManager::closePopupInContext(); } ImGui::Unindent(10.0f); ImGui::PopStyleVar(); - m_popupManager.closePopup(); + PopupManager::closePopup(); } } @@ -257,7 +257,6 @@ namespace nexo::editor { void EditorScene::renderToolbar() { - auto &app = getApp(); constexpr float buttonWidth = 35.0f; constexpr float buttonHeight = 35.0f; constexpr ImVec2 buttonSize{buttonWidth, buttonHeight}; @@ -351,7 +350,7 @@ namespace nexo::editor { // -------- Grid enabled button -------- bool rightClicked = false; - components::RenderContext::GridParams &gridParams = app.m_coordinator->getSingletonComponent().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 = !gridParams.enabled; diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp index 7c589957b..0ed54d099 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -104,18 +104,18 @@ namespace nexo::editor { { if (ImGui::MenuItem("Create Scene")) m_popupManager.openPopup("Create New Scene"); - m_popupManager.closePopup(); + PopupManager::closePopup(); } if (m_popupManager.showPopup("Scene selection context menu")) { m_popupManager.runPopupCallback("Scene selection context menu"); - m_popupManager.closePopup(); + PopupManager::closePopup(); } if (m_popupManager.showPopupModal("Popup camera inspector")) { m_popupManager.runPopupCallback("Popup camera inspector"); - m_popupManager.closePopup(); + PopupManager::closePopup(); } } @@ -129,18 +129,16 @@ namespace nexo::editor { ImGui::Text("Enter Scene Name:"); ImGui::InputText("##SceneName", sceneNameBuffer, sizeof(sceneNameBuffer)); - if (ImNexo::Button("Create")) { - if (handleSceneCreation(sceneNameBuffer)) { - memset(sceneNameBuffer, 0, sizeof(sceneNameBuffer)); - m_popupManager.closePopupInContext(); - } + if (ImNexo::Button("Create") && handleSceneCreation(sceneNameBuffer)) { + memset(sceneNameBuffer, 0, sizeof(sceneNameBuffer)); + PopupManager::closePopupInContext(); } ImGui::SameLine(); if (ImNexo::Button("Cancel")) - m_popupManager.closePopupInContext(); + PopupManager::closePopupInContext(); - m_popupManager.closePopup(); + PopupManager::closePopup(); } void SceneTreeWindow::showNode(SceneObject &object) @@ -182,7 +180,7 @@ namespace nexo::editor { if (object.type != SelectionType::NONE && ImGui::BeginPopupContextItem(uniqueLabel.c_str())) { // Only show rename option for the primary selected entity or for non-selected entities - if ((!isSelected || (isSelected && selector.getPrimaryEntity() == object.data.entity)) && + if ((!isSelected || selector.getPrimaryEntity() == object.data.entity) && ImGui::MenuItem("Rename")) { m_renameTarget = {object.type, object.uuid}; diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index dd9fdce25..0c25fc6e0 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -158,7 +158,7 @@ namespace nexo::editor { 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.5, 0.09f * 1.15, 0.13f * 1.25, 1.0f); + 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); @@ -286,7 +286,7 @@ namespace nexo::editor { // ───────────────────────────────────────────── // Dock the windows into their corresponding nodes. - const std::string defaultSceneUniqueStrId = std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(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); @@ -314,7 +314,7 @@ namespace nexo::editor { ImGui::DockSpaceOverViewport(viewport->ID); } - void Editor::handleGlobalCommands() + void Editor::handleGlobalCommands() const { if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyPressed(ImGuiKey_Z)) { @@ -371,7 +371,7 @@ namespace nexo::editor { return possibleCommands; } - void Editor::drawShortcutBar(const std::vector &possibleCommands) + void Editor::drawShortcutBar(const std::vector &possibleCommands) const { constexpr float bottomBarHeight = 38.0f; ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.05f, 0.06f, 0.12f, 0.85f)); // Matches your dark blue theme @@ -472,7 +472,7 @@ namespace nexo::editor { ImGui::PopStyleColor(2); // Pop both text and bg colors } - void Editor::drawBackground() + void Editor::drawBackground() const { const auto viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->Pos); @@ -515,7 +515,6 @@ namespace nexo::editor { buildDockspace(); drawMenuBar(); - // ImGui::ShowDemoWindow(); m_windowRegistry.render(); handleGlobalCommands(); diff --git a/editor/src/Editor.hpp b/editor/src/Editor.hpp index 0d687d8e2..71683a08e 100644 --- a/editor/src/Editor.hpp +++ b/editor/src/Editor.hpp @@ -157,11 +157,11 @@ namespace nexo::editor { * sets a flag to signal that the editor should quit. */ void drawMenuBar(); - void drawShortcutBar(const std::vector &possibleCommands); - void drawBackground(); + void drawShortcutBar(const std::vector &possibleCommands) const; + void drawBackground() const; - void handleGlobalCommands(); - std::vector handleFocusedWindowCommands(); + void handleGlobalCommands() const; + std::vector handleFocusedWindowCommands(); bool m_quit = false; bool m_showDemoWindow = false; diff --git a/editor/src/ImNexo/Components.cpp b/editor/src/ImNexo/Components.cpp index c1b024053..76e59597c 100644 --- a/editor/src/ImNexo/Components.cpp +++ b/editor/src/ImNexo/Components.cpp @@ -332,15 +332,13 @@ namespace ImNexo { IM_COL32(255, 180, 180, 255) }); - channels.sliders.push_back({ - labelId, - value, - speed, - minValue, - maxValue, - 0, 0, 0, 0, - "%.2f" - }); + channels.sliders.emplace_back(labelId, + value, + speed, + minValue, + maxValue, + 0, 0, 0, 0, + "%.2f"); return RowDragFloat(channels); } diff --git a/editor/src/ImNexo/Panels.cpp b/editor/src/ImNexo/Panels.cpp index d83685964..fd220d96a 100644 --- a/editor/src/ImNexo/Panels.cpp +++ b/editor/src/ImNexo/Panels.cpp @@ -406,7 +406,7 @@ namespace ImNexo { } nameIsEmpty = false; auto &selector = nexo::editor::Selector::get(); - auto &uuid = nexo::Application::m_coordinator->getComponent(camera); + const auto &uuid = 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); diff --git a/editor/src/inputs/InputManager.cpp b/editor/src/inputs/InputManager.cpp index c19b7c387..f1f0c6c96 100644 --- a/editor/src/inputs/InputManager.cpp +++ b/editor/src/inputs/InputManager.cpp @@ -166,7 +166,7 @@ namespace nexo::editor { } // Also check deeper in the hierarchy - auto remainingBits = repeatSignature; + const auto &remainingBits = repeatSignature; processRepeatCommands(command.getChildren(), remainingBits, currentlyHeldKeys); } // Standard partial match handling From 7793f966026429de0144132765ee9a22fab4115b Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 2 May 2025 07:58:47 +0900 Subject: [PATCH 343/450] fix(undo-redo): fix sonar issues --- common/math/Projection.cpp | 12 +-- engine/src/Application.cpp | 7 +- engine/src/Application.hpp | 3 - engine/src/CameraFactory.cpp | 5 +- engine/src/EntityFactory3D.cpp | 1 - engine/src/EntityFactory3D.hpp | 5 -- engine/src/LightFactory.cpp | 26 +++--- engine/src/assets/Asset.cpp | 1 - engine/src/assets/Asset.hpp | 1 - engine/src/assets/AssetCatalog.cpp | 2 + engine/src/assets/AssetCatalog.hpp | 1 - engine/src/assets/AssetImporter.cpp | 4 +- engine/src/assets/AssetImporter.hpp | 2 +- engine/src/assets/AssetImporterBase.hpp | 1 - engine/src/assets/AssetImporterContext.hpp | 2 +- .../src/assets/Assets/Material/Material.hpp | 2 - engine/src/assets/Assets/Model/Model.hpp | 1 - .../src/assets/Assets/Model/ModelImporter.cpp | 75 ++++++++-------- .../assets/Assets/Texture/TextureImporter.cpp | 4 +- engine/src/components/Camera.hpp | 4 +- engine/src/components/Render.hpp | 2 +- engine/src/components/Render3D.hpp | 3 - engine/src/components/RenderContext.hpp | 90 +++++++++---------- engine/src/components/SceneComponents.hpp | 42 ++++----- engine/src/components/Transform.hpp | 3 +- engine/src/components/Uuid.hpp | 4 +- engine/src/ecs/Access.hpp | 1 - engine/src/ecs/Components.hpp | 1 - engine/src/ecs/Coordinator.cpp | 12 +-- engine/src/ecs/Coordinator.hpp | 4 +- engine/src/ecs/Entity.cpp | 1 + engine/src/ecs/SingletonComponent.hpp | 1 - engine/src/ecs/SingletonComponentMixin.hpp | 2 - engine/src/renderer/Buffer.hpp | 2 +- engine/src/renderer/Framebuffer.cpp | 1 - engine/src/renderer/Framebuffer.hpp | 4 +- engine/src/renderer/RenderCommand.hpp | 22 ++--- engine/src/renderer/Renderer.cpp | 7 +- engine/src/renderer/Renderer.hpp | 3 - engine/src/renderer/Renderer3D.cpp | 12 +-- engine/src/renderer/Renderer3D.hpp | 15 ++-- engine/src/renderer/Shader.hpp | 12 +-- engine/src/renderer/Texture.cpp | 4 +- engine/src/renderer/Texture.hpp | 2 +- engine/src/renderer/VertexArray.hpp | 2 +- .../src/renderer/opengl/OpenGlFramebuffer.cpp | 1 - .../src/renderer/opengl/OpenGlFramebuffer.hpp | 2 +- .../src/renderer/opengl/OpenGlRendererApi.cpp | 19 ++-- engine/src/renderer/opengl/OpenGlShader.hpp | 16 ++-- .../opengl/OpenGlShaderStorageBuffer.cpp | 6 +- .../opengl/OpenGlShaderStorageBuffer.hpp | 4 +- .../src/renderer/opengl/OpenGlTexture2D.cpp | 10 +-- .../src/renderer/opengl/OpenGlTexture2D.hpp | 10 +-- .../src/renderer/opengl/OpenGlVertexArray.cpp | 1 - .../src/renderer/opengl/OpenGlVertexArray.hpp | 2 +- engine/src/renderer/primitives/Billboard.cpp | 44 +++++---- engine/src/renderer/primitives/Cube.cpp | 28 +++--- engine/src/systems/RenderSystem.cpp | 22 ++--- engine/src/systems/RenderSystem.hpp | 6 +- .../src/systems/lights/AmbientLightSystem.cpp | 4 +- 60 files changed, 278 insertions(+), 308 deletions(-) diff --git a/common/math/Projection.cpp b/common/math/Projection.cpp index 9691e61fd..61a4e280d 100644 --- a/common/math/Projection.cpp +++ b/common/math/Projection.cpp @@ -16,23 +16,23 @@ namespace nexo::math { - const glm::vec3 projectRayToWorld(float x, float y, + const glm::vec3 projectRayToWorld(const float x, const float y, const glm::mat4 &viewProjectionMatrix, const glm::vec3 &cameraPosition, - unsigned int width, unsigned int height + const unsigned int width, const unsigned int height ) { // Convert to NDC - float ndcX = (2.0f * x) / width - 1.0f; - float ndcY = 1.0f - (2.0f * y) / height; + const float ndcX = (2.0f * x) / static_cast(width) - 1.0f; + const float ndcY = 1.0f - (2.0f * y) / static_cast(height); - glm::mat4 inverseViewProj = glm::inverse(viewProjectionMatrix); + const glm::mat4 inverseViewProj = glm::inverse(viewProjectionMatrix); // Points in NDC space at near and far planes glm::vec4 nearPoint = inverseViewProj * glm::vec4(ndcX, ndcY, -1.0f, 1.0f); nearPoint /= nearPoint.w; - glm::vec3 rayDir = glm::normalize(glm::vec3(nearPoint) - cameraPosition); + const glm::vec3 rayDir = glm::normalize(glm::vec3(nearPoint) - cameraPosition); return rayDir; } diff --git a/engine/src/Application.cpp b/engine/src/Application.cpp index e48d90934..7e17c7310 100644 --- a/engine/src/Application.cpp +++ b/engine/src/Application.cpp @@ -29,6 +29,7 @@ #include "core/event/Input.hpp" #include "Timestep.hpp" #include "renderer/RendererExceptions.hpp" +#include "renderer/Renderer.hpp" #include "systems/CameraSystem.hpp" #include "systems/RenderSystem.hpp" #include "systems/lights/DirectionalLightsSystem.hpp" @@ -249,7 +250,7 @@ namespace nexo { if (!m_isMinimized) { - renderContext.sceneRendered = sceneInfo.id; + renderContext.sceneRendered = static_cast(sceneInfo.id); renderContext.sceneType = sceneInfo.sceneType; if (sceneInfo.isChildWindow) { renderContext.isChildWindow = true; @@ -287,12 +288,12 @@ namespace nexo { return m_coordinator->createEntity(); } - void Application::deleteEntity(ecs::Entity entity) + void Application::deleteEntity(const ecs::Entity entity) { const auto tag = m_coordinator->tryGetComponent(entity); if (tag) { - unsigned int sceneId = tag->get().id; + const unsigned int sceneId = tag->get().id; m_SceneManager.getScene(sceneId).removeEntity(entity); } m_coordinator->destroyEntity(entity); diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index c607c2bb3..214a9599c 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -23,7 +23,6 @@ #include "core/event/WindowEvent.hpp" #include "core/event/SignalEvent.hpp" #include "renderer/Buffer.hpp" -#include "renderer/Renderer.hpp" #include "ecs/Coordinator.hpp" #include "core/scene/SceneManager.hpp" #include "Logger.hpp" @@ -38,8 +37,6 @@ namespace nexo { - - enum EventDebugFlags { DEBUG_LOG_RESIZE_EVENT = 1 << 0, DEBUG_LOG_KEYBOARD_EVENT = 1 << 1, diff --git a/engine/src/CameraFactory.cpp b/engine/src/CameraFactory.cpp index e5dce48e7..ee3365a9f 100644 --- a/engine/src/CameraFactory.cpp +++ b/engine/src/CameraFactory.cpp @@ -13,6 +13,8 @@ /////////////////////////////////////////////////////////////////////////////// #include "CameraFactory.hpp" + +#include #include "Application.hpp" #include "components/Transform.hpp" #include "components/Camera.hpp" @@ -33,7 +35,8 @@ namespace nexo { camera.nearPlane = nearPlane; camera.farPlane = farPlane; camera.type = components::CameraType::PERSPECTIVE; - camera.m_renderTarget = renderTarget; + if (renderTarget) + camera.m_renderTarget = std::move(renderTarget); camera.clearColor = clearColor; ecs::Entity newCamera = Application::m_coordinator->createEntity(); diff --git a/engine/src/EntityFactory3D.cpp b/engine/src/EntityFactory3D.cpp index 6cdfea6e8..59f5a99ef 100644 --- a/engine/src/EntityFactory3D.cpp +++ b/engine/src/EntityFactory3D.cpp @@ -18,7 +18,6 @@ #include "components/Transform.hpp" #include "components/Uuid.hpp" #include "components/Camera.hpp" -#include "core/exceptions/Exceptions.hpp" #include "Application.hpp" #define GLM_ENABLE_EXPERIMENTAL diff --git a/engine/src/EntityFactory3D.hpp b/engine/src/EntityFactory3D.hpp index 9a9fe6a70..f88c9bf2d 100644 --- a/engine/src/EntityFactory3D.hpp +++ b/engine/src/EntityFactory3D.hpp @@ -14,14 +14,9 @@ #pragma once #include -#include -#include -#include #include "assets/Assets/Model/Model.hpp" -#include "ecs/ECSExceptions.hpp" #include "components/Components.hpp" -#include "renderer/Framebuffer.hpp" namespace nexo { diff --git a/engine/src/LightFactory.cpp b/engine/src/LightFactory.cpp index a7b7e55bf..4e345a566 100644 --- a/engine/src/LightFactory.cpp +++ b/engine/src/LightFactory.cpp @@ -20,34 +20,34 @@ #include "components/Uuid.hpp" namespace nexo { - ecs::Entity LightFactory::createAmbientLight(glm::vec3 color) + ecs::Entity LightFactory::createAmbientLight(const glm::vec3 color) { - ecs::Entity newAmbientLight = Application::m_coordinator->createEntity(); - components::AmbientLightComponent newAmbientLightComponent{color}; + const ecs::Entity newAmbientLight = Application::m_coordinator->createEntity(); + const components::AmbientLightComponent newAmbientLightComponent{color}; Application::m_coordinator->addComponent(newAmbientLight, newAmbientLightComponent); - components::UuidComponent uuid; + const components::UuidComponent uuid; Application::m_coordinator->addComponent(newAmbientLight, uuid); return newAmbientLight; } - ecs::Entity LightFactory::createDirectionalLight(glm::vec3 lightDir, glm::vec3 color) + ecs::Entity LightFactory::createDirectionalLight(const glm::vec3 lightDir, const glm::vec3 color) { - ecs::Entity newDirectionalLight = Application::m_coordinator->createEntity(); - components::DirectionalLightComponent newDirectionalLightComponent(lightDir, color); + const ecs::Entity newDirectionalLight = Application::m_coordinator->createEntity(); + const components::DirectionalLightComponent newDirectionalLightComponent(lightDir, color); Application::m_coordinator->addComponent(newDirectionalLight, newDirectionalLightComponent); - components::UuidComponent uuid; + const components::UuidComponent uuid; Application::m_coordinator->addComponent(newDirectionalLight, uuid); return newDirectionalLight; } - ecs::Entity LightFactory::createPointLight(glm::vec3 position, glm::vec3 color, float linear, float quadratic) + ecs::Entity LightFactory::createPointLight(const glm::vec3 position, const glm::vec3 color, const float linear, const float quadratic) { - ecs::Entity newPointLight = Application::m_coordinator->createEntity(); - components::TransformComponent transformComponent(position); + const ecs::Entity newPointLight = Application::m_coordinator->createEntity(); + const components::TransformComponent transformComponent(position); Application::m_coordinator->addComponent(newPointLight, transformComponent); - components::PointLightComponent newPointLightComponent(color, linear, quadratic); + const components::PointLightComponent newPointLightComponent(color, linear, quadratic); Application::m_coordinator->addComponent(newPointLight, newPointLightComponent); - components::UuidComponent uuid; + const components::UuidComponent uuid; Application::m_coordinator->addComponent(newPointLight, uuid); return newPointLight; } diff --git a/engine/src/assets/Asset.cpp b/engine/src/assets/Asset.cpp index f1e426c9f..29e78e9d8 100644 --- a/engine/src/assets/Asset.cpp +++ b/engine/src/assets/Asset.cpp @@ -13,7 +13,6 @@ /////////////////////////////////////////////////////////////////////////////// #include "Asset.hpp" -#include "Assets/Model/ModelImporter.hpp" namespace nexo::assets { diff --git a/engine/src/assets/Asset.hpp b/engine/src/assets/Asset.hpp index 9700ba4fa..28336a47a 100644 --- a/engine/src/assets/Asset.hpp +++ b/engine/src/assets/Asset.hpp @@ -20,7 +20,6 @@ #include #include #include -#include #include "AssetLocation.hpp" #include "AssetRef.hpp" diff --git a/engine/src/assets/AssetCatalog.cpp b/engine/src/assets/AssetCatalog.cpp index 402dfea73..b2736d0ac 100644 --- a/engine/src/assets/AssetCatalog.cpp +++ b/engine/src/assets/AssetCatalog.cpp @@ -16,6 +16,8 @@ #include +#include + namespace nexo::assets { void AssetCatalog::deleteAsset(AssetID id) diff --git a/engine/src/assets/AssetCatalog.hpp b/engine/src/assets/AssetCatalog.hpp index 70e417b86..600652f25 100644 --- a/engine/src/assets/AssetCatalog.hpp +++ b/engine/src/assets/AssetCatalog.hpp @@ -20,7 +20,6 @@ #include "Asset.hpp" #include "AssetImporter.hpp" #include "AssetLocation.hpp" -#include "Assets/Texture/Texture.hpp" #include "Assets/Texture/Texture.hpp" diff --git a/engine/src/assets/AssetImporter.cpp b/engine/src/assets/AssetImporter.cpp index a6b4d05e8..9c70ca446 100644 --- a/engine/src/assets/AssetImporter.cpp +++ b/engine/src/assets/AssetImporter.cpp @@ -21,6 +21,8 @@ #include "Assets/Texture/Texture.hpp" #include "Assets/Texture/TextureImporter.hpp" +#include + namespace nexo::assets { AssetImporter::AssetImporter() { @@ -98,7 +100,7 @@ namespace nexo::assets { getImportersForType(const std::type_index& typeIdx) const { if (const auto it = m_importers.find(typeIdx) ; it == m_importers.end()) { - static const std::vector empty; + static constexpr 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 7d57b264c..fa53de4cb 100644 --- a/engine/src/assets/AssetImporter.hpp +++ b/engine/src/assets/AssetImporter.hpp @@ -89,7 +89,7 @@ namespace nexo::assets { * @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. */ - GenericAssetRef importAssetTryImporters(const AssetLocation& location, const ImporterInputVariant& inputVariant, const std::vector& + [[nodiscard]] GenericAssetRef importAssetTryImporters(const AssetLocation& location, const ImporterInputVariant& inputVariant, const std::vector& importers) const; /** diff --git a/engine/src/assets/AssetImporterBase.hpp b/engine/src/assets/AssetImporterBase.hpp index b91084a25..1a7305c10 100644 --- a/engine/src/assets/AssetImporterBase.hpp +++ b/engine/src/assets/AssetImporterBase.hpp @@ -14,7 +14,6 @@ #pragma once -#include "Asset.hpp" #include "AssetImporterContext.hpp" #include "AssetImporterInput.hpp" diff --git a/engine/src/assets/AssetImporterContext.hpp b/engine/src/assets/AssetImporterContext.hpp index 045e9f97b..29fb35ff8 100644 --- a/engine/src/assets/AssetImporterContext.hpp +++ b/engine/src/assets/AssetImporterContext.hpp @@ -121,7 +121,7 @@ namespace nexo::assets { */ static AssetName formatUniqueName(const std::string& name, const AssetType type, unsigned int id) { - return AssetName(std::format("{}_{}{}", name, getAssetTypeName(type), id)); + return {std::format("{}_{}{}", name, getAssetTypeName(type), id)}; } private: diff --git a/engine/src/assets/Assets/Material/Material.hpp b/engine/src/assets/Assets/Material/Material.hpp index 3e9d3e13f..04b0a3702 100644 --- a/engine/src/assets/Assets/Material/Material.hpp +++ b/engine/src/assets/Assets/Material/Material.hpp @@ -16,8 +16,6 @@ #include "assets/Asset.hpp" -#include - namespace nexo::assets { /** diff --git a/engine/src/assets/Assets/Model/Model.hpp b/engine/src/assets/Assets/Model/Model.hpp index 15625ba7d..2f14a0397 100644 --- a/engine/src/assets/Assets/Model/Model.hpp +++ b/engine/src/assets/Assets/Model/Model.hpp @@ -17,7 +17,6 @@ #include "assets/Asset.hpp" #include "components/Shapes3D.hpp" -#include "assets/Assets/Material/Material.hpp" namespace nexo::assets { diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index c81efcd0c..d68af03e5 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -56,8 +56,8 @@ namespace nexo::assets { auto model = std::make_unique(); const auto param = ctx.getParameters(); - int flags = aiProcess_Triangulate - | aiProcess_GenNormals; + 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); @@ -90,14 +90,13 @@ namespace nexo::assets { for (int i = 0; scene->mNumTextures; ++i) { aiTexture *texture = scene->mTextures[i]; auto loadedTexture = loadEmbeddedTexture(ctx, texture); - m_textures.emplace(texture->mFilename.C_Str(), loadedTexture); + m_textures.try_emplace(texture->mFilename.C_Str(), loadedTexture); } } AssetRef ModelImporter::loadEmbeddedTexture(AssetImporterContext& ctx, aiTexture* texture) { - if (texture->mHeight == 0) { // Compressed texture AssetImporter assetImporter; const ImporterInputVariant inputVariant = ImporterMemoryInput{ @@ -109,30 +108,28 @@ namespace nexo::assets { return assetImporter.importAsset( ctx.genUniqueDependencyLocation(), inputVariant); - } else { // Uncompressed texture - auto& catalog = AssetCatalog::getInstance(); - - renderer::NxTextureFormat format; - if (texture->achFormatHint[0] == '\0') { // if empty, then ARGB888 - renderer::NxTextureFormatConvertArgb8ToRgba8( - reinterpret_cast(texture->pcData), - texture->mWidth * 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); - return nullptr; - } - - return catalog.createAsset(ctx.genUniqueDependencyLocation(), - reinterpret_cast(texture->pcData), texture->mWidth, texture->mHeight, format); + } + // Uncompressed texture + auto& catalog = AssetCatalog::getInstance(); + + renderer::NxTextureFormat format; + if (texture->achFormatHint[0] == '\0') { // if empty, then ARGB888 + renderer::NxTextureFormatConvertArgb8ToRgba8( + reinterpret_cast(texture->pcData), + texture->mWidth * 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); + return nullptr; + } + return catalog.createAsset(ctx.genUniqueDependencyLocation(), + reinterpret_cast(texture->pcData), texture->mWidth, texture->mHeight, format); } renderer::NxTextureFormat ModelImporter::convertAssimpHintToNxTextureFormat(const char achFormatHint[9]) @@ -143,19 +140,19 @@ namespace nexo::assets { } // Split into channels (first 4 chars) and bit depths (next 4 chars) - std::string_view channels(achFormatHint, 4); - std::string_view bits_str(achFormatHint + 4, 4); + const std::string_view channels(achFormatHint, 4); + const std::string_view bits_str(achFormatHint + 4, 4); // Parse active channels and their bit depths struct ChannelInfo { char code; int bits; }; std::vector active_channels; for (int i = 0; i < 4; ++i) { - const char ch = static_cast(std::tolower(channels[i])); - if (not (ch == 'r' || ch == 'g' || ch == 'b' || ch == 'a')) { + const auto ch = static_cast(std::tolower(channels[i])); + if (!(ch == 'r' || ch == 'g' || ch == 'b' || ch == 'a')) { return renderer::NxTextureFormat::INVALID; } - if (not std::isdigit(bits_str[i])) { + if (!std::isdigit(bits_str[i])) { return renderer::NxTextureFormat::INVALID; } const int bits = bits_str[i] - '0'; @@ -265,19 +262,19 @@ namespace nexo::assets { return it->second; } } - std::filesystem::path texturePath = (modelDirectory / cStr).lexically_normal(); - auto texturePathStr = texturePath.string(); + 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()) { return it->second; } AssetImporter assetImporter; - ImporterInputVariant inputVariant = ImporterFileInput{ + const ImporterInputVariant inputVariant = ImporterFileInput{ .filePath = texturePath }; auto assetTexture = assetImporter.importAsset( ctx.genUniqueDependencyLocation(), inputVariant); - m_textures.emplace(texturePathStr.c_str(), assetTexture); + m_textures.try_emplace(texturePathStr.c_str(), assetTexture); return assetTexture; } return nullptr; @@ -295,7 +292,7 @@ namespace nexo::assets { materialComponent->metallicMap ? "Yes" : "No", materialComponent->roughnessMap ? "Yes" : "No"); - auto materialRef = AssetCatalog::getInstance().createAsset( + const auto materialRef = AssetCatalog::getInstance().createAsset( ctx.genUniqueDependencyLocation(), std::move(materialComponent) ); @@ -308,7 +305,7 @@ namespace nexo::assets { { auto meshNode = std::make_shared(); - glm::mat4 nodeTransform = convertAssimpMatrixToGLM(node->mTransformation); + const glm::mat4 nodeTransform = convertAssimpMatrixToGLM(node->mTransformation); meshNode->transform = nodeTransform; @@ -353,7 +350,7 @@ namespace nexo::assets { for (unsigned int i = 0; i < mesh->mNumFaces; i++) { - aiFace face = mesh->mFaces[i]; + const aiFace face = mesh->mFaces[i]; indices.insert(indices.end(), face.mIndices, face.mIndices + face.mNumIndices); } @@ -374,12 +371,12 @@ namespace nexo::assets { glm::mat4 ModelImporter::convertAssimpMatrixToGLM(const aiMatrix4x4& matrix) { - return glm::mat4( + 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/Texture/TextureImporter.cpp b/engine/src/assets/Assets/Texture/TextureImporter.cpp index ef855522d..0dacf5b4d 100644 --- a/engine/src/assets/Assets/Texture/TextureImporter.cpp +++ b/engine/src/assets/Assets/Texture/TextureImporter.cpp @@ -18,7 +18,7 @@ #include #include "assets/AssetImporterBase.hpp" #include "assets/Assets/Texture/Texture.hpp" -#include "TextureParameters.hpp" +#include namespace nexo::assets { @@ -57,7 +57,7 @@ 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(), input.memoryData.size(), nullptr, nullptr, nullptr); + const int ok = stbi_info_from_memory(input.memoryData.data(), static_cast(input.memoryData.size()), nullptr, nullptr, nullptr); return ok; } diff --git a/engine/src/components/Camera.hpp b/engine/src/components/Camera.hpp index 811ff3968..98ee49db7 100644 --- a/engine/src/components/Camera.hpp +++ b/engine/src/components/Camera.hpp @@ -17,11 +17,9 @@ #include "math/Vector.hpp" #include "core/event/Input.hpp" #include "renderer/Framebuffer.hpp" -#include "ecs/Entity.hpp" +#include "ecs/Definitions.hpp" #include #include -#include - namespace nexo::components { diff --git a/engine/src/components/Render.hpp b/engine/src/components/Render.hpp index c206b243c..a50dcbcdd 100644 --- a/engine/src/components/Render.hpp +++ b/engine/src/components/Render.hpp @@ -117,7 +117,7 @@ namespace nexo::components renderable = memento.renderable; } - Memento save() const + [[nodiscard]] Memento save() const { return { isRendered, diff --git a/engine/src/components/Render3D.hpp b/engine/src/components/Render3D.hpp index 494f13283..33ba4f397 100644 --- a/engine/src/components/Render3D.hpp +++ b/engine/src/components/Render3D.hpp @@ -13,10 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "renderer/Texture.hpp" -#include "renderer/Shader.hpp" #include -#include #include "assets/AssetRef.hpp" #include "assets/Assets/Texture/Texture.hpp" diff --git a/engine/src/components/RenderContext.hpp b/engine/src/components/RenderContext.hpp index 3494236e6..accc6e8de 100644 --- a/engine/src/components/RenderContext.hpp +++ b/engine/src/components/RenderContext.hpp @@ -20,60 +20,58 @@ #include "Light.hpp" namespace nexo::components { - struct RenderContext { - int sceneRendered = -1; - SceneType sceneType; - 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; - float minPixelsBetweenCells = 2.0f; - float cellSize = 0.025f; - }; - GridParams gridParams; - renderer::NxRenderer3D renderer3D; - std::queue cameras; - LightContext sceneLights; + struct RenderContext { + int sceneRendered = -1; + SceneType sceneType; + 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; + float minPixelsBetweenCells = 2.0f; + float cellSize = 0.025f; + }; + GridParams gridParams; + renderer::NxRenderer3D renderer3D; + std::queue cameras; + LightContext sceneLights; - RenderContext() - { - renderer3D.init(); - } - - // Delete copy constructor to enforce singleton semantics - RenderContext(const RenderContext&) = delete; - RenderContext& operator=(const RenderContext&) = delete; + RenderContext(): sceneType(), sceneLights() + { + renderer3D.init(); + } - RenderContext(RenderContext&& other) noexcept - : sceneRendered(other.sceneRendered), - renderer3D(std::move(other.renderer3D)), - cameras(std::move(other.cameras)), - sceneLights(std::move(other.sceneLights)) - { - } + // Delete copy constructor to enforce singleton semantics + RenderContext(const RenderContext&) = delete; + RenderContext& operator=(const RenderContext&) = delete; + RenderContext(RenderContext&& other) noexcept + : sceneRendered(other.sceneRendered), sceneType{}, + renderer3D(std::move(other.renderer3D)), + cameras(std::move(other.cameras)), + sceneLights(other.sceneLights) + {} ~RenderContext() { - renderer3D.shutdown(); + renderer3D.shutdown(); - reset(); + reset(); } - void reset() - { - sceneRendered = -1; - isChildWindow = false; - viewportBounds[0] = glm::vec2{}; - viewportBounds[1] = glm::vec2{}; - std::queue empty; - std::swap(cameras, empty); - sceneLights.ambientLight = glm::vec3(0.0f); - sceneLights.pointLightCount = 0; - sceneLights.spotLightCount = 0; - sceneLights.dirLight = DirectionalLightComponent{}; - } - }; + void reset() + { + sceneRendered = -1; + isChildWindow = false; + viewportBounds[0] = glm::vec2{}; + viewportBounds[1] = glm::vec2{}; + std::queue empty; + std::swap(cameras, empty); + sceneLights.ambientLight = glm::vec3(0.0f); + sceneLights.pointLightCount = 0; + sceneLights.spotLightCount = 0; + sceneLights.dirLight = DirectionalLightComponent{}; + } + }; } diff --git a/engine/src/components/SceneComponents.hpp b/engine/src/components/SceneComponents.hpp index 61cd2bb34..71e168077 100644 --- a/engine/src/components/SceneComponents.hpp +++ b/engine/src/components/SceneComponents.hpp @@ -23,27 +23,27 @@ namespace nexo::components { }; struct SceneTag { - unsigned int id; - bool isActive = true; - bool isRendered = true; - - struct Memento { - unsigned int id; - bool isActive; - bool isRendered; - }; - - void restore(const Memento &memento) - { - id = memento.id; - isActive = memento.isActive; - isRendered = memento.isRendered; - } - - Memento save() const - { - return {id, isActive, isRendered}; - } + unsigned int id{}; + bool isActive = true; + bool isRendered = true; + + struct Memento { + unsigned int id; + bool isActive; + bool isRendered; + }; + + void restore(const Memento &memento) + { + id = memento.id; + isActive = memento.isActive; + isRendered = memento.isRendered; + } + + [[nodiscard]] Memento save() const + { + return {id, isActive, isRendered}; + } }; } diff --git a/engine/src/components/Transform.hpp b/engine/src/components/Transform.hpp index 7742b9fce..fa7d5b060 100644 --- a/engine/src/components/Transform.hpp +++ b/engine/src/components/Transform.hpp @@ -16,7 +16,6 @@ #include #include - namespace nexo::components { struct TransformComponent final { @@ -33,7 +32,7 @@ namespace nexo::components { size = memento.scale; } - Memento save() const + [[nodiscard]] Memento save() const { return {pos, quat, size}; } diff --git a/engine/src/components/Uuid.hpp b/engine/src/components/Uuid.hpp index 4206dbee4..1dbdddc29 100644 --- a/engine/src/components/Uuid.hpp +++ b/engine/src/components/Uuid.hpp @@ -25,11 +25,11 @@ namespace nexo::components { std::uniform_int_distribution dist(0, 15); - const char *v = "0123456789abcdef"; 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)]; @@ -47,7 +47,7 @@ namespace nexo::components { uuid = memento.uuid; } - Memento save() const + [[nodiscard]] Memento save() const { return {uuid}; } diff --git a/engine/src/ecs/Access.hpp b/engine/src/ecs/Access.hpp index b198b4928..7ed05c2a0 100644 --- a/engine/src/ecs/Access.hpp +++ b/engine/src/ecs/Access.hpp @@ -14,7 +14,6 @@ #pragma once #include -#include #include namespace nexo::ecs { diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 71aefdecc..05131ed4c 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -356,7 +356,6 @@ namespace nexo::ecs { const Signature oldSignature, const Signature newSignature ) { - const ComponentType typeID = getComponentTypeID(); const auto &componentArray = getComponentArray(); const auto sourceComponent = componentArray->get(sourceEntity); addComponent(destEntity, sourceComponent, oldSignature, newSignature); diff --git a/engine/src/ecs/Coordinator.cpp b/engine/src/ecs/Coordinator.cpp index 3a5c844e6..28fe82700 100644 --- a/engine/src/ecs/Coordinator.cpp +++ b/engine/src/ecs/Coordinator.cpp @@ -76,20 +76,20 @@ namespace nexo::ecs { return components; } - Entity Coordinator::duplicateEntity(Entity sourceEntity) + Entity Coordinator::duplicateEntity(const Entity sourceEntity) const { - Entity newEntity = createEntity(); - Signature signature = m_entityManager->getSignature(sourceEntity); + const Entity newEntity = createEntity(); + const Signature signature = m_entityManager->getSignature(sourceEntity); Signature destSignature = m_entityManager->getSignature(newEntity); for (ComponentType type = 0; type < MAX_COMPONENT_TYPE; ++type) { if (signature.test(type) && m_typeIDtoTypeIndex.contains(type)) { - Signature previousSignature = destSignature; + const Signature previousSignature = destSignature; destSignature.set(type, true); m_componentManager->duplicateComponent(type, sourceEntity, newEntity, previousSignature, destSignature); } } - m_entityManager->setSignature(newEntity, destSignature); - m_systemManager->entitySignatureChanged(newEntity, Signature{}, destSignature); + m_entityManager->setSignature(newEntity, destSignature); + m_systemManager->entitySignatureChanged(newEntity, Signature{}, destSignature); return newEntity; } diff --git a/engine/src/ecs/Coordinator.hpp b/engine/src/ecs/Coordinator.hpp index 6a0990f4f..2596bb4f2 100644 --- a/engine/src/ecs/Coordinator.hpp +++ b/engine/src/ecs/Coordinator.hpp @@ -447,9 +447,9 @@ namespace nexo::ecs { return signature.test(componentType); } - bool supportsMementoPattern(const std::type_index typeIndex) const; + bool supportsMementoPattern(std::type_index typeIndex) const; - Entity duplicateEntity(Entity sourceEntity); + Entity duplicateEntity(Entity sourceEntity) const; private: template diff --git a/engine/src/ecs/Entity.cpp b/engine/src/ecs/Entity.cpp index f086d298f..b54f745f8 100644 --- a/engine/src/ecs/Entity.cpp +++ b/engine/src/ecs/Entity.cpp @@ -11,6 +11,7 @@ // Description: Source file for the entity class // /////////////////////////////////////////////////////////////////////////////// + #include "Entity.hpp" #include "ECSExceptions.hpp" diff --git a/engine/src/ecs/SingletonComponent.hpp b/engine/src/ecs/SingletonComponent.hpp index 3bb98e946..c37affc9b 100644 --- a/engine/src/ecs/SingletonComponent.hpp +++ b/engine/src/ecs/SingletonComponent.hpp @@ -15,7 +15,6 @@ #pragma once #include -#include #include #include "Definitions.hpp" diff --git a/engine/src/ecs/SingletonComponentMixin.hpp b/engine/src/ecs/SingletonComponentMixin.hpp index 6d73cabc7..b9cf1d628 100644 --- a/engine/src/ecs/SingletonComponentMixin.hpp +++ b/engine/src/ecs/SingletonComponentMixin.hpp @@ -14,10 +14,8 @@ #pragma once #include "Access.hpp" -#include "Definitions.hpp" #include "SingletonComponent.hpp" #include -#include #include namespace nexo::ecs { diff --git a/engine/src/renderer/Buffer.hpp b/engine/src/renderer/Buffer.hpp index 2d9bc6f1d..05bfd85a2 100644 --- a/engine/src/renderer/Buffer.hpp +++ b/engine/src/renderer/Buffer.hpp @@ -348,7 +348,7 @@ namespace nexo::renderer { */ [[nodiscard]] virtual unsigned int getCount() const = 0; - virtual unsigned int getId() const = 0; + [[nodiscard]] virtual unsigned int getId() const = 0; }; /** diff --git a/engine/src/renderer/Framebuffer.cpp b/engine/src/renderer/Framebuffer.cpp index 4cc5f3cd3..32072173e 100644 --- a/engine/src/renderer/Framebuffer.cpp +++ b/engine/src/renderer/Framebuffer.cpp @@ -25,7 +25,6 @@ namespace nexo::renderer { return std::make_shared(specs); #endif THROW_EXCEPTION(NxUnknownGraphicsApi, "UNKNOWN"); - } } diff --git a/engine/src/renderer/Framebuffer.hpp b/engine/src/renderer/Framebuffer.hpp index d6deb6e00..6cba74c92 100644 --- a/engine/src/renderer/Framebuffer.hpp +++ b/engine/src/renderer/Framebuffer.hpp @@ -194,7 +194,7 @@ namespace nexo::renderer { */ virtual void resize(unsigned int width, unsigned int height) = 0; - virtual const glm::vec2 getSize() const = 0; + [[nodiscard]] virtual const glm::vec2 getSize() const = 0; virtual void getPixelWrapper(unsigned int attachementIndex, int x, int y, void *result, const std::type_info &ti) const = 0; @@ -269,7 +269,7 @@ namespace nexo::renderer { */ [[nodiscard]] virtual unsigned int getColorAttachmentId(unsigned int index = 0) const = 0; - virtual unsigned int getDepthAttachmentId() const = 0; + [[nodiscard]] virtual unsigned int getDepthAttachmentId() const = 0; /** * @brief Creates a framebuffer based on the provided specifications. diff --git a/engine/src/renderer/RenderCommand.hpp b/engine/src/renderer/RenderCommand.hpp index 5ce02c5e1..da2ee2dbc 100644 --- a/engine/src/renderer/RenderCommand.hpp +++ b/engine/src/renderer/RenderCommand.hpp @@ -74,7 +74,7 @@ namespace nexo::renderer { * Usage: * - Call this method whenever the window is resized to adjust the rendering area. */ - static void setViewport(unsigned int x, unsigned int y, unsigned int width, unsigned int height) { _rendererApi->setViewport(x, y, width, height); }; + 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. @@ -117,27 +117,27 @@ namespace nexo::renderer { * Usage: * - Use this method to draw meshes or primitives with indexed geometry. */ - static void drawIndexed(const std::shared_ptr &vertexArray, unsigned int indexCount = 0) + static void drawIndexed(const std::shared_ptr &vertexArray, const unsigned int indexCount = 0) { _rendererApi->drawIndexed(vertexArray, indexCount); } - static void drawUnIndexed(unsigned int verticesCount) + static void drawUnIndexed(const unsigned int verticesCount) { _rendererApi->drawUnIndexed(verticesCount); } - static void setDepthTest(bool enable) + static void setDepthTest(const bool enable) { _rendererApi->setDepthTest(enable); } - static void setDepthMask(bool enable) + static void setDepthMask(const bool enable) { _rendererApi->setDepthMask(enable); } - static void setDepthFunc(unsigned int func) + static void setDepthFunc(const unsigned int func) { _rendererApi->setDepthFunc(func); } @@ -155,7 +155,7 @@ namespace nexo::renderer { * - 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(bool enable) { _rendererApi->setStencilTest(enable); } + static void setStencilTest(const bool enable) { _rendererApi->setStencilTest(enable); } /** * @brief Sets the stencil mask that controls which bits of the stencil buffer are updated. @@ -169,7 +169,7 @@ namespace nexo::renderer { * Usage: * - Set a specific mask before performing stencil operations to control which bits are affected. */ - static void setStencilMask(unsigned int mask) { _rendererApi->setStencilMask(mask); } + static void setStencilMask(const unsigned int mask) { _rendererApi->setStencilMask(mask); } /** * @brief Configures the stencil function used for stencil testing. @@ -185,7 +185,8 @@ namespace nexo::renderer { * Usage: * - Configure before performing operations that rely on specific stencil buffer values. */ - static void setStencilFunc(unsigned int func, int ref, unsigned int mask) { + static void setStencilFunc(const unsigned int func, const int ref, const unsigned int mask) + { _rendererApi->setStencilFunc(func, ref, mask); } @@ -204,7 +205,8 @@ namespace nexo::renderer { * Usage: * - Set before performing complex stencil operations like object outlining or shadow volumes. */ - static void setStencilOp(unsigned int sfail, unsigned int dpfail, unsigned int dppass) { + static void setStencilOp(const unsigned int sfail, const unsigned int dpfail, const unsigned int dppass) + { _rendererApi->setStencilOp(sfail, dpfail, dppass); } diff --git a/engine/src/renderer/Renderer.cpp b/engine/src/renderer/Renderer.cpp index 51fdb70cb..40ee26a16 100644 --- a/engine/src/renderer/Renderer.cpp +++ b/engine/src/renderer/Renderer.cpp @@ -11,19 +11,20 @@ // Description: Source file for renderer class // /////////////////////////////////////////////////////////////////////////////// + #include "Renderer.hpp" -#include "Renderer2D.hpp" +#include "RenderCommand.hpp" namespace nexo::renderer { - NxRenderer::NxSceneData *NxRenderer::_sceneData = new NxRenderer::NxSceneData; + NxRenderer::NxSceneData *NxRenderer::_sceneData = new NxSceneData; void NxRenderer::init() { NxRenderCommand::init(); } - void NxRenderer::onWindowResize(unsigned int width, unsigned int height) + void NxRenderer::onWindowResize(const unsigned int width, const unsigned int height) { NxRenderCommand::setViewport(0, 0, width, height); } diff --git a/engine/src/renderer/Renderer.hpp b/engine/src/renderer/Renderer.hpp index d6f8bbeef..07e28b0c5 100644 --- a/engine/src/renderer/Renderer.hpp +++ b/engine/src/renderer/Renderer.hpp @@ -13,9 +13,6 @@ /////////////////////////////////////////////////////////////////////////////// #pragma once -#include "RenderCommand.hpp" -#include "Shader.hpp" - #include namespace nexo::renderer { diff --git a/engine/src/renderer/Renderer3D.cpp b/engine/src/renderer/Renderer3D.cpp index cd14ebf13..e74a55bf2 100644 --- a/engine/src/renderer/Renderer3D.cpp +++ b/engine/src/renderer/Renderer3D.cpp @@ -58,13 +58,13 @@ namespace nexo::renderer { for (int i = 0; i < static_cast(NxRenderer3DStorage::maxTextureSlots); ++i) samplers[i] = i; - auto phong = m_storage->shaderLibrary.load("Phong", Path::resolvePathRelativeToExe( + const auto phong = m_storage->shaderLibrary.load("Phong", Path::resolvePathRelativeToExe( "../resources/shaders/phong.glsl").string()); m_storage->shaderLibrary.load("Outline pulse flat", Path::resolvePathRelativeToExe( "../resources/shaders/outline_pulse_flat.glsl").string()); - auto outlinePulseTransparentFlat = m_storage->shaderLibrary.load("Outline pulse transparent flat", Path::resolvePathRelativeToExe( + const auto outlinePulseTransparentFlat = m_storage->shaderLibrary.load("Outline pulse transparent flat", Path::resolvePathRelativeToExe( "../resources/shaders/outline_pulse_transparent_flat.glsl").string()); - auto albedoUnshadedTransparent = m_storage->shaderLibrary.load("Albedo unshaded transparent", Path::resolvePathRelativeToExe( + const auto albedoUnshadedTransparent = m_storage->shaderLibrary.load("Albedo unshaded transparent", Path::resolvePathRelativeToExe( "../resources/shaders/albedo_unshaded_transparent.glsl").string()); m_storage->shaderLibrary.load("Grid shader", Path::resolvePathRelativeToExe( "../resources/shaders/grid_shader.glsl").string()); @@ -171,14 +171,14 @@ namespace nexo::renderer { { if (*m_storage->textureSlots[i].get() == *texture) { - textureIndex = i; + textureIndex = static_cast(i); break; } } if (textureIndex == 0) { - textureIndex = m_storage->textureSlotIndex; + textureIndex = static_cast(m_storage->textureSlotIndex); m_storage->textureSlots[m_storage->textureSlotIndex] = texture; m_storage->textureSlotIndex++; } @@ -186,7 +186,7 @@ namespace nexo::renderer { return textureIndex; } - void NxRenderer3D::setMaterialUniforms(const renderer::NxIndexedMaterial& material) const + void NxRenderer3D::setMaterialUniforms(const NxIndexedMaterial& material) const { if (!m_storage) THROW_EXCEPTION(NxRendererNotInitialized, NxRendererType::RENDERER_3D); diff --git a/engine/src/renderer/Renderer3D.hpp b/engine/src/renderer/Renderer3D.hpp index 30a051fd2..5be594349 100644 --- a/engine/src/renderer/Renderer3D.hpp +++ b/engine/src/renderer/Renderer3D.hpp @@ -20,7 +20,6 @@ #include #include -#include namespace nexo::renderer { @@ -64,7 +63,7 @@ namespace nexo::renderer { std::shared_ptr roughnessMap = nullptr; std::shared_ptr emissiveMap = nullptr; - std::string shader = ""; + std::string shader; }; //TODO: Add stats for the meshes @@ -324,9 +323,9 @@ namespace nexo::renderer { */ void drawMesh(const std::vector& vertices, const std::vector& indices, const std::shared_ptr& texture, int entityID = -1) const; - void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& size, const renderer::NxMaterial& material, int entityID = -1) const; - void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& size, const renderer::NxMaterial& material, int entityID = -1) const; - void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::mat4& transform, const renderer::NxMaterial& material, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& size, const NxMaterial& material, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& size, const NxMaterial& material, int entityID = -1) const; + void drawMesh(const std::vector& vertices, const std::vector& indices, const glm::mat4& transform, const NxMaterial& material, int entityID = -1) const; void drawBillboard(const glm::vec3& position, const glm::vec2& size, const glm::vec4& color, int entityID) const; void drawBillboard(const glm::vec3& position, const glm::vec2& size, const NxMaterial& material, int entityID) const; @@ -351,9 +350,9 @@ namespace nexo::renderer { */ [[nodiscard]] NxRenderer3DStats getStats() const; - std::shared_ptr &getShader() const {return m_storage->currentSceneShader;}; + [[nodiscard]] std::shared_ptr &getShader() const {return m_storage->currentSceneShader;}; - std::shared_ptr getInternalStorage() const { return m_storage; }; + [[nodiscard]] std::shared_ptr getInternalStorage() const { return m_storage; }; private: std::shared_ptr m_storage; bool m_renderingScene = false; @@ -389,7 +388,7 @@ namespace nexo::renderer { * * @throws NxRendererNotInitialized if the renderer is not initialized. */ - void setMaterialUniforms(const renderer::NxIndexedMaterial& material) const; + void setMaterialUniforms(const NxIndexedMaterial& material) const; }; } diff --git a/engine/src/renderer/Shader.hpp b/engine/src/renderer/Shader.hpp index bf2272a82..ec3a9bf0b 100644 --- a/engine/src/renderer/Shader.hpp +++ b/engine/src/renderer/Shader.hpp @@ -138,12 +138,12 @@ namespace nexo::renderer { virtual bool setUniformInt(const std::string &name, int value) const = 0; virtual bool setUniformIntArray(const std::string &name, const int *values, unsigned int count) const = 0; - virtual bool setUniformFloat(const NxShaderUniforms uniform, const float value) const = 0; - virtual bool setUniformFloat3(const NxShaderUniforms uniform, const glm::vec3 &values) const = 0; - virtual bool setUniformFloat4(const NxShaderUniforms uniform, const glm::vec4 &values) const = 0; - virtual bool setUniformMatrix(const NxShaderUniforms uniform, const glm::mat4 &matrix) const = 0; - virtual bool setUniformInt(const NxShaderUniforms uniform, int value) const = 0; - virtual bool setUniformIntArray(const NxShaderUniforms uniform, const int *values, unsigned int count) const = 0; + virtual bool setUniformFloat(NxShaderUniforms uniform, float value) const = 0; + virtual bool setUniformFloat3(NxShaderUniforms uniform, const glm::vec3 &values) const = 0; + virtual bool setUniformFloat4(NxShaderUniforms uniform, const glm::vec4 &values) const = 0; + virtual bool setUniformMatrix(NxShaderUniforms uniform, const glm::mat4 &matrix) const = 0; + virtual bool setUniformInt(NxShaderUniforms uniform, int value) const = 0; + virtual bool setUniformIntArray(NxShaderUniforms uniform, const int *values, unsigned int count) const = 0; void addStorageBuffer(const std::shared_ptr &buffer); void setStorageBufferData(unsigned int index, void *data, unsigned int size); diff --git a/engine/src/renderer/Texture.cpp b/engine/src/renderer/Texture.cpp index d718f2c68..9549dc75f 100644 --- a/engine/src/renderer/Texture.cpp +++ b/engine/src/renderer/Texture.cpp @@ -32,9 +32,9 @@ namespace nexo::renderer { return NxTextureFormat::INVALID; } - void NxTextureFormatConvertArgb8ToRgba8(uint8_t *bytes, size_t size) + void NxTextureFormatConvertArgb8ToRgba8(uint8_t *bytes, const size_t size) { - uint32_t *pixels = reinterpret_cast(bytes); + auto *pixels = reinterpret_cast(bytes); const size_t width = size / 4; for (size_t i = 0; i < width; ++i) { diff --git a/engine/src/renderer/Texture.hpp b/engine/src/renderer/Texture.hpp index a62297e83..b78016d94 100644 --- a/engine/src/renderer/Texture.hpp +++ b/engine/src/renderer/Texture.hpp @@ -41,7 +41,7 @@ namespace nexo::renderer { [[nodiscard]] virtual unsigned int getWidth() const = 0; [[nodiscard]] virtual unsigned int getHeight() const = 0; - virtual unsigned int getMaxTextureSize() const = 0; + [[nodiscard]] virtual unsigned int getMaxTextureSize() const = 0; [[nodiscard]] virtual unsigned int getId() const = 0; diff --git a/engine/src/renderer/VertexArray.hpp b/engine/src/renderer/VertexArray.hpp index 227ea6f15..a0efef9bc 100644 --- a/engine/src/renderer/VertexArray.hpp +++ b/engine/src/renderer/VertexArray.hpp @@ -46,7 +46,7 @@ namespace nexo::renderer { [[nodiscard]] virtual const std::vector> &getVertexBuffers() const = 0; [[nodiscard]] virtual const std::shared_ptr &getIndexBuffer() const = 0; - virtual unsigned int getId() const = 0; + [[nodiscard]] virtual unsigned int getId() const = 0; }; /** diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp index 988eda37a..bda0b78e6 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp @@ -19,7 +19,6 @@ #include #include - namespace nexo::renderer { static constexpr unsigned int sMaxFramebufferSize = 8192; diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp index 8facdee19..66769ea4d 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp @@ -138,7 +138,7 @@ namespace nexo::renderer { */ void resize(unsigned int width, unsigned int height) override; - const glm::vec2 getSize() const override; + [[nodiscard]] const glm::vec2 getSize() const override; /** diff --git a/engine/src/renderer/opengl/OpenGlRendererApi.cpp b/engine/src/renderer/opengl/OpenGlRendererApi.cpp index 20bfb9036..50eaa31f1 100644 --- a/engine/src/renderer/opengl/OpenGlRendererApi.cpp +++ b/engine/src/renderer/opengl/OpenGlRendererApi.cpp @@ -11,6 +11,7 @@ // Description: Source file for renderer api class // /////////////////////////////////////////////////////////////////////////////// + #include #include @@ -81,7 +82,7 @@ namespace nexo::renderer { glClearDepth(depth); } - void NxOpenGlRendererApi::setDepthTest(bool enable) + void NxOpenGlRendererApi::setDepthTest(const bool enable) { if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); @@ -91,14 +92,14 @@ namespace nexo::renderer { glDisable(GL_DEPTH_TEST); } - void NxOpenGlRendererApi::setDepthFunc(unsigned int func) + void NxOpenGlRendererApi::setDepthFunc(const unsigned int func) { if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glDepthFunc(func); } - void NxOpenGlRendererApi::setDepthMask(bool enable) + void NxOpenGlRendererApi::setDepthMask(const bool enable) { if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); @@ -118,14 +119,14 @@ namespace nexo::renderer { glDrawElements(GL_TRIANGLES, static_cast(count), GL_UNSIGNED_INT, nullptr); } - void NxOpenGlRendererApi::drawUnIndexed(unsigned int verticesCount) + void NxOpenGlRendererApi::drawUnIndexed(const unsigned int verticesCount) { if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); - glDrawArrays(GL_TRIANGLES, 0, verticesCount); + glDrawArrays(GL_TRIANGLES, 0, static_cast(verticesCount)); } - void NxOpenGlRendererApi::setStencilTest(bool enable) + void NxOpenGlRendererApi::setStencilTest(const bool enable) { if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); @@ -135,21 +136,21 @@ namespace nexo::renderer { glDisable(GL_STENCIL_TEST); } - void NxOpenGlRendererApi::setStencilMask(unsigned int mask) + void NxOpenGlRendererApi::setStencilMask(const unsigned int mask) { if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glStencilMask(mask); } - void NxOpenGlRendererApi::setStencilFunc(unsigned int func, int ref, unsigned int mask) + void NxOpenGlRendererApi::setStencilFunc(const unsigned int func, const int ref, const unsigned int mask) { if (!m_initialized) THROW_EXCEPTION(NxGraphicsApiNotInitialized, "OPENGL"); glStencilFunc(func, ref, mask); } - void NxOpenGlRendererApi::setStencilOp(unsigned int sfail, unsigned int dpfail, 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"); diff --git a/engine/src/renderer/opengl/OpenGlShader.hpp b/engine/src/renderer/opengl/OpenGlShader.hpp index 9996a6e11..4422bc41b 100644 --- a/engine/src/renderer/opengl/OpenGlShader.hpp +++ b/engine/src/renderer/opengl/OpenGlShader.hpp @@ -45,7 +45,7 @@ namespace nexo::renderer { * - `NxFileNotFoundException` if the file cannot be found. * - `NxShaderCreationFailed` if shader compilation fails. */ - NxOpenGlShader(const std::string &path); + explicit NxOpenGlShader(const std::string &path); NxOpenGlShader(std::string name, const std::string_view &vertexSource, const std::string_view &fragmentSource); ~NxOpenGlShader() override; @@ -57,7 +57,7 @@ namespace nexo::renderer { void bind() const override; void unbind() const override; - bool setUniformFloat(const std::string &name, const float value) 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; @@ -66,12 +66,12 @@ namespace nexo::renderer { 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(const NxShaderUniforms uniform, const float value) const override; - bool setUniformFloat3(const NxShaderUniforms uniform, const glm::vec3 &values) const override; - bool setUniformFloat4(const NxShaderUniforms uniform, const glm::vec4 &values) const override; - bool setUniformMatrix(const NxShaderUniforms uniform, const glm::mat4 &matrix) const override; - bool setUniformInt(const NxShaderUniforms uniform, int value) const override; - bool setUniformIntArray(const NxShaderUniforms uniform, 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; diff --git a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp index 5b101e801..712f19c0b 100644 --- a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.cpp @@ -16,7 +16,7 @@ #include "OpenGlShaderStorageBuffer.hpp" namespace nexo::renderer { - NxOpenGlShaderStorageBuffer::NxOpenGlShaderStorageBuffer(unsigned int size) + NxOpenGlShaderStorageBuffer::NxOpenGlShaderStorageBuffer(const unsigned int size) { glCreateBuffers(1, &m_id); glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); @@ -29,7 +29,7 @@ namespace nexo::renderer { glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); } - void NxOpenGlShaderStorageBuffer::bindBase(unsigned int bindingLocation) const + void NxOpenGlShaderStorageBuffer::bindBase(const unsigned int bindingLocation) const { glBindBufferBase(GL_SHADER_STORAGE_BUFFER, bindingLocation, m_id); } @@ -39,7 +39,7 @@ namespace nexo::renderer { glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); } - void NxOpenGlShaderStorageBuffer::setData(void* data, unsigned int size) + void NxOpenGlShaderStorageBuffer::setData(void* data, const unsigned int size) { glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_id); glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, size, data); diff --git a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp index 2e7a9e69d..c70259ef8 100644 --- a/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlShaderStorageBuffer.hpp @@ -16,7 +16,7 @@ #include "renderer/ShaderStorageBuffer.hpp" namespace nexo::renderer { - class NxOpenGlShaderStorageBuffer : public NxShaderStorageBuffer { + class NxOpenGlShaderStorageBuffer final : public NxShaderStorageBuffer { public: explicit NxOpenGlShaderStorageBuffer(unsigned int size); ~NxOpenGlShaderStorageBuffer() override = default; @@ -30,6 +30,6 @@ namespace nexo::renderer { [[nodiscard]] unsigned int getId() const override { return m_id; }; private: - unsigned int m_id; + unsigned int m_id{}; }; } diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index 4425844d9..02211f698 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -74,7 +74,7 @@ namespace nexo::renderer { try { ingestDataFromStb(data, width, height, channels, path); - } catch (const Exception& e) { + } catch (const Exception&) { stbi_image_free(data); throw; } @@ -86,20 +86,20 @@ namespace nexo::renderer { glDeleteTextures(1, &m_id); } - NxOpenGlTexture2D::NxOpenGlTexture2D(const uint8_t* buffer, unsigned int len) + NxOpenGlTexture2D::NxOpenGlTexture2D(const uint8_t* buffer, const unsigned int len) { 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); - stbi_uc *data = stbi_load_from_memory(buffer, len, &width, &height, &channels, 0); + stbi_uc *data = stbi_load_from_memory(buffer, static_cast(len), &width, &height, &channels, 0); if (!data) THROW_EXCEPTION(NxTextureUnsupportedFormat, "OPENGL", channels, "(buffer)"); try { ingestDataFromStb(data, width, height, channels, "(buffer)"); - } catch (const Exception& e) { + } catch (const Exception&) { stbi_image_free(data); throw; } @@ -123,7 +123,7 @@ namespace nexo::renderer { glBindTexture(GL_TEXTURE_2D, 0); } - void NxOpenGlTexture2D::ingestDataFromStb(uint8_t* data, int width, int height, int channels, + void NxOpenGlTexture2D::ingestDataFromStb(uint8_t* data, const int width, const int height, const int channels, const std::string& debugPath) { GLint internalFormat = 0; diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.hpp b/engine/src/renderer/opengl/OpenGlTexture2D.hpp index 1bd99a52b..fcac30bb8 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.hpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.hpp @@ -135,7 +135,7 @@ namespace nexo::renderer { * * @return The maximum texture size in pixels. */ - unsigned int getMaxTextureSize() const override; + [[nodiscard]] unsigned int getMaxTextureSize() const override; [[nodiscard]] unsigned int getId() const override {return m_id;}; @@ -223,10 +223,10 @@ namespace nexo::renderer { 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_width{}; + unsigned int m_height{}; unsigned int m_id{}; - GLint m_internalFormat; - GLenum m_dataFormat; + GLint m_internalFormat{}; + GLenum m_dataFormat{}; }; } diff --git a/engine/src/renderer/opengl/OpenGlVertexArray.cpp b/engine/src/renderer/opengl/OpenGlVertexArray.cpp index a4f4673d9..d5a2c61cc 100644 --- a/engine/src/renderer/opengl/OpenGlVertexArray.cpp +++ b/engine/src/renderer/opengl/OpenGlVertexArray.cpp @@ -61,7 +61,6 @@ namespace nexo::renderer { case NxShaderDataType::BOOL: return true; default: return false; } - return false; } NxOpenGlVertexArray::NxOpenGlVertexArray() diff --git a/engine/src/renderer/opengl/OpenGlVertexArray.hpp b/engine/src/renderer/opengl/OpenGlVertexArray.hpp index 43a4befb7..051907575 100644 --- a/engine/src/renderer/opengl/OpenGlVertexArray.hpp +++ b/engine/src/renderer/opengl/OpenGlVertexArray.hpp @@ -83,7 +83,7 @@ namespace nexo::renderer { [[nodiscard]] const std::vector> &getVertexBuffers() const override; [[nodiscard]] const std::shared_ptr &getIndexBuffer() const override; - unsigned int getId() const override; + [[nodiscard]] unsigned int getId() const override; private: std::vector> _vertexBuffers; std::shared_ptr _indexBuffer; diff --git a/engine/src/renderer/primitives/Billboard.cpp b/engine/src/renderer/primitives/Billboard.cpp index dee9a4029..c561f4be1 100644 --- a/engine/src/renderer/primitives/Billboard.cpp +++ b/engine/src/renderer/primitives/Billboard.cpp @@ -21,7 +21,6 @@ #define GLM_ENABLE_EXPERIMENTAL #include #include -#include namespace nexo::renderer { // Quad vertices for a 1x1 billboard centered at origin @@ -100,30 +99,27 @@ namespace nexo::renderer { glm::vec3 right = glm::normalize(glm::cross(cameraUp, look)); glm::vec3 up = glm::cross(look, right); - return glm::mat4( - 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) - ); - } else { - glm::vec3 right = glm::normalize(glm::cross(cameraUp, look)); - 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) + }; + } + const glm::vec3 right = glm::normalize(glm::cross(cameraUp, look)); + const glm::vec3 up = glm::cross(look, right); - return glm::mat4( - glm::vec4(right, 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) - ); - } + }; } void NxRenderer3D::drawBillboard( const glm::vec3& position, const glm::vec2& size, const glm::vec4& color, - int entityID) const + const int entityID) const { if (!m_renderingScene) { @@ -131,9 +127,9 @@ namespace nexo::renderer { "Renderer not rendering a scene, make sure to call beginScene first"); } - glm::vec3 cameraPos = m_storage->cameraPosition; + const glm::vec3 cameraPos = m_storage->cameraPosition; - glm::mat4 billboardRotation = calculateBillboardRotation(position, cameraPos); + const glm::mat4 billboardRotation = calculateBillboardRotation(position, cameraPos); const glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * billboardRotation * @@ -141,7 +137,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::NxIndexedMaterial mat; + NxIndexedMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -163,7 +159,7 @@ namespace nexo::renderer { m_storage->vertexBufferPtr++; } - std::ranges::for_each(indices, [this](unsigned int index) { + std::ranges::for_each(indices, [this](const unsigned int index) { m_storage->indexBufferBase[m_storage->indexCount++] = index; }); } @@ -172,7 +168,7 @@ namespace nexo::renderer { const glm::vec3& position, const glm::vec2& size, const NxMaterial& material, - int entityID) const + const int entityID) const { if (!m_renderingScene) { @@ -180,9 +176,9 @@ namespace nexo::renderer { "Renderer not rendering a scene, make sure to call beginScene first"); } - glm::vec3 cameraPos = m_storage->cameraPosition; + const glm::vec3 cameraPos = m_storage->cameraPosition; - glm::mat4 billboardRotation = calculateBillboardRotation(position, cameraPos); + const glm::mat4 billboardRotation = calculateBillboardRotation(position, cameraPos); const glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * billboardRotation * @@ -190,7 +186,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::NxIndexedMaterial mat; + NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = getTextureIndex(material.albedoTexture); mat.specularColor = material.specularColor; @@ -215,7 +211,7 @@ namespace nexo::renderer { m_storage->vertexBufferPtr++; } - std::ranges::for_each(indices, [this](unsigned int index) { + std::ranges::for_each(indices, [this](const unsigned int index) { m_storage->indexBufferBase[m_storage->indexCount++] = index; }); } diff --git a/engine/src/renderer/primitives/Cube.cpp b/engine/src/renderer/primitives/Cube.cpp index c1c020510..8ce704234 100644 --- a/engine/src/renderer/primitives/Cube.cpp +++ b/engine/src/renderer/primitives/Cube.cpp @@ -130,7 +130,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::NxIndexedMaterial mat; + NxIndexedMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -156,7 +156,7 @@ namespace nexo::renderer { } // Index data - std::ranges::for_each(indices, [this](unsigned int index) { + std::ranges::for_each(indices, [this](const unsigned int index) { m_storage->indexBufferBase[m_storage->indexCount++] = index; }); @@ -164,7 +164,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void NxRenderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3 &rotation, const glm::vec4& color, int entityID) const + void NxRenderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3 &rotation, const glm::vec4& color, const int entityID) const { if (!m_renderingScene) { @@ -181,7 +181,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::NxIndexedMaterial mat; + NxIndexedMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -215,7 +215,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void NxRenderer3D::drawCube(const glm::mat4& transform, const glm::vec4& color, int entityID) const + void NxRenderer3D::drawCube(const glm::mat4& transform, const glm::vec4& color, const int entityID) const { if (!m_renderingScene) { @@ -226,7 +226,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::NxIndexedMaterial mat; + NxIndexedMaterial mat; mat.albedoColor = color; setMaterialUniforms(mat); @@ -260,7 +260,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void NxRenderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const NxMaterial& material, int entityID) const + void NxRenderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const NxMaterial& material, const int entityID) const { if (!m_renderingScene) { @@ -274,7 +274,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::NxIndexedMaterial mat; + NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; @@ -312,7 +312,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void NxRenderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const NxMaterial& material, int entityID) const + void NxRenderer3D::drawCube(const glm::vec3& position, const glm::vec3& size, const glm::vec3& rotation, const NxMaterial& material, const int entityID) const { if (!m_renderingScene) { @@ -329,7 +329,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::NxIndexedMaterial mat; + NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; @@ -367,7 +367,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void NxRenderer3D::drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const NxMaterial& material, int entityID) const + void NxRenderer3D::drawCube(const glm::vec3 &position, const glm::vec3 &size, const glm::quat &rotation, const NxMaterial& material, const int entityID) const { if (!m_renderingScene) { @@ -384,7 +384,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::NxIndexedMaterial mat; + NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; @@ -422,7 +422,7 @@ namespace nexo::renderer { m_storage->stats.cubeCount++; } - void NxRenderer3D::drawCube(const glm::mat4& transform, const NxMaterial& material, int entityID) const + void NxRenderer3D::drawCube(const glm::mat4& transform, const NxMaterial& material, const int entityID) const { if (!m_renderingScene) { @@ -432,7 +432,7 @@ namespace nexo::renderer { m_storage->currentSceneShader->setUniformMatrix("uMatModel", transform); - renderer::NxIndexedMaterial mat; + NxIndexedMaterial mat; mat.albedoColor = material.albedoColor; mat.albedoTexIndex = material.albedoTexture ? getTextureIndex(material.albedoTexture) : 0; mat.specularColor = material.specularColor; diff --git a/engine/src/systems/RenderSystem.cpp b/engine/src/systems/RenderSystem.cpp index d980e0979..7d01fc99e 100644 --- a/engine/src/systems/RenderSystem.cpp +++ b/engine/src/systems/RenderSystem.cpp @@ -31,7 +31,6 @@ #include - namespace nexo::system { RenderSystem::RenderSystem() @@ -57,10 +56,10 @@ namespace nexo::system { 1.0f, 1.0f, 0.0f, 1.0f, 1.0f }; - auto quadVB = renderer::createVertexBuffer(sizeof(quadVertices)); + const auto quadVB = renderer::createVertexBuffer(sizeof(quadVertices)); quadVB->setData(quadVertices, sizeof(quadVertices)); - renderer::NxBufferLayout quadLayout = { + const renderer::NxBufferLayout quadLayout = { {renderer::NxShaderDataType::FLOAT3, "aPosition"}, {renderer::NxShaderDataType::FLOAT2, "aTexCoord"} }; @@ -93,8 +92,8 @@ namespace nexo::system { if (!lastShader) return; shader->setUniformFloat3("uAmbientLight", lightContext.ambientLight); - shader->setUniformInt("uNumPointLights", lightContext.pointLightCount); - shader->setUniformInt("uNumSpotLights", lightContext.spotLightCount); + shader->setUniformInt("uNumPointLights", static_cast(lightContext.pointLightCount)); + shader->setUniformInt("uNumSpotLights", static_cast(lightContext.spotLightCount)); const auto &directionalLight = lightContext.dirLight; shader->setUniformFloat3("uDirLight.direction", directionalLight.direction); @@ -185,7 +184,7 @@ namespace nexo::system { const glm::vec3 rayDir = math::projectRayToWorld( framebufferPos.x, framebufferPos.y, camera.viewProjectionMatrix, camera.cameraPosition, - renderTargetSize.x, renderTargetSize.y + static_cast(renderTargetSize.x), static_cast(renderTargetSize.y) ); // Calculate intersection with y=0 plane (grid plane) @@ -200,7 +199,7 @@ namespace nexo::system { const glm::vec3 rayDir = math::projectRayToWorld( globalMousePos.x, globalMousePos.y, camera.viewProjectionMatrix, camera.cameraPosition, - renderTargetSize.x, renderTargetSize.y + static_cast(renderTargetSize.x), static_cast(renderTargetSize.y) ); if (rayDir.y != 0.0f) { @@ -230,7 +229,8 @@ namespace nexo::system { const components::CameraContext &camera, const components::RenderComponent &renderComponent, const components::TransformComponent &transformComponent - ) { + ) const + { if (m_maskFramebuffer->getSize().x != camera.renderTarget->getSize().x || m_maskFramebuffer->getSize().y != camera.renderTarget->getSize().y) { m_maskFramebuffer->resize( @@ -264,9 +264,9 @@ namespace nexo::system { // Step 3: Draw full-screen quad with outline post-process shader renderer::NxRenderCommand::setDepthMask(false); renderContext.renderer3D.beginScene(camera.viewProjectionMatrix, camera.cameraPosition, "Outline pulse flat"); - auto outlineShader = renderContext.renderer3D.getShader(); + const auto outlineShader = renderContext.renderer3D.getShader(); outlineShader->bind(); - unsigned int maskTexture = m_maskFramebuffer->getColorAttachmentId(0); + const unsigned int maskTexture = m_maskFramebuffer->getColorAttachmentId(0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, maskTexture); outlineShader->setUniformInt("uMaskTexture", 0); @@ -321,7 +321,7 @@ namespace nexo::system { renderContext.cameras.pop(); continue; } - nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneName)); + Logger::resetOnce(NEXO_LOG_ONCE_KEY("Nothing to render in scene {}, skipping", sceneName)); if (sceneType == SceneType::EDITOR && renderContext.gridParams.enabled) renderGrid(camera, renderContext); diff --git a/engine/src/systems/RenderSystem.hpp b/engine/src/systems/RenderSystem.hpp index 26c843d9c..8fef0509c 100644 --- a/engine/src/systems/RenderSystem.hpp +++ b/engine/src/systems/RenderSystem.hpp @@ -52,14 +52,14 @@ namespace nexo::system { void update(); private: - void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext); - void renderGrid(const components::CameraContext &camera, components::RenderContext &renderContext); + static void setupLights(const std::shared_ptr& shader, const components::LightContext& lightContext); + static void renderGrid(const components::CameraContext &camera, components::RenderContext &renderContext); void renderOutline( components::RenderContext &renderContext, const components::CameraContext &camera, const components::RenderComponent &renderComponent, const components::TransformComponent &transformComponent - ); + ) const; std::shared_ptr m_fullscreenQuad; std::shared_ptr m_maskFramebuffer; }; diff --git a/engine/src/systems/lights/AmbientLightSystem.cpp b/engine/src/systems/lights/AmbientLightSystem.cpp index f992bae95..eb77fc492 100644 --- a/engine/src/systems/lights/AmbientLightSystem.cpp +++ b/engine/src/systems/lights/AmbientLightSystem.cpp @@ -40,12 +40,12 @@ namespace nexo::system { LOG_ONCE(NEXO_WARN, "No ambient light found in scene {}, skipping", sceneName); return; } - nexo::Logger::resetOnce(NEXO_LOG_ONCE_KEY("No ambient light found in scene {}, skipping", sceneName)); + 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); else - nexo::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; } From 36ffae1ef3195248128e9682ba978704737de604 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 2 May 2025 08:16:43 +0900 Subject: [PATCH 344/450] fix(undo-redo): fix compile windows --- engine/src/assets/AssetImporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/assets/AssetImporter.cpp b/engine/src/assets/AssetImporter.cpp index 9c70ca446..f651bc456 100644 --- a/engine/src/assets/AssetImporter.cpp +++ b/engine/src/assets/AssetImporter.cpp @@ -100,7 +100,7 @@ namespace nexo::assets { getImportersForType(const std::type_index& typeIdx) const { if (const auto it = m_importers.find(typeIdx) ; it == m_importers.end()) { - static constexpr std::vector empty; + static std::vector empty; return empty; } return m_importers.at(typeIdx); From 89da3d9ce1463e3073a805751fcaad2ac88946fb Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Fri, 2 May 2025 09:01:31 +0900 Subject: [PATCH 345/450] fix(undo-redo): fix sonar issues --- .../ConsoleWindow/ConsoleWindow.hpp | 26 ++++++++++++------- .../src/DocumentWindows/ConsoleWindow/Log.cpp | 2 +- .../DocumentWindows/ConsoleWindow/Show.cpp | 2 +- .../DocumentWindows/ConsoleWindow/Utils.cpp | 14 +++++----- .../SceneTreeWindow/Rename.cpp | 6 +++-- .../SceneTreeWindow/SceneCreation.cpp | 8 +++--- .../SceneTreeWindow/Selection.cpp | 9 ++++--- .../SceneTreeWindow/Shortcuts.cpp | 20 +++++++------- .../SceneTreeWindow/Update.cpp | 12 ++++----- editor/src/ImNexo/Guard.hpp | 4 +-- 10 files changed, 55 insertions(+), 48 deletions(-) diff --git a/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp index fc2ae28cb..3d25cb82b 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp +++ b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp @@ -160,19 +160,15 @@ namespace nexo::editor { * @param fmt Format string similar to printf * @param args Arguments for the format string */ - // Add this implementation to your ConsoleWindow.hpp file template - void addLog(const char *fmt, Args &&... args) + void addLog(std::format_string fmt, Args&&... args) { try { - char buffer[1024]; - int result = snprintf(buffer, sizeof(buffer), fmt, std::forward(args)...); - if (result < 0) - return; + std::string formattedString = std::format(fmt, std::forward(args)...); LogMessage newMessage; newMessage.verbosity = loguru::Verbosity_1; - newMessage.message = std::string(buffer); + newMessage.message = formattedString; newMessage.prefix = ""; m_logs.push_back(newMessage); } catch (const std::exception &e) { @@ -180,9 +176,19 @@ namespace nexo::editor { newMessage.verbosity = loguru::Verbosity_ERROR; char errorBuffer[1024]; - snprintf(errorBuffer, sizeof(errorBuffer), "Error formatting log message: %s", e.what()); - newMessage.message = std::string(errorBuffer); + // 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); } @@ -206,7 +212,7 @@ namespace nexo::editor { * Writes any logs in the export buffer to the configured log file, * helping to prevent memory buildup from excessive logging. */ - void exportLogsBuffered(); + void exportLogsBuffered() const; /** * @brief Displays the popup for configuring verbosity settings. diff --git a/editor/src/DocumentWindows/ConsoleWindow/Log.cpp b/editor/src/DocumentWindows/ConsoleWindow/Log.cpp index 5810d3a98..e2af09fce 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Log.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Log.cpp @@ -46,7 +46,7 @@ namespace nexo::editor { m_logs.clear(); } - void ConsoleWindow::exportLogsBuffered() + void ConsoleWindow::exportLogsBuffered() const { if (!m_exportLog) return; diff --git a/editor/src/DocumentWindows/ConsoleWindow/Show.cpp b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp index 7aec9c0ea..bec5ba2dc 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Show.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp @@ -66,7 +66,7 @@ namespace nexo::editor { void ConsoleWindow::executeCommand(const char *commandLine) { m_commands.emplace_back(commandLine); - addLog("%s", commandLine); + addLog("{}", commandLine); } void ConsoleWindow::calcLogPadding() diff --git a/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp index b755fa355..9a517ce9f 100644 --- a/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp +++ b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include "ConsoleWindow.hpp" +#include namespace nexo::editor { /** @@ -104,14 +105,13 @@ namespace nexo::editor { std::string generateLogFilePath() { - auto now = std::time(nullptr); - auto tm = *std::localtime(&now); + using namespace std::chrono; - std::ostringstream ss; - ss << "../logs/NEXO-"; - ss << std::put_time(&tm, "%Y-%m-%d-%H%M%S"); - ss << ".log"; + // Truncate to seconds precision + auto now = floor(system_clock::now()); + zoned_time local_zoned{ current_zone(), now }; - return ss.str(); + std::string ts = std::format("{:%Y%m%d_%H%M%S}", local_zoned); + return std::format("../logs/NEXO-{}.log", ts); } } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp index 2294afb39..105c18e3e 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp @@ -22,9 +22,11 @@ namespace nexo::editor { ImGui::TextUnformatted(ObjectTypeToIcon.at(obj.type).c_str()); ImGui::SameLine(); - char buffer[256]; const std::string editableName = obj.uiName.substr(ObjectTypeToIcon.at(obj.type).size()); - std::snprintf(buffer, sizeof(buffer), "%s", editableName.c_str()); + char buffer[256]; + auto result = std::format_to_n(buffer, sizeof(buffer) - 1, "{}", editableName); + // Null-terminate at the position where format_to_n stopped writing + *(result.out) = '\0'; ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove border ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); // No rounding diff --git a/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp index 8c86ed706..d406b0b4a 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp @@ -66,7 +66,7 @@ namespace nexo::editor { const auto dockId = m_windowRegistry.getDockId(editorSceneInConfig[0]); if (!dockId) return false; - m_windowRegistry.setDockId(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId()), *dockId); + 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 @@ -74,14 +74,14 @@ namespace nexo::editor { } // Else we retrieve the first active editor scene - const std::string windowName = std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(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 (!dockId) { - setupNewDockSpaceNode(windowName, std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId())); + setupNewDockSpaceNode(windowName, std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, newScene->getSceneId())); return true; } - m_windowRegistry.setDockId(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(newScene->getSceneId()), *dockId); + m_windowRegistry.setDockId(std::format("{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, newScene->getSceneId()), *dockId); return true; } } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp index abdb55c21..6f09f3d9c 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp @@ -1,4 +1,4 @@ -//// Selection.cpp /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// // // zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz // zzzzzzz zzz zzzz zzzz zzzz zzzz @@ -19,6 +19,7 @@ #include "context/actions/EntityActions.hpp" #include +#include namespace nexo::editor { @@ -32,7 +33,7 @@ namespace nexo::editor { const bool multipleSelected = selectedEntities.size() > 1; const std::string menuText = multipleSelected ? - "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : + std::format("Delete Selected Entities ({})", selectedEntities.size()) : "Delete Entity"; if (ImGui::MenuItem(menuText.c_str())) @@ -67,7 +68,7 @@ namespace nexo::editor { const bool multipleSelected = selectedEntities.size() > 1; const std::string deleteMenuText = multipleSelected ? - "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : + std::format("Delete Selected Entities ({})", selectedEntities.size()) : "Delete Camera"; if (ImGui::MenuItem(deleteMenuText.c_str())) @@ -117,7 +118,7 @@ namespace nexo::editor { const bool multipleSelected = selectedEntities.size() > 1; const std::string menuText = multipleSelected ? - "Delete Selected Entities (" + std::to_string(selectedEntities.size()) + ")" : + std::format("Delete Selected Entities ({})", selectedEntities.size()) : "Delete Light"; if (ImGui::MenuItem(menuText.c_str())) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp index e5181eb03..2479d2dd4 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp @@ -39,7 +39,7 @@ namespace nexo::editor { Command::create() .description("Select all") .key("A") - .onPressed([this](){ + .onPressed([](){ selectAllCallback(); }) .build() ) @@ -47,14 +47,14 @@ namespace nexo::editor { Command::create() .description("Duplicate") .key("D") - .onPressed([this](){ duplicateSelectedCallback(); }) + .onPressed([](){ duplicateSelectedCallback(); }) .build() ) .addChild( Command::create() .description("Unhide all") .key("H") - .onPressed([this](){ showAllCallback(); }) + .onPressed([](){ showAllCallback(); }) .build() ) .addChild( @@ -72,7 +72,7 @@ namespace nexo::editor { Command::create() .description("Add Entity") .key("A") - .onPressed([this](){ this->addEntityCallback(); }) + .onPressed([this](){ addEntityCallback(); }) .build() ); @@ -81,7 +81,7 @@ namespace nexo::editor { Command::create() .description("Delete") .key("Delete") - .onPressed([this](){ this->deleteSelectedCallback(); }) + .onPressed([this](){ deleteSelectedCallback(); }) .build() ); @@ -90,7 +90,7 @@ namespace nexo::editor { Command::create() .description("Rename") .key("F2") - .onPressed([this](){ this->renameSelectedCallback(); }) + .onPressed([this](){ renameSelectedCallback(); }) .build() ); @@ -99,7 +99,7 @@ namespace nexo::editor { Command::create() .description("Expand all") .key("Down") - .onPressed([this](){ this->expandAllCallback(); }) + .onPressed([this](){ expandAllCallback(); }) .build() ); @@ -108,7 +108,7 @@ namespace nexo::editor { Command::create() .description("Collapse all") .key("Up") - .onPressed([this](){ this->collapseAllCallback(); }) + .onPressed([this](){ collapseAllCallback(); }) .build() ); @@ -117,7 +117,7 @@ namespace nexo::editor { Command::create() .description("Hide") .key("H") - .onPressed([this](){ hideSelectedCallback(); }) + .onPressed([](){ hideSelectedCallback(); }) .build() ); } @@ -131,7 +131,7 @@ namespace nexo::editor { m_popupManager.openPopupWithCallback( "Scene selection context menu", - [this, currentSceneId]() {this->showSceneSelectionContextMenu(currentSceneId);} + [this, currentSceneId]() {showSceneSelectionContextMenu(currentSceneId);} ); } diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp index 7bd22e009..e3a752697 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Update.cpp @@ -41,28 +41,28 @@ namespace nexo::editor { generateNodes( sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + [](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) { - return this->newDirectionalLightNode(sceneId, uiId, entity); + return newDirectionalLightNode(sceneId, uiId, entity); }); generateNodes( sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newPointLightNode(sceneId, uiId, entity); + return newPointLightNode(sceneId, uiId, entity); }); generateNodes( sceneNodes, [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { - return this->newSpotLightNode(sceneId, uiId, entity); + return newSpotLightNode(sceneId, uiId, entity); }); generateNodes>( sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + [](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return newCameraNode(sceneId, uiId, entity); }); @@ -74,7 +74,7 @@ namespace nexo::editor { ecs::Exclude, ecs::Exclude>( sceneNodes, - [this](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { + [](const scene::SceneId sceneId, const WindowId uiId, const ecs::Entity entity) { return newEntityNode(sceneId, uiId, entity); }); diff --git a/editor/src/ImNexo/Guard.hpp b/editor/src/ImNexo/Guard.hpp index 2ee2e35c2..d8c031ec2 100644 --- a/editor/src/ImNexo/Guard.hpp +++ b/editor/src/ImNexo/Guard.hpp @@ -186,7 +186,7 @@ namespace ImNexo { * * @param scale The scaling factor to apply to the current window's font */ - explicit FontScaleGuard(const float scale) : m_scale(scale) + explicit FontScaleGuard(const float scale) { ImGui::SetWindowFontScale(scale); } @@ -198,7 +198,5 @@ namespace ImNexo { { ImGui::SetWindowFontScale(1.0f); } - private: - float m_scale; ///< Stores the applied scale factor }; } From 4fb73c622bc5d4eed8b728be37fa44e62db4515d Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Sun, 4 May 2025 21:44:34 +0900 Subject: [PATCH 346/450] fix(undo-redo): fix sonar issues --- common/Logger.hpp | 6 ++--- common/math/Projection.cpp | 2 +- common/math/Projection.hpp | 2 +- editor/main.cpp | 6 +++-- editor/src/ADocumentWindow.cpp | 2 +- .../src/DocumentWindows/AssetManager/Show.cpp | 15 ++++++++--- .../src/DocumentWindows/EditorScene/Show.cpp | 14 +++++++--- .../DocumentWindows/EditorScene/Update.cpp | 2 +- .../EntityProperties/PointLightProperty.cpp | 2 +- .../EntityProperties/RenderProperty.cpp | 3 ++- editor/src/WindowRegistry.cpp | 2 +- editor/src/WindowRegistry.hpp | 2 +- editor/src/context/ActionManager.hpp | 3 --- editor/src/context/actions/EntityActions.hpp | 5 +++- editor/src/inputs/Command.cpp | 26 +++++++++++++++++-- editor/src/inputs/Command.hpp | 2 +- editor/src/inputs/WindowState.cpp | 2 +- editor/src/inputs/WindowState.hpp | 2 +- editor/src/utils/Config.cpp | 5 ++-- editor/src/utils/Config.hpp | 2 +- engine/src/EntityFactory3D.cpp | 20 -------------- engine/src/LightFactory.cpp | 8 +++--- engine/src/components/RenderContext.hpp | 6 ++--- engine/src/core/scene/Scene.hpp | 2 +- engine/src/ecs/Components.hpp | 2 +- engine/src/renderer/Framebuffer.hpp | 2 +- engine/src/renderer/ShaderLibrary.hpp | 19 +++++++++++++- .../src/renderer/opengl/OpenGlFramebuffer.cpp | 2 +- .../src/renderer/opengl/OpenGlFramebuffer.hpp | 2 +- .../src/renderer/opengl/OpenGlTexture2D.cpp | 2 +- .../src/renderer/opengl/OpenGlTexture2D.hpp | 2 +- engine/src/systems/CameraSystem.cpp | 5 ++-- tests/engine/components/Camera.test.cpp | 2 +- 33 files changed, 108 insertions(+), 71 deletions(-) diff --git a/common/Logger.hpp b/common/Logger.hpp index 58ec1b26c..09402e3e4 100644 --- a/common/Logger.hpp +++ b/common/Logger.hpp @@ -106,7 +106,7 @@ namespace nexo { * @return false If this message has been logged before */ bool shouldLog(const std::string& key) { - std::lock_guard lock(m_mutex); + std::scoped_lock lock(m_mutex); return m_loggedKeys.insert(key).second; } @@ -116,7 +116,7 @@ namespace nexo { * @param key The unique identifier for the message to reset */ void reset(const std::string& key) { - std::lock_guard lock(m_mutex); + std::scoped_lock lock(m_mutex); m_loggedKeys.erase(key); } @@ -124,7 +124,7 @@ namespace nexo { * @brief Reset all messages so they can be logged again */ void resetAll() { - std::lock_guard lock(m_mutex); + std::scoped_lock lock(m_mutex); m_loggedKeys.clear(); } diff --git a/common/math/Projection.cpp b/common/math/Projection.cpp index 61a4e280d..0f4cbf4f0 100644 --- a/common/math/Projection.cpp +++ b/common/math/Projection.cpp @@ -16,7 +16,7 @@ namespace nexo::math { - const glm::vec3 projectRayToWorld(const float x, const float y, + glm::vec3 projectRayToWorld(const float x, const float y, const glm::mat4 &viewProjectionMatrix, const glm::vec3 &cameraPosition, const unsigned int width, const unsigned int height diff --git a/common/math/Projection.hpp b/common/math/Projection.hpp index 0e8a923a6..ec4b0056d 100644 --- a/common/math/Projection.hpp +++ b/common/math/Projection.hpp @@ -16,7 +16,7 @@ #include namespace nexo::math { - const glm::vec3 projectRayToWorld(float x, float y, + glm::vec3 projectRayToWorld(float x, float y, const glm::mat4 &viewProjectionMatrix, const glm::vec3 &cameraPosition, unsigned int width, unsigned int height diff --git a/editor/main.cpp b/editor/main.cpp index db56aaa1a..4e726b76e 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -30,14 +30,16 @@ try { loguru::g_stderr_verbosity = loguru::Verbosity_3; nexo::editor::Editor &editor = nexo::editor::Editor::getInstance(); - editor.registerWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)); + editor.registerWindow( + 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); editor.registerWindow(NEXO_WND_USTRID_MATERIAL_INSPECTOR); editor.registerWindow(NEXO_WND_USTRID_ASSET_MANAGER); - if (const auto defaultScene = editor.getWindow("Default Scene" NEXO_WND_USTRID_DEFAULT_SCENE + std::to_string(0)).lock()) + if (const auto defaultScene = editor.getWindow(std::format("Default Scene{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, 0)).lock()) defaultScene->setDefault(); editor.init(); diff --git a/editor/src/ADocumentWindow.cpp b/editor/src/ADocumentWindow.cpp index 5a31ddba8..009f07c66 100644 --- a/editor/src/ADocumentWindow.cpp +++ b/editor/src/ADocumentWindow.cpp @@ -33,7 +33,7 @@ namespace nexo::editor { 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 (m_firstOpened && ((dockId && currentDockID != *dockId))) + 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 diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index 54cec1e24..c95c5c879 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -158,15 +158,24 @@ namespace nexo::editor { ImGuiListClipper clipper; const auto assets = assets::AssetCatalog::getInstance().getAssets(); - clipper.Begin(assets.size(), m_layout.size.itemStep.y); + clipper.Begin(static_cast(assets.size()), m_layout.size.itemStep.y); while (clipper.Step()) { for (int lineIdx = clipper.DisplayStart; lineIdx < clipper.DisplayEnd; ++lineIdx) { int startIdx = lineIdx * m_layout.size.columnCount; int endIdx = std::min(startIdx + m_layout.size.columnCount, static_cast(assets.size())); + int columns = m_layout.size.columnCount; + float stepX = m_layout.size.itemStep.x; + float stepY = m_layout.size.itemStep.y; + for (int i = startIdx; i < endIdx; ++i) { - ImVec2 itemPos = ImVec2(startPos.x + (i % m_layout.size.columnCount) * m_layout.size.itemStep.x, - startPos.y + (i / m_layout.size.columnCount) * m_layout.size.itemStep.y); + float idx = static_cast(i); + float col = std::fmod(idx, static_cast(columns)); + float row = std::floor(idx / static_cast(columns)); + ImVec2 itemPos{ + startPos.x + col * stepX, + startPos.y + row * stepY + }; drawAsset(assets[i], i, itemPos, m_layout.size.itemSize); } } diff --git a/editor/src/DocumentWindows/EditorScene/Show.cpp b/editor/src/DocumentWindows/EditorScene/Show.cpp index f2d14f005..e183370cf 100644 --- a/editor/src/DocumentWindows/EditorScene/Show.cpp +++ b/editor/src/DocumentWindows/EditorScene/Show.cpp @@ -88,7 +88,7 @@ namespace nexo::editor { ImNexo::CameraInspector(this->m_sceneId); }, ImVec2(1440,900)); } - m_popupManager.closePopup(); + PopupManager::closePopup(); } void EditorScene::renderView() @@ -122,12 +122,20 @@ namespace nexo::editor { 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 = m_windowName + std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId); + const std::string &sceneWindowName = std::format("{}{}{}", + m_windowName, + NEXO_WND_USTRID_DEFAULT_SCENE, + m_sceneId + ); m_wasVisibleLastFrame = m_isVisibleInDock; m_isVisibleInDock = false; if (ImGui::Begin(sceneWindowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollWithMouse)) { - beginRender(std::string(NEXO_WND_USTRID_DEFAULT_SCENE) + std::to_string(m_sceneId)); + 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); diff --git a/editor/src/DocumentWindows/EditorScene/Update.cpp b/editor/src/DocumentWindows/EditorScene/Update.cpp index c47e52dd5..e0670deec 100644 --- a/editor/src/DocumentWindows/EditorScene/Update.cpp +++ b/editor/src/DocumentWindows/EditorScene/Update.cpp @@ -49,7 +49,7 @@ namespace nexo::editor { void EditorScene::updateWindowState() { - auto &selector = Selector::get(); + const auto &selector = Selector::get(); if (selector.hasSelection()) { if (m_currentGizmoOperation == ImGuizmo::OPERATION::TRANSLATE) m_windowState = m_gizmoTranslateState; diff --git a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp index 5264c1f12..0f9c4549c 100644 --- a/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/PointLightProperty.cpp @@ -43,7 +43,7 @@ namespace nexo::editor { } else if (ImNexo::isItemDeactivated()) { auto afterStatePoint = pointComponent.save(); auto afterStateTransform = transform.save(); - auto actionGroup = ActionManager::get().createActionGroup(); + 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)); diff --git a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp index f6ad6b4b2..2ddd4151a 100644 --- a/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp +++ b/editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp @@ -17,6 +17,7 @@ #include "RenderProperty.hpp" #include "AEntityProperty.hpp" #include "Application.hpp" +#include "DocumentWindows/PopupManager.hpp" #include "Framebuffer.hpp" #include "components/Light.hpp" #include "context/actions/EntityActions.hpp" @@ -217,7 +218,7 @@ namespace nexo::editor { if (m_popupManager.showPopupModal("Create new material")) { createMaterialPopup(entity); - m_popupManager.closePopup(); + PopupManager::closePopup(); } } } diff --git a/editor/src/WindowRegistry.cpp b/editor/src/WindowRegistry.cpp index 7735ed69d..c7e88de33 100644 --- a/editor/src/WindowRegistry.cpp +++ b/editor/src/WindowRegistry.cpp @@ -48,7 +48,7 @@ namespace nexo::editor { return m_dockingRegistry.getDockId(name); } - const std::shared_ptr WindowRegistry::getFocusedWindow() const + std::shared_ptr WindowRegistry::getFocusedWindow() const { for (const auto &[_, windows]: m_windows) { diff --git a/editor/src/WindowRegistry.hpp b/editor/src/WindowRegistry.hpp index 4441f5a9f..7c0760322 100644 --- a/editor/src/WindowRegistry.hpp +++ b/editor/src/WindowRegistry.hpp @@ -239,7 +239,7 @@ namespace nexo::editor { */ std::optional getDockId(const std::string& name) const; - const std::shared_ptr getFocusedWindow() const; + std::shared_ptr getFocusedWindow() const; /** * @brief Removes a window's docking identifier. diff --git a/editor/src/context/ActionManager.hpp b/editor/src/context/ActionManager.hpp index da8610151..35fccdb88 100644 --- a/editor/src/context/ActionManager.hpp +++ b/editor/src/context/ActionManager.hpp @@ -35,10 +35,7 @@ namespace nexo::editor { const typename MementoComponent::Memento& beforeState, const typename MementoComponent::Memento& afterState) { - auto& component = Application::m_coordinator->getComponent(entityId); - auto action = std::make_unique>(entityId, beforeState, afterState); - recordAction(std::move(action)); } diff --git a/editor/src/context/actions/EntityActions.hpp b/editor/src/context/actions/EntityActions.hpp index 659bba252..e23bd09e0 100644 --- a/editor/src/context/actions/EntityActions.hpp +++ b/editor/src/context/actions/EntityActions.hpp @@ -22,7 +22,10 @@ namespace nexo::editor { Application::m_coordinator->addComponent(m_entity, target); } - void redo() override {} + void redo() override + { + //We have nothing to do here since we are simply redeleting the entity and its components + } private: ecs::Entity m_entity; diff --git a/editor/src/inputs/Command.cpp b/editor/src/inputs/Command.cpp index 8906ec1f9..e2722b1cb 100644 --- a/editor/src/inputs/Command.cpp +++ b/editor/src/inputs/Command.cpp @@ -22,6 +22,28 @@ #include namespace nexo::editor { + + struct StringHash { + using is_transparent = void; // enable heterogeneous lookup + size_t operator()(std::string_view sv) const noexcept { + return std::hash{}(sv); + } + 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 { + return a == b; + } + bool operator()(const std::string &a, const std::string &b) const noexcept { + return a == b; + } + }; + Command::Command( std::string description, const std::string &key, @@ -33,7 +55,7 @@ namespace nexo::editor { : m_description(std::move(description)), m_key(key), m_pressedCallback(pressedCallback), m_releaseCallback(releaseCallback), m_repeatCallback(repeatCallback), m_isModifier(isModifier), m_childrens(childrens) { // Create a mapping of key names to ImGuiKey values - static const std::unordered_map keyMap = { + static const std::unordered_map keyMap = { // Common modifiers {"ctrl", ImGuiKey_LeftCtrl}, {"control", ImGuiKey_LeftCtrl}, @@ -152,7 +174,7 @@ namespace nexo::editor { m_repeatCallback(); } - const std::span Command::getChildren() const + std::span Command::getChildren() const { return std::span(m_childrens); } diff --git a/editor/src/inputs/Command.hpp b/editor/src/inputs/Command.hpp index 4aa722ce7..06c341988 100644 --- a/editor/src/inputs/Command.hpp +++ b/editor/src/inputs/Command.hpp @@ -38,7 +38,7 @@ namespace nexo::editor { void executePressedCallback() const; void executeReleasedCallback() const; void executeRepeatCallback() const; - [[nodiscard]] const std::span getChildren() const; + [[nodiscard]] std::span getChildren() const; [[nodiscard]] const std::bitset &getSignature() const; [[nodiscard]] const std::string &getKey() const; [[nodiscard]] const std::string &getDescription() const; diff --git a/editor/src/inputs/WindowState.cpp b/editor/src/inputs/WindowState.cpp index 7c6991e29..69dfe21bd 100644 --- a/editor/src/inputs/WindowState.cpp +++ b/editor/src/inputs/WindowState.cpp @@ -26,7 +26,7 @@ namespace nexo::editor { m_commands.push_back(command); } - const std::span WindowState::getCommands() const + std::span WindowState::getCommands() const { return std::span(m_commands.data(), m_commands.size()); } diff --git a/editor/src/inputs/WindowState.hpp b/editor/src/inputs/WindowState.hpp index 63d18a750..8fcec7b15 100644 --- a/editor/src/inputs/WindowState.hpp +++ b/editor/src/inputs/WindowState.hpp @@ -27,7 +27,7 @@ namespace nexo::editor { [[nodiscard]] unsigned int getId() const; void registerCommand(const Command &command); - [[nodiscard]] const std::span getCommands() const; + [[nodiscard]] std::span getCommands() const; private: unsigned int m_id{}; std::vector m_commands; diff --git a/editor/src/utils/Config.cpp b/editor/src/utils/Config.cpp index 038061475..46a191e6d 100644 --- a/editor/src/utils/Config.cpp +++ b/editor/src/utils/Config.cpp @@ -66,7 +66,7 @@ namespace nexo::editor { return dockId; } - const std::vector findAllEditorScenes() + std::vector findAllEditorScenes() { std::string configPath = Path::resolvePathRelativeToExe( "../config/default-layout.ini").string(); @@ -121,8 +121,7 @@ namespace nexo::editor { inWindowSection = true; // Check if the window name starts with ### - isHashedWindow = (currentWindowName.size() >= 3 && - currentWindowName.substr(0, 3) == "###"); + isHashedWindow = currentWindowName.starts_with("###"); continue; } diff --git a/editor/src/utils/Config.hpp b/editor/src/utils/Config.hpp index 7c183dccb..fdb77661f 100644 --- a/editor/src/utils/Config.hpp +++ b/editor/src/utils/Config.hpp @@ -42,7 +42,7 @@ namespace nexo::editor { * @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. */ - const std::vector findAllEditorScenes(); + std::vector findAllEditorScenes(); /** * @brief Sets dock IDs for all windows in the registry based on the configuration file. diff --git a/engine/src/EntityFactory3D.cpp b/engine/src/EntityFactory3D.cpp index 59f5a99ef..86efcda56 100644 --- a/engine/src/EntityFactory3D.cpp +++ b/engine/src/EntityFactory3D.cpp @@ -66,26 +66,6 @@ namespace nexo { return newCube; } - /*ecs::Entity EntityFactory3D::createModel(const std::string &path, glm::vec3 pos, glm::vec3 size, glm::vec3 rotation) - { - components::TransformComponent transform{}; - transform.pos = pos; - transform.size = size; - transform.quat = glm::quat(rotation); - components::Material material{}; - std::shared_ptr rootNode = utils::loadModel(path); - auto model = std::make_shared(rootNode); - auto renderable = std::make_shared(material, model); - components::RenderComponent renderComponent(renderable, components::RenderType::RENDER_3D); - - ecs::Entity newModel = Application::m_coordinator->createEntity(); - Application::m_coordinator->addComponent(newModel, transform); - Application::m_coordinator->addComponent(newModel, renderComponent); - components::UuidComponent uuid; - Application::m_coordinator->addComponent(newModel, uuid); - return newModel; - }*/ - ecs::Entity EntityFactory3D::createBillboard(const glm::vec3 &pos, const glm::vec3 &size, const glm::vec4 &color) { components::TransformComponent transform{}; diff --git a/engine/src/LightFactory.cpp b/engine/src/LightFactory.cpp index 4e345a566..d554b9c4b 100644 --- a/engine/src/LightFactory.cpp +++ b/engine/src/LightFactory.cpp @@ -43,9 +43,9 @@ namespace nexo { 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); + const components::TransformComponent transformComponent{position}; Application::m_coordinator->addComponent(newPointLight, transformComponent); - const components::PointLightComponent newPointLightComponent(color, linear, quadratic); + const components::PointLightComponent newPointLightComponent{color, linear, quadratic}; Application::m_coordinator->addComponent(newPointLight, newPointLightComponent); const components::UuidComponent uuid; Application::m_coordinator->addComponent(newPointLight, uuid); @@ -58,9 +58,9 @@ namespace nexo { float outerCutOff) { ecs::Entity newSpotLight = Application::m_coordinator->createEntity(); - components::TransformComponent transformComponent(position); + components::TransformComponent transformComponent{position}; Application::m_coordinator->addComponent(newSpotLight, transformComponent); - components::SpotLightComponent newSpotLightComponent(direction, color, cutOff, outerCutOff, linear, quadratic); + components::SpotLightComponent newSpotLightComponent{direction, color, cutOff, outerCutOff, linear, quadratic}; Application::m_coordinator->addComponent(newSpotLight, newSpotLightComponent); components::UuidComponent uuid; Application::m_coordinator->addComponent(newSpotLight, uuid); diff --git a/engine/src/components/RenderContext.hpp b/engine/src/components/RenderContext.hpp index accc6e8de..ab6402601 100644 --- a/engine/src/components/RenderContext.hpp +++ b/engine/src/components/RenderContext.hpp @@ -22,7 +22,7 @@ namespace nexo::components { struct RenderContext { int sceneRendered = -1; - SceneType sceneType; + 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 struct GridParams { @@ -34,10 +34,10 @@ namespace nexo::components { GridParams gridParams; renderer::NxRenderer3D renderer3D; std::queue cameras; - LightContext sceneLights; + LightContext sceneLights{}; - RenderContext(): sceneType(), sceneLights() + RenderContext() { renderer3D.init(); } diff --git a/engine/src/core/scene/Scene.hpp b/engine/src/core/scene/Scene.hpp index 2c2415b1b..99d92bba9 100644 --- a/engine/src/core/scene/Scene.hpp +++ b/engine/src/core/scene/Scene.hpp @@ -91,7 +91,7 @@ namespace nexo::scene { void setName(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() {return m_entities;}; + [[nodiscard]] const std::set &getEntities() const {return m_entities;}; private: unsigned int m_id = nextSceneId++; std::string m_sceneName; diff --git a/engine/src/ecs/Components.hpp b/engine/src/ecs/Components.hpp index 05131ed4c..2ca96ab0a 100644 --- a/engine/src/ecs/Components.hpp +++ b/engine/src/ecs/Components.hpp @@ -368,7 +368,7 @@ namespace nexo::ecs { const Signature oldSignature, const Signature newSignature ) { - auto& componentArray = m_componentArrays[componentType]; + const auto& componentArray = m_componentArrays[componentType]; componentArray->duplicateComponent(sourceEntity, destEntity); for (const auto& group : std::ranges::views::values(m_groupRegistry)) { diff --git a/engine/src/renderer/Framebuffer.hpp b/engine/src/renderer/Framebuffer.hpp index 6cba74c92..651d772db 100644 --- a/engine/src/renderer/Framebuffer.hpp +++ b/engine/src/renderer/Framebuffer.hpp @@ -194,7 +194,7 @@ namespace nexo::renderer { */ virtual void resize(unsigned int width, unsigned int height) = 0; - [[nodiscard]] virtual const glm::vec2 getSize() const = 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; diff --git a/engine/src/renderer/ShaderLibrary.hpp b/engine/src/renderer/ShaderLibrary.hpp index 6e4d41256..1e19c9f5c 100644 --- a/engine/src/renderer/ShaderLibrary.hpp +++ b/engine/src/renderer/ShaderLibrary.hpp @@ -17,6 +17,18 @@ #include "Shader.hpp" namespace nexo::renderer { + + struct TransparentStringHasher { + using is_transparent = void; // enable heterogeneous lookup + + size_t operator()(std::string_view sv) const noexcept { + return std::hash{}(sv); + } + size_t operator()(const std::string &s) const noexcept { + return operator()(std::string_view(s)); + } + }; + class ShaderLibrary { public: void add(const std::shared_ptr &shader); @@ -26,6 +38,11 @@ namespace nexo::renderer { 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; private: - std::unordered_map> m_shaders; + std::unordered_map< + std::string, + std::shared_ptr, + TransparentStringHasher, + std::equal_to<> + > m_shaders; }; } diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp index bda0b78e6..290101184 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.cpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.cpp @@ -312,7 +312,7 @@ namespace nexo::renderer { toResize = true; } - const glm::vec2 NxOpenGlFramebuffer::getSize() const + glm::vec2 NxOpenGlFramebuffer::getSize() const { return glm::vec2(m_specs.width, m_specs.height); } diff --git a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp index 66769ea4d..b46bce374 100644 --- a/engine/src/renderer/opengl/OpenGlFramebuffer.hpp +++ b/engine/src/renderer/opengl/OpenGlFramebuffer.hpp @@ -138,7 +138,7 @@ namespace nexo::renderer { */ void resize(unsigned int width, unsigned int height) override; - [[nodiscard]] const glm::vec2 getSize() const override; + [[nodiscard]] glm::vec2 getSize() const override; /** diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.cpp b/engine/src/renderer/opengl/OpenGlTexture2D.cpp index 02211f698..76a9843d3 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.cpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.cpp @@ -123,7 +123,7 @@ namespace nexo::renderer { glBindTexture(GL_TEXTURE_2D, 0); } - void NxOpenGlTexture2D::ingestDataFromStb(uint8_t* data, const int width, const int height, const int channels, + void NxOpenGlTexture2D::ingestDataFromStb(const uint8_t* data, const int width, const int height, const int channels, const std::string& debugPath) { GLint internalFormat = 0; diff --git a/engine/src/renderer/opengl/OpenGlTexture2D.hpp b/engine/src/renderer/opengl/OpenGlTexture2D.hpp index fcac30bb8..f9e9e14ee 100644 --- a/engine/src/renderer/opengl/OpenGlTexture2D.hpp +++ b/engine/src/renderer/opengl/OpenGlTexture2D.hpp @@ -204,7 +204,7 @@ namespace nexo::renderer { * ingestDataFromStb(data, width, height, channels, path); * ``` */ - void ingestDataFromStb(uint8_t *data, int width, int height, int channels, const std::string& debugPath = "(buffer)"); + 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. diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index e092f657d..69966d2e6 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -130,7 +130,7 @@ namespace nexo::system { { constexpr float zoomSpeed = 0.5f; auto &sceneTag = getComponent(entity); - auto &cameraComponent = getComponent(entity); + const auto &cameraComponent = getComponent(entity); if (!sceneTag.isActive || sceneTag.id != sceneRendered || !cameraComponent.active) continue; auto &transform = getComponent(entity); @@ -190,8 +190,7 @@ namespace nexo::system { // Extract camera orientation vectors from current quaternion glm::vec3 right = transform.quat * glm::vec3(1.0f, 0.0f, 0.0f); - glm::vec3 up = transform.quat * glm::vec3(0.0f, 1.0f, 0.0f); - glm::vec3 front = transform.quat * glm::vec3(0.0f, 0.0f, -1.0f); + // Create rotation quaternions based on mouse movement glm::quat pitchRotation = glm::angleAxis(glm::radians(-mouseDelta.y), right); diff --git a/tests/engine/components/Camera.test.cpp b/tests/engine/components/Camera.test.cpp index 6970434bb..786a12888 100644 --- a/tests/engine/components/Camera.test.cpp +++ b/tests/engine/components/Camera.test.cpp @@ -28,7 +28,7 @@ class DummyFramebuffer : public nexo::renderer::NxFramebuffer { void unbind() override {} void setClearColor(const glm::vec4 &) override {} unsigned int getFramebufferId() const override { return 0; } - const glm::vec2 getSize() const override { return glm::vec2(0.0f); } + glm::vec2 getSize() const override { return glm::vec2(0.0f); } void resize(unsigned int, unsigned int ) override {} void getPixelWrapper(unsigned int, int, int, void *, const std::type_info &) const override {} void clearAttachmentWrapper(unsigned int, const void *, const std::type_info &) const override {} From c21c7bc1ec64ff943bf5584cb6e0b1985337e39a Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 01:53:36 +0900 Subject: [PATCH 347/450] fix(undo-redo): fix sonar issues --- editor/src/DocumentWindows/AssetManager/Show.cpp | 2 +- editor/src/DocumentWindows/EditorScene/Shortcuts.cpp | 2 +- editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp | 4 ++-- engine/src/systems/CameraSystem.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/editor/src/DocumentWindows/AssetManager/Show.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp index c95c5c879..3aa3b2e8d 100644 --- a/editor/src/DocumentWindows/AssetManager/Show.cpp +++ b/editor/src/DocumentWindows/AssetManager/Show.cpp @@ -169,7 +169,7 @@ namespace nexo::editor { float stepY = m_layout.size.itemStep.y; for (int i = startIdx; i < endIdx; ++i) { - float idx = static_cast(i); + auto idx = static_cast(i); float col = std::fmod(idx, static_cast(columns)); float row = std::floor(idx / static_cast(columns)); ImVec2 itemPos{ diff --git a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp index f3564d889..f452665f1 100644 --- a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp +++ b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp @@ -41,7 +41,7 @@ namespace nexo::editor { { auto &selector = Selector::get(); auto &app = nexo::getApp(); - auto &scene = app.getSceneManager().getScene(m_sceneId); + const auto &scene = app.getSceneManager().getScene(m_sceneId); selector.clearSelection(); diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp index 2479d2dd4..26a12014c 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp @@ -142,7 +142,7 @@ namespace nexo::editor { if (currentSceneId != -1) { auto& app = nexo::getApp(); - auto& scene = app.getSceneManager().getScene(currentSceneId); + const auto& scene = app.getSceneManager().getScene(currentSceneId); selector.clearSelection(); @@ -273,7 +273,7 @@ namespace nexo::editor { if (currentSceneId == -1) return; auto& app = getApp(); - auto& scene = app.getSceneManager().getScene(currentSceneId); + const auto& scene = app.getSceneManager().getScene(currentSceneId); auto& actionManager = ActionManager::get(); auto actionGroup = ActionManager::createActionGroup(); diff --git a/engine/src/systems/CameraSystem.cpp b/engine/src/systems/CameraSystem.cpp index 69966d2e6..301435b58 100644 --- a/engine/src/systems/CameraSystem.cpp +++ b/engine/src/systems/CameraSystem.cpp @@ -229,7 +229,7 @@ namespace nexo::system { { constexpr float zoomSpeed = 0.5f; auto &tag = getComponent(entity); - auto &cameraComponent = getComponent(entity); + const auto &cameraComponent = getComponent(entity); if (!tag.isActive || sceneRendered != tag.id || !cameraComponent.active) continue; auto &target = getComponent(entity); From a2543eaba9715bdfa6bc8f779c129241f6a677d1 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 03:25:32 +0900 Subject: [PATCH 348/450] fix(undo-redo): fix possible overflow --- engine/src/assets/Assets/Model/ModelImporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index d68af03e5..22fc45237 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -116,7 +116,7 @@ namespace nexo::assets { if (texture->achFormatHint[0] == '\0') { // if empty, then ARGB888 renderer::NxTextureFormatConvertArgb8ToRgba8( reinterpret_cast(texture->pcData), - texture->mWidth * texture->mHeight * sizeof(aiTexel) + static_cast(texture->mWidth) * static_cast(texture->mHeight) * sizeof(aiTexel) ); format = renderer::NxTextureFormat::RGBA8; } else { From 53ed08f9ffed8c55963eca0e596d3a9721b932c7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 04:32:39 +0900 Subject: [PATCH 349/450] tests(test-window): add test file protocol for the editor --- tests/editor/CameraInspector.test | 14 +++++++++ tests/editor/Console.test | 5 ++++ tests/editor/Docking.test | 5 ++++ tests/editor/EditorScene.test | 40 +++++++++++++++++++++++++ tests/editor/Global.test | 3 ++ tests/editor/Inspector.test | 45 +++++++++++++++++++++++++++++ tests/editor/MaterialInspector.test | 12 ++++++++ tests/editor/SceneTree.test | 40 +++++++++++++++++++++++++ 8 files changed, 164 insertions(+) create mode 100644 tests/editor/CameraInspector.test create mode 100644 tests/editor/Console.test create mode 100644 tests/editor/Docking.test create mode 100644 tests/editor/EditorScene.test create mode 100644 tests/editor/Global.test create mode 100644 tests/editor/Inspector.test create mode 100644 tests/editor/MaterialInspector.test create mode 100644 tests/editor/SceneTree.test diff --git a/tests/editor/CameraInspector.test b/tests/editor/CameraInspector.test new file mode 100644 index 000000000..e05caee47 --- /dev/null +++ b/tests/editor/CameraInspector.test @@ -0,0 +1,14 @@ +# Camera Inspector +- Should be able to modify transform component +- Should be able to modify camera component +- Should be able to add camera controller component +- Should be able to modify camera controller +- Should be able to add camera target component +- Should be able to modify camera target component +- Undo/Redo should work properly in this inspector +- An error should be showed if no name is provided +- Nothing should be added when cancelling +- Undo/Redo should reset when cancelling +- A new camera should be added when confirming +- Undo should delete the newly camera created +- Camera preview should display a preview of the scene from the new camera diff --git a/tests/editor/Console.test b/tests/editor/Console.test new file mode 100644 index 000000000..7929e59f7 --- /dev/null +++ b/tests/editor/Console.test @@ -0,0 +1,5 @@ +# Console Window +- Should see logs +- Should be able to filter the logs +- Should be able to write in it +- Log file should be generated when program closes diff --git a/tests/editor/Docking.test b/tests/editor/Docking.test new file mode 100644 index 000000000..a70587c62 --- /dev/null +++ b/tests/editor/Docking.test @@ -0,0 +1,5 @@ +# Docking +- When no config file, layout should be set by default +- When config file, layout should persist +- Should be able to move windows +- Should be able to resize windows diff --git a/tests/editor/EditorScene.test b/tests/editor/EditorScene.test new file mode 100644 index 000000000..059cc242b --- /dev/null +++ b/tests/editor/EditorScene.test @@ -0,0 +1,40 @@ +# Editor Scene Window +## Toolbar +- Should be able to click on any toolbar button +- Should be able to add a cube +- Should be able to edit editor camera +- Should be able to switch gizmo mode +- Should be able to switch between local/global coordinates +- Should be able to enable translation/rotate snap +- Should be able to edit snap settings by right clicking +- Should be able to enable/disable grid +- Should be able to edit grid setting by right clicking +## Shortcuts +- Should be able to use SHIFT + A to add new object +- Should be able to add a cube +- Should be able to add a directional light +- Should be able to add a point light +- Should be able to add a spot light +- Should be able to add a camera +- When adding camera, should open a camera inspector popup +- Should be able to unhide all with CTRL + H +- Should be able to select all with A +- Should be able to delete selected object with DELETE +- Should be able to delete multiple objects with DELETE +- Should be able to hide object with H +- Should be able to hide multiple objects with H +- Should be able to switch Gizmo mode with G/R/S/U +- Should be able to toggle snap with SHIFT + S +- Should be able to hide everything but selected object with SHIFT + H +- Should be able to hide everything but selected objects with multiple selection +## Selection +- Should be able to click on an entity to select it +- Selected entities should have an outline +- Should be able to use SHIFT + LEFT CLICK to select multiple entity +- Should be able to use CTRL + LEFT CLICK to select multiple entity +- Should be able to use CTRL + LEFT CLICK to unselect an entity +## Gizmo +- Should be able to move object (every different axis/plane) +- Should be able to rotate object (every axis/screen) +- Should be able to scale object (every axis) +- Should be able to perform all interactions above on multiple objects diff --git a/tests/editor/Global.test b/tests/editor/Global.test new file mode 100644 index 000000000..2f80ff805 --- /dev/null +++ b/tests/editor/Global.test @@ -0,0 +1,3 @@ +# Global +- Should be able to undo action (CTRL + Z) +- Should be able to redo ation (CTRL + SHIFT + Z) diff --git a/tests/editor/Inspector.test b/tests/editor/Inspector.test new file mode 100644 index 000000000..d4746bd1b --- /dev/null +++ b/tests/editor/Inspector.test @@ -0,0 +1,45 @@ +# Inspector Window +- Should be able to view selected entity components +- When multiple entity selected, only the first one should display +## Transform property +- Should be able to modify position values +- Should be able to manually write a position +- Should be able to modify rotation values +- Should be able to manually write a rotation +- Should be able to modify scale values +- Should be able to manually write a scale +## Render Component +- Should be able to hide a an entity +- Material preview should display +- Create new material should open a popup +- Modify material should open the material inspector on its last docked position +## Camera component +- Viewport should not be modifiable if locked +- If viewport unlocked, should be able to modify values +- If viewport unlocked, should be able to write values +- Should be able to modify FOV +- Should be able to modify near plane +- Should be able to modify far plane +- Should be able to modify clear color +## Camera controller +- Should be able to modify mouse sensitivity +## Camera target +- Should be able to modify mouse sensitivity +- Should be able to modify distance from targeted entity +- Should be able to modify targeted entity +## Ambient light +- Should be able to modify color +## Directional light +- Should be able to modify color +- Should be able to modify direction +## Point light +- Should be able to modify color +- Should be able to modify position +- Should be able to modify distance (intensity) +## Spot light +- Should be able to modify color +- Should be able to modify position +- Should be able to modify direction +- Should be able to modify distance (intensity) +- Should be able to modify inner cut off +- Should be able to modify outer cut off diff --git a/tests/editor/MaterialInspector.test b/tests/editor/MaterialInspector.test new file mode 100644 index 000000000..9d7213ea6 --- /dev/null +++ b/tests/editor/MaterialInspector.test @@ -0,0 +1,12 @@ +# Material Inspector +## Modify Material +- Should be able to modify albedo color +- Should be able to modify specular color +- Should be able to set albedo texture +- Should be able to set specular texture +## New Material +- Should be able to modify albedo color +- Should be able to modify specular color +- Should be able to set albedo texture +- Should be able to set specular texture +- Material preview should display a preview of the material with a camera target diff --git a/tests/editor/SceneTree.test b/tests/editor/SceneTree.test new file mode 100644 index 000000000..05cf3b754 --- /dev/null +++ b/tests/editor/SceneTree.test @@ -0,0 +1,40 @@ +# Scene Tree Window +- Should be able to expand tree nodes +- Should be able to create new scene when right clicking outside a scene +## Scene Creation +- Should open a popup when creating a new scene +- New scene should get docked to existing scene if one exists +- New scene should get docked to last position of a scene if none exists +## Scene context +- Should be able to add a cube +- Should be able to add a directional light +- Should be able to add a point light +- Should be able to add a spot light +- Should be able to add a camera +- When adding camera, should open a camera inspector popup +- Should be able to view ambient light +- Should be able to view directional light +- Should be able to view point light +- Should be able to view spot light +- Should be able to view camera +- Should be able to view camera preview when hovering +- Should be able to rename entities +- Should be able to delete entity +- Should be able to delete multiple entity +## Selection +- Should be able to select multiple entities with CTRL + LEFT CLICK +- Should be able to deselect entity with CTRL + LEFT CLICK +- Should be able to select multiple entities with SHIFT + LEFT CLICK +## Shortcuts +- Should be able to expand all with down arrow +- Should be able to collapse all with up arrow +- Should be able to select all with CTRL + A +- Should be able to create scene with CTRL + N +- Should be able to duplicate entity with CTRL + D +- Should be able to duplicate multiple entities with CTRL + D +- Should be able to add entity with A +- Should be able to hide entity with H +- Should be able to hide multiple entities with H +- Should be able to unhide all with CTRL + H +- Should be able to delete entity with DELETE +- Should be able to delete multiple entities with DELETE From 7ef9f05d37890e8a6ab236024f241d6e33984b22 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 08:10:45 +0900 Subject: [PATCH 350/450] feat(test-window): add test window + test protocol file parser --- .../src/DocumentWindows/TestWindow/Init.cpp | 24 +++++ .../src/DocumentWindows/TestWindow/Parser.cpp | 99 +++++++++++++++++++ .../src/DocumentWindows/TestWindow/Show.cpp | 93 +++++++++++++++++ .../DocumentWindows/TestWindow/Shutdown.cpp | 24 +++++ .../DocumentWindows/TestWindow/TestWindow.hpp | 57 +++++++++++ .../src/DocumentWindows/TestWindow/Update.cpp | 24 +++++ 6 files changed, 321 insertions(+) create mode 100644 editor/src/DocumentWindows/TestWindow/Init.cpp create mode 100644 editor/src/DocumentWindows/TestWindow/Parser.cpp create mode 100644 editor/src/DocumentWindows/TestWindow/Show.cpp create mode 100644 editor/src/DocumentWindows/TestWindow/Shutdown.cpp create mode 100644 editor/src/DocumentWindows/TestWindow/TestWindow.hpp create mode 100644 editor/src/DocumentWindows/TestWindow/Update.cpp diff --git a/editor/src/DocumentWindows/TestWindow/Init.cpp b/editor/src/DocumentWindows/TestWindow/Init.cpp new file mode 100644 index 000000000..a6e0f0a17 --- /dev/null +++ b/editor/src/DocumentWindows/TestWindow/Init.cpp @@ -0,0 +1,24 @@ +//// Init.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: 05/05/2025 +// Description: Source file for the test window initialization +// +/////////////////////////////////////////////////////////////////////////////// + +#include "TestWindow.hpp" + +namespace nexo::editor { + + void TestWindow::setup() + { + parseTestFolder(); + } + +} diff --git a/editor/src/DocumentWindows/TestWindow/Parser.cpp b/editor/src/DocumentWindows/TestWindow/Parser.cpp new file mode 100644 index 000000000..95a4142b6 --- /dev/null +++ b/editor/src/DocumentWindows/TestWindow/Parser.cpp @@ -0,0 +1,99 @@ +//// Parser.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: 05/05/2025 +// Description: Source file for the parsing logic for the test window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "Exception.hpp" +#include "TestWindow.hpp" +#include "utils/String.hpp" +#include "Path.hpp" +#include "Logger.hpp" +#include "exceptions/Exceptions.hpp" + +#include +#include +#include + +namespace nexo::editor { + + static std::string parseBullet(std::string line) + { + 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(), std::strerror(errno)); + + TestSection* currentSection = nullptr; + TestSection* currentSubSection = nullptr; + std::string line; + + while (std::getline(in, line)) { + utils::trim(line); + // Top-level section + if (line.rfind("# ", 0) == 0) { + m_testSections.emplace_back(); + currentSection = &m_testSections.back(); + currentSection->name = line.substr(2); + currentSubSection = nullptr; + // Sub-section + } else if (line.rfind("## ", 0) == 0) { + if (!currentSection) + THROW_EXCEPTION(InvalidTestFileFormat, entry.path().string(), "Subsection found without main section"); + currentSection->subSections.emplace_back(); + currentSubSection = ¤tSection->subSections.back(); + currentSubSection->name = line.substr(3); + // 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 \""); + + TestCase testCase; + testCase.name = std::move(testName); + + if (currentSubSection) + currentSubSection->testCases.push_back(std::move(testCase)); + else if (currentSection) + currentSection->testCases.push_back(std::move(testCase)); + } + } + } + + void TestWindow::parseTestFolder() + { + std::filesystem::path testDir = Path::resolvePathRelativeToExe( + "../tests/editor"); + + for (const auto &entry : std::filesystem::directory_iterator(testDir)) { + if (!entry.is_regular_file()) { + LOG(NEXO_WARN, "{} is a directory, skipping...", entry.path().string()); + continue; + } + if (entry.path().extension() != ".test") { + LOG(NEXO_WARN, "{} is not a test file, skipping...", entry.path().string()); + continue; + } + try { + parseFile(entry); + } catch (const nexo::Exception &e) { + LOG_EXCEPTION(e); + } + } + } + +} diff --git a/editor/src/DocumentWindows/TestWindow/Show.cpp b/editor/src/DocumentWindows/TestWindow/Show.cpp new file mode 100644 index 000000000..f1a2cde65 --- /dev/null +++ b/editor/src/DocumentWindows/TestWindow/Show.cpp @@ -0,0 +1,93 @@ +//// Show.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: 05/05/2025 +// Description: Source file for the test window rendering +// +/////////////////////////////////////////////////////////////////////////////// + +#include "TestWindow.hpp" +#include "ImNexo/Components.hpp" + +namespace nexo::editor { + + static void renderRadioButtons(TestResult &result) + { + if (ImGui::RadioButton("Passed", result == TestResult::PASSED)) + result = TestResult::PASSED; + ImGui::SameLine(); + if (ImGui::RadioButton("Failed", result == TestResult::FAILED)) + result = TestResult::FAILED; + ImGui::SameLine(); + if (ImGui::RadioButton("Skipped", result == TestResult::SKIPPED)) + result = TestResult::SKIPPED; + } + + static void renderTestCases(TestSection §ion) + { + for (unsigned int i = 0; i < section.testCases.size(); ++i) { + auto &tc = section.testCases[i]; + ImGui::PushID(static_cast(i)); + ImGui::Text("%s", tc.name.c_str()); + ImGui::SameLine(); + + renderRadioButtons(tc.result); + + if (tc.result == TestResult::SKIPPED) { + ImGui::Indent(20.0f); + ImGui::InputTextWithHint( + "##skip_reason", + "Reason for skip...", + tc.skippedMessage, + sizeof(tc.skippedMessage) + ); + ImGui::Unindent(20.0f); + } + + ImGui::PopID(); + } + } + + static void renderSubSections(std::vector &subSections) + { + for (unsigned int i = 0; i < subSections.size(); ++i) { + auto &sub = subSections[i]; + ImGui::PushID(static_cast(i)); + ImNexo::ToggleButtonWithSeparator(sub.name, &sub.sectionOpen); + if (sub.sectionOpen) + renderTestCases(sub); + ImGui::PopID(); + } + } + + void TestWindow::show() + { + if (!ImGui::Begin(NEXO_WND_USTRID_TEST, &m_opened, ImGuiWindowFlags_None)) { + ImGui::End(); + return; + } + beginRender(NEXO_WND_USTRID_TEST); + + for (unsigned int i = 0; i < m_testSections.size(); ++i) { + auto §ion = m_testSections[i]; + ImGui::PushID(static_cast(i)); + if (ImNexo::Header(std::format("##MainSection{}", i), section.name)) { + // Any test cases directly in this section + renderTestCases(section); + + // Now sub-sections + renderSubSections(section.subSections); + ImGui::TreePop(); + } + ImGui::PopID(); + } + + ImGui::End(); + } +} diff --git a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp new file mode 100644 index 000000000..6e95fb022 --- /dev/null +++ b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp @@ -0,0 +1,24 @@ +//// Shutdown.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: 05/05/2025 +// Description: Source file for the shutdown logic of the test window +// +/////////////////////////////////////////////////////////////////////////////// + +#include "TestWindow.hpp" + +namespace nexo::editor { + + void TestWindow::shutdown() + { + + } + +} diff --git a/editor/src/DocumentWindows/TestWindow/TestWindow.hpp b/editor/src/DocumentWindows/TestWindow/TestWindow.hpp new file mode 100644 index 000000000..b4cb8ae92 --- /dev/null +++ b/editor/src/DocumentWindows/TestWindow/TestWindow.hpp @@ -0,0 +1,57 @@ +//// TestWindow.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: Mehdy MORVAN +// Date: 05/05/2025 +// Description: Header file for the test window +// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "ADocumentWindow.hpp" + +#include +#include + +namespace nexo::editor { + + enum class TestResult { + NOT_TESTED, + PASSED, + FAILED, + SKIPPED + }; + + struct TestCase { + std::string name; + TestResult result = TestResult::NOT_TESTED; + char skippedMessage[512]; + }; + + struct TestSection { + std::string name; + std::vector testCases; + bool sectionOpen = false; + std::vector subSections; + }; + + 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); + }; +} diff --git a/editor/src/DocumentWindows/TestWindow/Update.cpp b/editor/src/DocumentWindows/TestWindow/Update.cpp new file mode 100644 index 000000000..38dc96b55 --- /dev/null +++ b/editor/src/DocumentWindows/TestWindow/Update.cpp @@ -0,0 +1,24 @@ +//// Update.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: 05/05/2025 +// Description: Source file for the test window update +// +/////////////////////////////////////////////////////////////////////////////// + +#include "TestWindow.hpp" + +namespace nexo::editor { + + void TestWindow::update() + { + + } + +} From fe36a73fa4aefc4de94d88c685be07bb518656b7 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 08:11:03 +0900 Subject: [PATCH 351/450] feat(test-window): add struid for test window --- editor/src/ADocumentWindow.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index 325c2ab3a..5513481e4 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -30,6 +30,7 @@ namespace nexo::editor { #define NEXO_WND_USTRID_MATERIAL_INSPECTOR "###Material Inspector" #define NEXO_WND_USTRID_DEFAULT_SCENE "###Default Scene" #define NEXO_WND_USTRID_BOTTOM_BAR "###CommandsBar" + #define NEXO_WND_USTRID_TEST "###TestWindow" class ADocumentWindow : public IDocumentWindow { public: From 851b2bafc2ce08a856d22b8fffd9e6753e8b2005 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 08:11:27 +0900 Subject: [PATCH 352/450] feat(test-window): add test window opening when pressing CTRL + SHIFT + T --- editor/src/Editor.cpp | 8 +++++++- editor/src/Editor.hpp | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index 0c25fc6e0..c4870e665 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -23,6 +23,7 @@ #include "IconsFontAwesome.h" #include "ImNexo/Elements.hpp" #include "context/ActionManager.hpp" +#include "DocumentWindows/TestWindow/TestWindow.hpp" #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" @@ -314,7 +315,7 @@ namespace nexo::editor { ImGui::DockSpaceOverViewport(viewport->ID); } - void Editor::handleGlobalCommands() const + void Editor::handleGlobalCommands() { if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyPressed(ImGuiKey_Z)) { @@ -327,6 +328,11 @@ namespace nexo::editor { ActionManager::get().undo(); } } + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyDown(ImGuiKey_LeftShift) && ImGui::IsKeyPressed(ImGuiKey_T)) + { + registerWindow(NEXO_WND_USTRID_TEST); + getWindow(NEXO_WND_USTRID_TEST).lock()->setup(); + } } std::vector Editor::handleFocusedWindowCommands() diff --git a/editor/src/Editor.hpp b/editor/src/Editor.hpp index 71683a08e..a56be2a04 100644 --- a/editor/src/Editor.hpp +++ b/editor/src/Editor.hpp @@ -160,7 +160,7 @@ namespace nexo::editor { void drawShortcutBar(const std::vector &possibleCommands) const; void drawBackground() const; - void handleGlobalCommands() const; + void handleGlobalCommands(); std::vector handleFocusedWindowCommands(); bool m_quit = false; From 3cd1f5403c4f4dd6de85bd196b1055daf92ef451 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 08:11:39 +0900 Subject: [PATCH 353/450] chore(test-window): add source files --- editor/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index e2496b699..38a190f12 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -84,6 +84,11 @@ set(SRCS editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp editor/src/DocumentWindows/EntityProperties/CameraController.cpp editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp + editor/src/DocumentWindows/TestWindow/Init.cpp + editor/src/DocumentWindows/TestWindow/Parser.cpp + editor/src/DocumentWindows/TestWindow/Show.cpp + editor/src/DocumentWindows/TestWindow/Shutdown.cpp + editor/src/DocumentWindows/TestWindow/Update.cpp ) # Windows App Icon From 71d4a787272c37c4164032fd9d6e0090d3847b37 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 08:12:19 +0900 Subject: [PATCH 354/450] feat(test-window): add InvalidTestFileFormat + FileRead exceptions --- editor/src/exceptions/Exceptions.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/editor/src/exceptions/Exceptions.hpp b/editor/src/exceptions/Exceptions.hpp index fe3ab5361..8b7cd5fe5 100644 --- a/editor/src/exceptions/Exceptions.hpp +++ b/editor/src/exceptions/Exceptions.hpp @@ -38,6 +38,13 @@ namespace nexo::editor { : Exception("File not found: " + filePath, loc) {} }; + 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) {} + }; + class WindowNotRegistered final : public Exception { public: /** @@ -104,4 +111,11 @@ namespace nexo::editor { : Exception("[" + backendApiName + " FATAL ERROR]" + message, loc) {} }; + 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) {} + }; + } From bd7363acb7b6838a8fdf519f141765e0809116ce Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 08:12:38 +0900 Subject: [PATCH 355/450] feat(test-window): add util func to trim whitespace from a string --- editor/src/utils/String.cpp | 9 +++++++++ editor/src/utils/String.hpp | 2 ++ 2 files changed, 11 insertions(+) diff --git a/editor/src/utils/String.cpp b/editor/src/utils/String.cpp index 5a1db93cb..053431ab9 100644 --- a/editor/src/utils/String.cpp +++ b/editor/src/utils/String.cpp @@ -14,6 +14,8 @@ #include "String.hpp" +#include + namespace nexo::editor::utils { std::string removeIconPrefix(const std::string &str) { @@ -21,4 +23,11 @@ namespace nexo::editor::utils { return str.substr(pos + 1); return str; } + + void trim(std::string &s) + { + auto not_space = [](char c){ return !std::isspace(c); }; + s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space)); + s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end()); + } } diff --git a/editor/src/utils/String.hpp b/editor/src/utils/String.hpp index b25790f48..417f3c918 100644 --- a/editor/src/utils/String.hpp +++ b/editor/src/utils/String.hpp @@ -27,4 +27,6 @@ namespace nexo::editor::utils { * @return std::string The string with the icon prefix removed. */ std::string removeIconPrefix(const std::string &str); + + void trim(std::string &s); } From 362da8367b39c66f8fe9e8550157b0f3dce835db Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 09:23:38 +0900 Subject: [PATCH 356/450] feat(test-window): add method to set the open state of a window --- editor/src/ADocumentWindow.hpp | 1 + editor/src/IDocumentWindow.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp index 5513481e4..798c6070b 100644 --- a/editor/src/ADocumentWindow.hpp +++ b/editor/src/ADocumentWindow.hpp @@ -48,6 +48,7 @@ namespace nexo::editor { [[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; } diff --git a/editor/src/IDocumentWindow.hpp b/editor/src/IDocumentWindow.hpp index 1e2f56616..e88dcec35 100644 --- a/editor/src/IDocumentWindow.hpp +++ b/editor/src/IDocumentWindow.hpp @@ -33,6 +33,7 @@ namespace nexo::editor { [[nodiscard]] virtual bool isFocused() const = 0; [[nodiscard]] virtual bool isOpened() const = 0; + virtual void setOpened(bool opened) = 0; [[nodiscard]] virtual bool isHovered() const = 0; [[nodiscard]] virtual const ImVec2 &getContentSize() const = 0; [[nodiscard]] virtual bool &getOpened() = 0; From d21be0f5d90da6578d60828cf4d1ce67f02d6442 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 09:23:59 +0900 Subject: [PATCH 357/450] feat(test-window): add cancel and confirm button --- editor/src/DocumentWindows/TestWindow/Show.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/editor/src/DocumentWindows/TestWindow/Show.cpp b/editor/src/DocumentWindows/TestWindow/Show.cpp index f1a2cde65..b5e0cef5d 100644 --- a/editor/src/DocumentWindows/TestWindow/Show.cpp +++ b/editor/src/DocumentWindows/TestWindow/Show.cpp @@ -88,6 +88,19 @@ namespace nexo::editor { ImGui::PopID(); } + // Action buttons + ImGui::Separator(); + if (ImGui::Button("Cancel")) { + resetTestCases(); + m_opened = false; + } + ImGui::SameLine(); + if (ImGui::Button("Confirm")) { + writeTestReport(); + resetTestCases(); + m_opened = false; + } + ImGui::End(); } } From ecaaf1d50afc461258d69e853495ad0c575f0e1f Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 09:24:27 +0900 Subject: [PATCH 358/450] feat(test-window): add file write exception --- editor/src/exceptions/Exceptions.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/editor/src/exceptions/Exceptions.hpp b/editor/src/exceptions/Exceptions.hpp index 8b7cd5fe5..fb95d9b17 100644 --- a/editor/src/exceptions/Exceptions.hpp +++ b/editor/src/exceptions/Exceptions.hpp @@ -45,6 +45,13 @@ namespace nexo::editor { : Exception(std::format("Error reading file {}: {}", filePath, message), loc) {} }; + 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) {} + }; + class WindowNotRegistered final : public Exception { public: /** From 8eed25fb36922387befecfc95374eba165c08952 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 09:24:56 +0900 Subject: [PATCH 359/450] refactor(test-window): now first if the a test window has already been creating when pressing CTRL + SHIFT + T --- editor/src/Editor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/editor/src/Editor.cpp b/editor/src/Editor.cpp index c4870e665..b7dfabf3f 100644 --- a/editor/src/Editor.cpp +++ b/editor/src/Editor.cpp @@ -330,8 +330,12 @@ namespace nexo::editor { } if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsKeyDown(ImGuiKey_LeftShift) && ImGui::IsKeyPressed(ImGuiKey_T)) { - registerWindow(NEXO_WND_USTRID_TEST); - getWindow(NEXO_WND_USTRID_TEST).lock()->setup(); + if (auto testWindow = getWindow(NEXO_WND_USTRID_TEST).lock()) { + testWindow->setOpened(true); + } else { + registerWindow(NEXO_WND_USTRID_TEST); + getWindow(NEXO_WND_USTRID_TEST).lock()->setup(); + } } } From a3abddffbff36b51e8daedcaa1324f1a2597c438 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 09:25:22 +0900 Subject: [PATCH 360/450] feat(test-window): add dump test report with test results + environnements of the user --- .../DocumentWindows/TestWindow/Shutdown.cpp | 165 +++++++++++++++++- .../DocumentWindows/TestWindow/TestWindow.hpp | 2 + 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp index 6e95fb022..15fb6b7ed 100644 --- a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp +++ b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp @@ -12,13 +12,176 @@ // /////////////////////////////////////////////////////////////////////////////// +#include "Exception.hpp" #include "TestWindow.hpp" +#include "Path.hpp" +#include "exceptions/Exceptions.hpp" + +#include + +#ifdef __linux__ + #include + #include +#endif + +#ifdef NX_GRAPHICS_API_OPENGL + #include +#endif namespace nexo::editor { - void TestWindow::shutdown() + void TestWindow::resetTestCases() + { + for (auto §ion : m_testSections) { + for (auto &tc : section.testCases) { + tc.result = TestResult::NOT_TESTED; + memset(&tc.skippedMessage, 0, sizeof(tc.skippedMessage)); + } + for (auto &sub : section.subSections) { + for (auto &tc : sub.testCases) { + tc.result = TestResult::NOT_TESTED; + memset(&tc.skippedMessage, 0, sizeof(tc.skippedMessage)); + } + } + } + } + + // Helper to get OS name + static std::string getOSName() + { +#if defined(_WIN32) + return "Windows"; +#elif defined(__APPLE__) + return "macOS"; +#elif defined(__linux__) + struct utsname info; + if (uname(&info) == 0) + return std::string(info.sysname) + " " + info.release; + else + return "Linux"; +#else + return "Unknown OS"; +#endif + } + + // Helper to get CPU info (model name) + static std::string getCPUInfo() { +#if defined(__linux__) + std::ifstream cpuinfoFile("/proc/cpuinfo"); + std::string line; + while (std::getline(cpuinfoFile, line)) { + if (line.rfind("model name", 0) == 0) { + auto pos = line.find(':'); + if (pos != std::string::npos) { + std::string model = line.substr(pos + 1); + // trim leading spaces + model.erase(model.begin(), std::find_if(model.begin(), model.end(), [](unsigned char ch) { return !std::isspace(ch); })); + return model; + } + } + } + return "Unknown CPU"; +#elif defined(_WIN32) + // Using __cpuid to get CPU brand string + int cpuInfo[4] = {0}; + char brand[0x40] = { 0 }; + __cpuid(cpuInfo, 0x80000000); + unsigned int maxId = cpuInfo[0]; + if (maxId >= 0x80000004) { + __cpuid((int*)cpuInfo, 0x80000002); + memcpy(brand, cpuInfo, sizeof(cpuInfo)); + __cpuid((int*)cpuInfo, 0x80000003); + memcpy(brand + 16, cpuInfo, sizeof(cpuInfo)); + __cpuid((int*)cpuInfo, 0x80000004); + memcpy(brand + 32, cpuInfo, sizeof(cpuInfo)); + return std::string(brand); + } + return "Unknown CPU"; +#else + return std::to_string(std::thread::hardware_concurrency()) + " cores"; +#endif + } + + // Helper to get GPU / graphics backend info (OpenGL example) + static std::string getGraphicsInfo() + { +#ifdef NX_GRAPHICS_API_OPENGL + const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); + const char* renderer = reinterpret_cast(glGetString(GL_RENDERER)); + const char* version = reinterpret_cast(glGetString(GL_VERSION)); + return std::string("OpenGL: ") + vendor + " - " + renderer + " (" + version + ")"; +#else + return "Graphics info not available"; +#endif + } + // Write environment section at top of report + static void writeEnvironmentReport(std::ofstream &out) + { + out << std::format("# Environment\n"); + out << std::format("OS: {}\n", getOSName()); + out << std::format("CPU: {}\n", getCPUInfo()); + out << std::format("Graphics: {}\n", getGraphicsInfo()); + out << std::format("Timestamp: {:%Y-%m-%d %H:%M:%S}\n\n", std::chrono::system_clock::now()); + } + + 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"; + } + } + + 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); + } + + static std::filesystem::path getTestReportFilePath() + { + 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 filename = std::format("EditorTestResults_{}.test", ts); + + auto testDir = std::filesystem::path(Path::resolvePathRelativeToExe("../tests/editor")); + std::filesystem::create_directories(testDir); + auto filePath = testDir / filename; + return filePath; + } + + void TestWindow::writeTestReport() + { + const auto filePath = getTestReportFilePath(); + + std::ofstream out(filePath.string()); + if (!out) + THROW_EXCEPTION(FileWriteException, filePath.string(), std::strerror(errno)); + + writeEnvironmentReport(out); + + for (auto §ion : m_testSections) { + out << std::format("# {}\n", section.name); + for (auto &tc : section.testCases) + writeTestCaseReport(out, tc); + for (auto &sub : section.subSections) { + out << std::format("## {}\n", sub.name); + for (auto &tc : sub.testCases) + writeTestCaseReport(out, tc); + } + } + } + + void TestWindow::shutdown() + { + writeTestReport(); } } diff --git a/editor/src/DocumentWindows/TestWindow/TestWindow.hpp b/editor/src/DocumentWindows/TestWindow/TestWindow.hpp index b4cb8ae92..2299a54c5 100644 --- a/editor/src/DocumentWindows/TestWindow/TestWindow.hpp +++ b/editor/src/DocumentWindows/TestWindow/TestWindow.hpp @@ -53,5 +53,7 @@ namespace nexo::editor { void parseTestFolder(); void parseFile(const std::filesystem::directory_entry &entry); + void resetTestCases(); + void writeTestReport(); }; } From 6b8deb580c0c58fed8da3b059f5bd2b6bfa16034 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Mon, 5 May 2025 09:50:48 +0900 Subject: [PATCH 361/450] fix(test-window): report file extension is now .report and not .test to avoid conflict when parsing the test files --- editor/src/DocumentWindows/TestWindow/Shutdown.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp index 15fb6b7ed..8669934b5 100644 --- a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp +++ b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp @@ -149,7 +149,7 @@ namespace nexo::editor { 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 filename = std::format("EditorTestResults_{}.test", ts); + std::string filename = std::format("EditorTestResults_{}.report", ts); auto testDir = std::filesystem::path(Path::resolvePathRelativeToExe("../tests/editor")); std::filesystem::create_directories(testDir); From c47ba330e6efe5cfa4703709dc3c7ee290110c38 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 6 May 2025 07:22:29 +0900 Subject: [PATCH 362/450] fix(test-window): fix warning --- editor/src/DocumentWindows/EditorScene/Gizmo.cpp | 2 +- editor/src/DocumentWindows/EditorScene/Shortcuts.cpp | 2 +- editor/src/DocumentWindows/SceneTreeWindow/Show.cpp | 2 +- engine/src/Application.hpp | 2 +- engine/src/assets/Assets/Model/ModelImporter.cpp | 4 ++-- engine/src/assets/Assets/Model/ModelParameters.hpp | 4 ++-- engine/src/assets/Assets/Texture/TextureParameters.hpp | 2 +- engine/src/systems/lights/DirectionalLightsSystem.cpp | 3 ++- 8 files changed, 11 insertions(+), 10 deletions(-) diff --git a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp index 1aa465cfe..19a2ae304 100644 --- a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp +++ b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp @@ -103,7 +103,7 @@ namespace nexo::editor { // Apply transforms to all selected entities except the source for (const auto& entity : targetEntities) { - if (entity == sourceEntity) continue; + if (entity == static_cast(sourceEntity)) continue; auto entityTransform = coord->tryGetComponent(entity); if (!entityTransform) continue; diff --git a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp index f452665f1..836b199d3 100644 --- a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp +++ b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp @@ -46,7 +46,7 @@ namespace nexo::editor { selector.clearSelection(); for (const auto entity : scene.getEntities()) { - if (entity == m_editorCamera) continue; // Skip editor camera + if (static_cast(entity) == m_editorCamera) continue; // Skip editor camera const auto uuidComponent = Application::m_coordinator->tryGetComponent(entity); if (uuidComponent) diff --git a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp index 0ed54d099..f6eb37037 100644 --- a/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp +++ b/editor/src/DocumentWindows/SceneTreeWindow/Show.cpp @@ -180,7 +180,7 @@ namespace nexo::editor { if (object.type != SelectionType::NONE && ImGui::BeginPopupContextItem(uniqueLabel.c_str())) { // Only show rename option for the primary selected entity or for non-selected entities - if ((!isSelected || selector.getPrimaryEntity() == object.data.entity) && + if ((!isSelected || selector.getPrimaryEntity() == static_cast(object.data.entity)) && ImGui::MenuItem("Rename")) { m_renameTarget = {object.type, object.uuid}; diff --git a/engine/src/Application.hpp b/engine/src/Application.hpp index 214a9599c..8ec599eba 100644 --- a/engine/src/Application.hpp +++ b/engine/src/Application.hpp @@ -78,7 +78,7 @@ namespace nexo { 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 + glm::vec2 viewportBounds[2]{}; //<< Viewport bounds in absolute coordinates (if the window viewport is embedded in the window), this is used for mouse coordinates }; /** diff --git a/engine/src/assets/Assets/Model/ModelImporter.cpp b/engine/src/assets/Assets/Model/ModelImporter.cpp index 22fc45237..8d2aea761 100644 --- a/engine/src/assets/Assets/Model/ModelImporter.cpp +++ b/engine/src/assets/Assets/Model/ModelImporter.cpp @@ -214,7 +214,7 @@ namespace nexo::assets { } std::filesystem::path modelDirectory = modelPath.parent_path(); - for (int matIdx = 0; matIdx < scene->mNumMaterials; ++matIdx) { + for (unsigned int matIdx = 0; matIdx < scene->mNumMaterials; ++matIdx) { aiMaterial const *material = scene->mMaterials[matIdx]; auto materialComponent = std::make_unique(); @@ -325,7 +325,7 @@ namespace nexo::assets { return meshNode; } - components::Mesh ModelImporter::processMesh(AssetImporterContext& ctx, aiMesh* mesh, const aiScene* scene) + components::Mesh ModelImporter::processMesh(AssetImporterContext& ctx, aiMesh* mesh, [[maybe_unused]] const aiScene* scene) { std::vector vertices; std::vector indices; diff --git a/engine/src/assets/Assets/Model/ModelParameters.hpp b/engine/src/assets/Assets/Model/ModelParameters.hpp index 1d42f8b06..94f7ef1bd 100644 --- a/engine/src/assets/Assets/Model/ModelParameters.hpp +++ b/engine/src/assets/Assets/Model/ModelParameters.hpp @@ -30,7 +30,7 @@ namespace nexo::assets { NLOHMANN_DEFINE_TYPE_INTRUSIVE(ModelImportParameters, textureParameters - ); + ) }; /** @@ -71,7 +71,7 @@ namespace nexo::assets { globalScale, textureQuality, convertToUncompressed - ); + ) }; NLOHMANN_JSON_SERIALIZE_ENUM(ModelImportPostProcessParameters::TextureQuality, diff --git a/engine/src/assets/Assets/Texture/TextureParameters.hpp b/engine/src/assets/Assets/Texture/TextureParameters.hpp index 90a792dac..e2845e743 100644 --- a/engine/src/assets/Assets/Texture/TextureParameters.hpp +++ b/engine/src/assets/Assets/Texture/TextureParameters.hpp @@ -46,7 +46,7 @@ namespace nexo::assets { format, maxSize, compressionQuality - ); + ) }; /** diff --git a/engine/src/systems/lights/DirectionalLightsSystem.cpp b/engine/src/systems/lights/DirectionalLightsSystem.cpp index 0f5cba667..4c9d0d9f5 100644 --- a/engine/src/systems/lights/DirectionalLightsSystem.cpp +++ b/engine/src/systems/lights/DirectionalLightsSystem.cpp @@ -48,7 +48,8 @@ namespace nexo::system { else 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 &dirLight = get()[0]; + const auto &lights = get(); // now 'lights' names the container + const auto &dirLight = lights[0]; renderContext.sceneLights.dirLight = dirLight; } } From 575043a177f13c52be543fa961dd11e2485d3774 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 6 May 2025 07:54:42 +0900 Subject: [PATCH 363/450] fix(test-window): fix sonar issues --- .../src/DocumentWindows/TestWindow/Shutdown.cpp | 15 +++++++++------ editor/src/DocumentWindows/TestWindow/Update.cpp | 2 +- editor/src/utils/String.cpp | 14 +++++++++++--- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp index 8669934b5..60feeb6bc 100644 --- a/editor/src/DocumentWindows/TestWindow/Shutdown.cpp +++ b/editor/src/DocumentWindows/TestWindow/Shutdown.cpp @@ -76,7 +76,10 @@ namespace nexo::editor { if (pos != std::string::npos) { std::string model = line.substr(pos + 1); // trim leading spaces - model.erase(model.begin(), std::find_if(model.begin(), model.end(), [](unsigned char ch) { return !std::isspace(ch); })); + model.erase( + model.begin(), + std::ranges::find_if(model, [](unsigned char ch) { return !std::isspace(ch); }) + ); return model; } } @@ -107,9 +110,9 @@ namespace nexo::editor { static std::string getGraphicsInfo() { #ifdef NX_GRAPHICS_API_OPENGL - const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); - const char* renderer = reinterpret_cast(glGetString(GL_RENDERER)); - const char* version = reinterpret_cast(glGetString(GL_VERSION)); + auto vendor = reinterpret_cast(glGetString(GL_VENDOR)); + auto renderer = reinterpret_cast(glGetString(GL_RENDERER)); + auto version = reinterpret_cast(glGetString(GL_VERSION)); return std::string("OpenGL: ") + vendor + " - " + renderer + " (" + version + ")"; #else return "Graphics info not available"; @@ -169,9 +172,9 @@ namespace nexo::editor { for (auto §ion : m_testSections) { out << std::format("# {}\n", section.name); - for (auto &tc : section.testCases) + for (const auto &tc : section.testCases) writeTestCaseReport(out, tc); - for (auto &sub : section.subSections) { + for (const auto &sub : section.subSections) { out << std::format("## {}\n", sub.name); for (auto &tc : sub.testCases) writeTestCaseReport(out, tc); diff --git a/editor/src/DocumentWindows/TestWindow/Update.cpp b/editor/src/DocumentWindows/TestWindow/Update.cpp index 38dc96b55..276bed1b7 100644 --- a/editor/src/DocumentWindows/TestWindow/Update.cpp +++ b/editor/src/DocumentWindows/TestWindow/Update.cpp @@ -18,7 +18,7 @@ namespace nexo::editor { void TestWindow::update() { - + // Nothing to do in the update } } diff --git a/editor/src/utils/String.cpp b/editor/src/utils/String.cpp index 053431ab9..743b79992 100644 --- a/editor/src/utils/String.cpp +++ b/editor/src/utils/String.cpp @@ -15,6 +15,9 @@ #include "String.hpp" #include +#include +#include +#include namespace nexo::editor::utils { std::string removeIconPrefix(const std::string &str) @@ -26,8 +29,13 @@ namespace nexo::editor::utils { void trim(std::string &s) { - auto not_space = [](char c){ return !std::isspace(c); }; - s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space)); - s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end()); + auto not_space = [](char c){ return !std::isspace(static_cast(c)); }; + + s.erase(s.begin(), std::ranges::find_if(s, not_space)); + auto rit = std::ranges::find_if( + s | std::views::reverse, + not_space + ); + s.erase(rit.base(),s.end()); } } From 66d9be325a06daca1146341948f12cf51b903f14 Mon Sep 17 00:00:00 2001 From: iMeaNz Date: Tue, 6 May 2025 08:17:06 +0900 Subject: [PATCH 364/450] feat(test-window): radio buttons are now aligned vertically --- .../src/DocumentWindows/TestWindow/Show.cpp | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/editor/src/DocumentWindows/TestWindow/Show.cpp b/editor/src/DocumentWindows/TestWindow/Show.cpp index b5e0cef5d..82a1eddef 100644 --- a/editor/src/DocumentWindows/TestWindow/Show.cpp +++ b/editor/src/DocumentWindows/TestWindow/Show.cpp @@ -31,26 +31,43 @@ namespace nexo::editor { static void renderTestCases(TestSection §ion) { - for (unsigned int i = 0; i < section.testCases.size(); ++i) { - auto &tc = section.testCases[i]; - ImGui::PushID(static_cast(i)); - ImGui::Text("%s", tc.name.c_str()); - ImGui::SameLine(); - - renderRadioButtons(tc.result); - - if (tc.result == TestResult::SKIPPED) { - ImGui::Indent(20.0f); - ImGui::InputTextWithHint( - "##skip_reason", - "Reason for skip...", - tc.skippedMessage, - sizeof(tc.skippedMessage) - ); - ImGui::Unindent(20.0f); - } + if (ImGui::BeginTable("TestCasesTable", 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_BordersInnerV)) + { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Result", ImGuiTableColumnFlags_WidthStretch); - ImGui::PopID(); + for (unsigned int i = 0; i < section.testCases.size(); ++i) { + auto &tc = section.testCases[i]; + ImGui::PushID(static_cast(i)); + + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::TextWrapped("%s", tc.name.c_str()); + + ImGui::TableSetColumnIndex(1); + renderRadioButtons(tc.result); + + // Skip reason (below the table row) + if (tc.result == TestResult::SKIPPED) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Indent(20.0f); + ImGui::InputTextWithHint( + "##skip_reason", + "Reason for skip...", + tc.skippedMessage, + sizeof(tc.skippedMessage) + ); + ImGui::Unindent(20.0f); + + ImGui::TableSetColumnIndex(1); + } + + ImGui::PopID(); + } + ImGui::EndTable(); } } From 33cc190ed0d88740c9a3a2ef863fb633fb826797 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Sun, 4 May 2025 19:54:17 +0900 Subject: [PATCH 365/450] feat(scripting): add utfcpp for utf-8/16 conversions --- vcpkg.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index 352d4e9ec..58965187d 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -26,6 +26,8 @@ { "name": "tinyfiledialogs", "version>=": "3.19.1#0" }, { "name": "gtest", "version>=": "1.15.2#0" }, { "name": "assimp", "version>=": "5.4.3#0" }, - {"name": "nlohmann-json", "version>=": "3.11.3#1"} + { "name": "nlohmann-json", "version>=": "3.11.3#1"}, + { "name": "nethost", "version>=": "8.0.3#0"}, + { "name": "utfcpp", "version>=": "4.0.6#0"} ] } From 7553fa8d551a3980213387db8d498dc03d0cc193 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 00:34:19 +0900 Subject: [PATCH 366/450] feat(scripting): add HostHandler (wrapper of hostfxr), HostString wrapper of char_t * --- engine/CMakeLists.txt | 2 + engine/src/scripting/native/HostString.cpp | 73 +++++ engine/src/scripting/native/HostString.hpp | 112 +++++++ engine/src/scripting/native/Scripting.cpp | 359 +++++++++++++++++++++ engine/src/scripting/native/Scripting.hpp | 131 ++++++++ 5 files changed, 677 insertions(+) create mode 100644 engine/src/scripting/native/HostString.cpp create mode 100644 engine/src/scripting/native/HostString.hpp create mode 100644 engine/src/scripting/native/Scripting.cpp create mode 100644 engine/src/scripting/native/Scripting.hpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 9aa39e862..edce59a3e 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -61,6 +61,8 @@ set(COMMON_SOURCES engine/src/assets/AssetImporterContext.cpp engine/src/assets/Assets/Model/ModelImporter.cpp engine/src/assets/Assets/Texture/TextureImporter.cpp + engine/src/scripting/native/Scripting.cpp + engine/src/scripting/native/HostString.cpp ) # Add API-specific sources diff --git a/engine/src/scripting/native/HostString.cpp b/engine/src/scripting/native/HostString.cpp new file mode 100644 index 000000000..2e5a0a981 --- /dev/null +++ b/engine/src/scripting/native/HostString.cpp @@ -0,0 +1,73 @@ +//// HostString.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: Guillaume HEIN +// Date: 04/05/2025 +// Description: char_t * string type wrapper for hostfxr +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +#include "HostString.hpp" + +namespace nexo::scripting { + + void HostString::init_from_utf8(const std::string& utf8) + { + #ifdef _WIN32 + std::wstring utf16; + utf8::utf8to16(utf8.begin(), utf8.end(), std::back_inserter(utf16)); + m_buffer.resize(utf16.size() + 1); + std::ranges::copy(utf16, m_buffer.data()); + m_buffer.back() = L'\0'; + #else + m_buffer.resize(utf8.size() + 1); + std::ranges::copy(utf8, m_buffer.data()); + m_buffer.back() = '\0'; + #endif + } + + void HostString::init_from_wide(const std::wstring& wide) + { + #ifdef _WIN32 + m_buffer.resize(wide.size() + 1); + std::ranges::copy(wide, m_buffer.data()); + m_buffer.back() = L'\0'; + #else + std::string utf8; + utf8::utf16to8(wide.begin(), wide.end(), std::back_inserter(utf8)); + m_buffer.resize(utf8.size() + 1); + std::ranges::copy(utf8, m_buffer.data()); + m_buffer.back() = '\0'; + #endif + } + + std::string HostString::to_utf8() const + { + #ifdef _WIN32 + std::string utf8; + utf8::utf16to8(m_buffer.data(), m_buffer.data() + size(), std::back_inserter(utf8)); + return utf8; + #else + return std::string(m_buffer.data(), size()); + #endif + } + + std::wstring HostString::to_wide() const + { + #ifdef _WIN32 + return std::wstring(m_buffer.data(), size()); + #else + std::wstring utf16; + utf8::utf8to16(m_buffer.data(), m_buffer.data() + size(), std::back_inserter(utf16)); + return utf16; + #endif + } + +} // namespace nexo::scripting diff --git a/engine/src/scripting/native/HostString.hpp b/engine/src/scripting/native/HostString.hpp new file mode 100644 index 000000000..f43ec7ea6 --- /dev/null +++ b/engine/src/scripting/native/HostString.hpp @@ -0,0 +1,112 @@ +//// HostString.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: Guillaume HEIN +// Date: 04/05/2025 +// Description: char_t * string type wrapper for hostfxr +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include + +#include +#include + +namespace nexo::scripting { + + class HostString { + public: + // Rule of Five with deep copy semantics + HostString() = default; + HostString(const HostString&) = default; + HostString& operator=(const HostString&) = default; + HostString(HostString&&) = default; + HostString& operator=(HostString&&) = default; + + explicit HostString(nullptr_t) : m_buffer{} + {} + + // Implicit constructors + explicit(false) HostString(const std::string& utf8) { + init_from_utf8(utf8); + } + explicit(false) HostString(const char *str) { + init_from_utf8(str); + } + + explicit(false) HostString(const std::wstring& wide) { + init_from_wide(wide); + } + explicit(false) HostString(const wchar_t *str) { + init_from_wide(str); + } + + // Access raw char_t* for hostfxr APIs + const char_t* c_str() const noexcept { + return m_buffer.data(); + } + + // Implicit conversions + explicit(false) operator std::string() const { + return to_utf8(); + } + + explicit(false) operator std::wstring() const { + return to_wide(); + } + + std::string to_utf8() const; + std::wstring to_wide() const; + + // Standard string interface + bool empty() const noexcept { return size() == 0; } + size_t size() const noexcept { return m_buffer.empty() ? 0 : m_buffer.size() - 1; } + + char_t& operator[](const size_t index) noexcept { return m_buffer[index]; } + const char_t& operator[](const size_t index) const noexcept { return m_buffer[index]; } + char_t& at(const size_t index) noexcept { return m_buffer.at(index); } + const char_t& at(const size_t index) const noexcept { return m_buffer.at(index); } + + // Iterators + auto begin() noexcept { return m_buffer.begin(); } + auto begin() const noexcept { return m_buffer.begin(); } + auto cbegin() const noexcept { return begin(); } + auto end() noexcept { return m_buffer.end() - 1; } // Exclude null terminator + auto end() const noexcept { return m_buffer.end() - 1; } // Exclude null terminator + auto cend() const noexcept { return end(); } + auto rbegin() noexcept { return std::reverse_iterator(end()); } + auto rbegin() const noexcept { return std::reverse_iterator(end()); } + auto crbegin() const noexcept { return rbegin(); } + auto rend() noexcept { return std::reverse_iterator(begin()); } + auto rend() const noexcept { return std::reverse_iterator(begin()); } + auto crend() const noexcept { return rend(); } + + // Add operators + HostString& operator+=(const HostString& other) { + m_buffer.insert(end(), other.begin(), other.end()); + return *this; + } + + HostString operator+(const HostString& other) const { + HostString result = *this; // Copy current object + result += other; + return result; + } + + private: + std::vector m_buffer = {'\0'}; + + void init_from_utf8(const std::string& utf8); + void init_from_wide(const std::wstring& wide); + }; + +} // namespace nexo::scripting diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp new file mode 100644 index 000000000..ed71124a1 --- /dev/null +++ b/engine/src/scripting/native/Scripting.cpp @@ -0,0 +1,359 @@ +//// Scripting.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: Guillaume HEIN +// Date: 30/04/2025 +// Description: Source file for the scripting system +// +/////////////////////////////////////////////////////////////////////////////// + +#include + +#include "Scripting.hpp" +#include "HostString.hpp" +#include "Logger.hpp" + + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Standard headers +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Provided by the AppHost NuGet package and installed as an SDK pack +#include + +// Header files copied from https://github.com/dotnet/core-setup +#include +#include +#include + +#ifdef WIN32 +#include + +#define STR(s) L ## s +#define CH(c) L ## c +#define DIR_SEPARATOR L'\\' + +#define string_compare wcscmp + +#else +#include +#include + +#define STR(s) s +#define CH(c) c +#define DIR_SEPARATOR '/' +#define MAX_PATH PATH_MAX + +#define string_compare strcmp + +#endif + +using string_t = std::basic_string; + +namespace nexo::scripting { + std::shared_ptr shared_library_handle = nullptr; + + hostfxr_initialize_for_dotnet_command_line_fn init_for_cmd_line_fptr = nullptr; + hostfxr_initialize_for_runtime_config_fn init_for_config_fptr = nullptr; + hostfxr_get_runtime_delegate_fn get_delegate_fptr = nullptr; + hostfxr_run_app_fn run_app_fptr = nullptr; + hostfxr_close_fn close_fptr = nullptr; + + int run_component_example(const HostString& root_path) + { + // + // STEP 1: Load HostFxr and get exported hosting functions + // + if (!load_hostfxr()) + { + assert(false && "Failure: load_hostfxr()"); + return EXIT_FAILURE; + } + + // + // STEP 2: Initialize and start the .NET Core runtime + // + const HostString config_path = root_path + STR("Nexo.runtimeconfig.json"); + load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = nullptr; + load_assembly_and_get_function_pointer = get_dotnet_load_assembly(config_path.c_str()); + assert(load_assembly_and_get_function_pointer != nullptr && "Failure: get_dotnet_load_assembly()"); + + // + // STEP 3: Load managed assembly and get function pointer to a managed method + // + const HostString dotnetlib_path = root_path + STR("Nexo.dll"); + const char_t *dotnet_type = STR("Nexo.Lib, Nexo"); + const char_t *dotnet_type_method = STR("Hello"); + // + // Function pointer to managed delegate + component_entry_point_fn hello = nullptr; + int rc = load_assembly_and_get_function_pointer( + dotnetlib_path.c_str(), + dotnet_type, + dotnet_type_method, + nullptr /*delegate_type_name*/, + nullptr, + (void**)&hello); + // + assert(rc == 0 && hello != nullptr && "Failure: load_assembly_and_get_function_pointer()"); + + // + // STEP 4: Run managed code + // + struct lib_args + { + const char_t *message; + int number; + }; + for (int i = 0; i < 3; ++i) + { + // + lib_args args + { + STR("from host!"), + i + }; + + hello(&args, sizeof(args)); + // + } + + // Function pointer to managed delegate with non-default signature + typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_fn)(lib_args args); + custom_entry_point_fn custom = nullptr; + lib_args args + { + STR("from host!"), + -1 + }; + + // UnmanagedCallersOnly + rc = load_assembly_and_get_function_pointer( + dotnetlib_path.c_str(), + dotnet_type, + STR("CustomEntryPointUnmanagedCallersOnly") /*method_name*/, + UNMANAGEDCALLERSONLY_METHOD, + nullptr, + (void**)&custom); + assert(rc == 0 && custom != nullptr && "Failure: load_assembly_and_get_function_pointer()"); + custom(args); + + // Custom delegate type + rc = load_assembly_and_get_function_pointer( + dotnetlib_path.c_str(), + dotnet_type, + STR("CustomEntryPoint") /*method_name*/, + STR("Nexo.Lib+CustomEntryPointDelegate, Nexo") /*delegate_type_name*/, + nullptr, + (void**)&custom); + assert(rc == 0 && custom != nullptr && "Failure: load_assembly_and_get_function_pointer()"); + custom(args); + + return EXIT_SUCCESS; + } + + HostHandler::~HostHandler() + { + if (shared_library_handle) { + if (m_hostfxr_fn.close) { + close_fptr(nullptr); + } + } + } + + HostHandler::Status HostHandler::initialize(Parameters parameters) + { + if (m_status == SUCCESS) + return m_status; + + m_params = std::move(parameters); + + if (!m_params.errorCallback) + m_params.errorCallback = DEFAULT_ERROR_CALLBACK; + if (m_params.nexoManagedPath.empty()) + m_params.nexoManagedPath = DEFAULT_NEXO_MANAGED_PATH; + + if (loadHostfxr() != SUCCESS) + return m_status; + + currentErrorCallback = m_params.errorCallback; + m_hostfxr_fn.set_error_writer([](const char_t* message) { + currentErrorCallback(message); + }); + + if (initRuntime() != SUCCESS) + return m_status; + + return m_status = SUCCESS; + } + + void HostHandler::defaultErrorCallback(const HostString& message) + { + std::cerr << "[Scripting] Error: " << message.to_utf8() << std::endl; + } + + HostHandler::Status HostHandler::loadHostfxr() + { + auto assemblyPath = HostString(m_params.assemblyPath.c_str()); + auto dotnetRoot = HostString(m_params.dotnetRoot.c_str()); + + get_hostfxr_parameters params { + sizeof(get_hostfxr_parameters), + assemblyPath.empty() ? nullptr : assemblyPath.c_str(), + dotnetRoot.empty() ? nullptr : dotnetRoot.c_str() + }; + // Pre-allocate a large buffer for the path to hostfxr + char_t buffer[MAX_PATH]; + size_t buffer_size = sizeof(buffer) / sizeof(char_t); + if (int rc = get_hostfxr_path(buffer, &buffer_size, ¶ms)) { + m_params.errorCallback(std::format("Failed to get hostfxr path. Error code 0x{:X}.", rc)); + return m_status = HOSTFXR_NOT_FOUND; + } + + m_dll_handle = std::make_shared(buffer); + if (m_dll_handle == nullptr) + { + m_params.errorCallback(std::format("Failed to load hostfxr library from path: {}", HostString(buffer).to_utf8())); + return m_status = HOSTFXR_LOAD_ERROR; + } + + m_hostfxr_fn.set_error_writer = m_dll_handle->get>("hostfxr_set_error_writer"); + m_hostfxr_fn.init_for_cmd_line = m_dll_handle->get>("hostfxr_initialize_for_dotnet_command_line"); + m_hostfxr_fn.init_for_config = m_dll_handle->get>("hostfxr_initialize_for_runtime_config"); + m_hostfxr_fn.get_delegate = m_dll_handle->get>("hostfxr_get_runtime_delegate"); + m_hostfxr_fn.run_app = m_dll_handle->get>("hostfxr_run_app"); + m_hostfxr_fn.close = m_dll_handle->get>("hostfxr_close"); + + if (not (m_hostfxr_fn.set_error_writer && m_hostfxr_fn.init_for_cmd_line && m_hostfxr_fn.init_for_config + && m_hostfxr_fn.get_delegate && m_hostfxr_fn.run_app && m_hostfxr_fn.close)) { + m_params.errorCallback(std::format("Failed to load hostfxr functions from path: {}", HostString(buffer).to_utf8())); + return m_status = HOSTFXR_LOAD_ERROR; + } + + return m_status = SUCCESS; + } + + HostHandler::Status HostHandler::initRuntime() + { + const std::filesystem::path runtimeConfigPath = + m_params.nexoManagedPath / NEXO_RUNTIMECONFIG_FILENAME; + + if (!std::filesystem::exists(runtimeConfigPath)) + { + m_params.errorCallback(std::format("Nexo runtime config file not found: {}", runtimeConfigPath.string())); + return m_status = RUNTIME_CONFIG_NOT_FOUND; + } + + const HostString configPath = runtimeConfigPath.c_str(); + + // Load .NET Core + int rc = init_for_config_fptr(configPath.c_str(), nullptr, &m_host_ctx); + if (rc != 0 || m_host_ctx == nullptr) + { + m_params.errorCallback(std::format("Init failed: 0x{:X}", rc)); + close_fptr(m_host_ctx); + return m_status = INIT_DOTNET_RUNTIME_ERROR; + } + return m_status = SUCCESS; + } + + HostHandler::Status HostHandler::getRuntimeDelegates() + { + int rc = 0; + + rc = get_delegate_fptr(m_host_ctx, hdt_load_assembly, reinterpret_cast(&m_delegates.load_assembly)); + if (rc != 0 || m_delegates.load_assembly == nullptr) { + m_params.errorCallback(std::format("Failed to get 'load_assembly' delegate: 0x{:X}", rc)); + return m_status = GET_DELEGATES_ERROR; + } + + rc = get_delegate_fptr(m_host_ctx, hdt_get_function_pointer, reinterpret_cast(&m_delegates.get_function_pointer)); + if (rc != 0 || m_delegates.get_function_pointer == nullptr) { + m_params.errorCallback(std::format("Failed to get 'get_function_pointer' delegate: 0x{:X}", rc)); + return m_status = GET_DELEGATES_ERROR; + } + return m_status = SUCCESS; + } + +/******************************************************************************************** + * Function used to load and activate .NET Core + ********************************************************************************************/ + // + // Using the nethost library, discover the location of hostfxr and get exports + bool load_hostfxr(const HostString& assembly_path /* = "" */) + { + get_hostfxr_parameters params { + sizeof(get_hostfxr_parameters), + assembly_path.empty() ? nullptr : assembly_path.c_str(), + nullptr }; + // Pre-allocate a large buffer for the path to hostfxr + char_t buffer[MAX_PATH]; + size_t buffer_size = sizeof(buffer) / sizeof(char_t); + int rc = get_hostfxr_path(buffer, &buffer_size, ¶ms); + if (rc != 0) + return false; + + // Load hostfxr and get desired exports + // NOTE: The .NET Runtime does not support unloading any of its native libraries. Running + // dlclose/FreeLibrary on any .NET libraries produces undefined behavior. + shared_library_handle = std::make_shared(buffer); + if (shared_library_handle == nullptr) + { + std::cerr << "Failed to load hostfxr library." << std::endl; + return false; + } + + init_for_cmd_line_fptr = shared_library_handle->get>("hostfxr_initialize_for_dotnet_command_line"); + init_for_config_fptr = shared_library_handle->get>("hostfxr_initialize_for_runtime_config"); + get_delegate_fptr = shared_library_handle->get>("hostfxr_get_runtime_delegate"); + run_app_fptr = shared_library_handle->get>("hostfxr_run_app"); + close_fptr = shared_library_handle->get>("hostfxr_close"); + + return (init_for_cmd_line_fptr && init_for_config_fptr && get_delegate_fptr && run_app_fptr && close_fptr); + } + // + + // + // Load and initialize .NET Core and get desired function pointer for scenario + load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const HostString& config_path) + { + // Load .NET Core + void *load_assembly_and_get_function_pointer = nullptr; + hostfxr_handle cxt = nullptr; + int rc = init_for_config_fptr(config_path.c_str(), nullptr, &cxt); + if (rc != 0 || cxt == nullptr) + { + std::cerr << "Init failed: " << std::hex << std::showbase << rc << std::endl; + close_fptr(cxt); + return nullptr; + } + + // Get the load assembly function pointer + rc = get_delegate_fptr( + cxt, + hdt_load_assembly_and_get_function_pointer, + &load_assembly_and_get_function_pointer); + if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) + std::cerr << "Get delegate failed: " << std::hex << std::showbase << rc << std::endl; + + close_fptr(cxt); + return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer; + } + // +} diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp new file mode 100644 index 000000000..798d227f0 --- /dev/null +++ b/engine/src/scripting/native/Scripting.hpp @@ -0,0 +1,131 @@ +//// Scripting.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: Guillaume HEIN +// Date: 30/04/2025 +// Description: Header file for the scripting system +// +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include + +#include "HostString.hpp" +#include "Path.hpp" + +namespace nexo::scripting { + + using string_t = std::basic_string; + + extern std::shared_ptr shared_library_handle; + + // Function pointers for hostfxr functions + extern hostfxr_initialize_for_dotnet_command_line_fn init_for_cmd_line_fptr; + extern hostfxr_initialize_for_runtime_config_fn init_for_config_fptr; + extern hostfxr_get_runtime_delegate_fn get_delegate_fptr; + extern hostfxr_run_app_fn run_app_fptr; + extern hostfxr_close_fn close_fptr; + + // Function to load the hostfxr library and retrieve function pointers + bool load_hostfxr(const HostString& assembly_path = ""); + load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const HostString& assembly); + + int run_component_example(const HostString& root_path); + + struct HostfxrFn { + hostfxr_set_error_writer_fn set_error_writer; + hostfxr_initialize_for_dotnet_command_line_fn init_for_cmd_line; + hostfxr_initialize_for_runtime_config_fn init_for_config; + hostfxr_get_runtime_delegate_fn get_delegate; + hostfxr_run_app_fn run_app; + hostfxr_close_fn close; + }; + + struct CoreclrDelegate { + load_assembly_fn load_assembly; + get_function_pointer_fn get_function_pointer; + }; + + class HostHandler { + protected: + static void defaultErrorCallback(const HostString& message); + public: + typedef void(*ErrorCallBackFn)(const HostString& message); + + // Globals + static inline const std::filesystem::path DEFAULT_NEXO_MANAGED_PATH = + Path::resolvePathRelativeToExe("../engine/src/scripting/managed/bin/Debug/net9.0/"); // TODO: Change it later for packing + static inline const std::string NEXO_RUNTIMECONFIG_FILENAME = "Nexo.runtimeconfig.json"; + static inline const ErrorCallBackFn DEFAULT_ERROR_CALLBACK = HostHandler::defaultErrorCallback; + + protected: + // Singleton: protected constructor and destructor + HostHandler() = default; + ~HostHandler(); + + public: + // Singleton: Meyers' Singleton Pattern + static HostHandler& getInstance() + { + static HostHandler s_instance; + return s_instance; + } + + // Singleton: delete copy constructor and assignment operator + HostHandler(HostHandler const&) = delete; + void operator=(HostHandler const&) = delete; + + enum Status { + SUCCESS, + UNINITIALIZED, + HOSTFXR_NOT_FOUND, + HOSTFXR_LOAD_ERROR, + RUNTIME_CONFIG_NOT_FOUND, + INIT_DOTNET_RUNTIME_ERROR, + GET_DELEGATES_ERROR, + + }; + + + struct Parameters { + // Hostfxr parameters, see https://github.com/dotnet/runtime/blob/main/docs/design/features/native-hosting.md#initialize-host-context + std::filesystem::path assemblyPath; + std::filesystem::path dotnetRoot; + + // Nexo + std::filesystem::path nexoManagedPath; + + ErrorCallBackFn errorCallback; + }; + static inline ErrorCallBackFn currentErrorCallback = nullptr; + + Status initialize(Parameters parameters); + + protected: + + Status loadHostfxr(); + Status initRuntime(); + Status getRuntimeDelegates(); + + protected: + Status m_status = UNINITIALIZED; + Parameters m_params; + + HostfxrFn m_hostfxr_fn = {}; + CoreclrDelegate m_delegates = {}; + + std::shared_ptr m_dll_handle = nullptr; + hostfxr_handle m_host_ctx = nullptr; + }; + +} // namespace nexo::scripting From 64b1dba7a4a7f142eda5e6f61ef50748cd0bc744 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 00:34:50 +0900 Subject: [PATCH 367/450] feat(scripting): add start of C# lib --- .../src/scripting/managed/AssemblyManager.cs | 9 ++ engine/src/scripting/managed/Lib.cs | 54 ++++++++++++ engine/src/scripting/managed/Nexo.csproj | 20 +++++ engine/src/scripting/managed/ObjectFactory.cs | 85 +++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 engine/src/scripting/managed/AssemblyManager.cs create mode 100644 engine/src/scripting/managed/Lib.cs create mode 100644 engine/src/scripting/managed/Nexo.csproj create mode 100644 engine/src/scripting/managed/ObjectFactory.cs diff --git a/engine/src/scripting/managed/AssemblyManager.cs b/engine/src/scripting/managed/AssemblyManager.cs new file mode 100644 index 000000000..e87bc28a9 --- /dev/null +++ b/engine/src/scripting/managed/AssemblyManager.cs @@ -0,0 +1,9 @@ +namespace Nexo +{ + + public class AssemblyManager + { + + } + +} \ No newline at end of file diff --git a/engine/src/scripting/managed/Lib.cs b/engine/src/scripting/managed/Lib.cs new file mode 100644 index 000000000..621cf3f68 --- /dev/null +++ b/engine/src/scripting/managed/Lib.cs @@ -0,0 +1,54 @@ +using System; +using System.Runtime.InteropServices; + +namespace Nexo +{ + public static class Lib + { + private static int s_CallCount = 1; + + [StructLayout(LayoutKind.Sequential)] + public struct LibArgs + { + public IntPtr Message; + public int Number; + } + + public static int Hello(IntPtr arg, int argLength) + { + if (argLength < System.Runtime.InteropServices.Marshal.SizeOf(typeof(LibArgs))) + { + return 1; + } + + LibArgs libArgs = Marshal.PtrToStructure(arg); + Console.WriteLine($"Hello, world! from {nameof(Lib)} [count: {s_CallCount++}]"); + PrintLibArgs(libArgs); + return 0; + } + + public delegate void CustomEntryPointDelegate(LibArgs libArgs); + public static void CustomEntryPoint(LibArgs libArgs) + { + Console.WriteLine($"Hello, world! from {nameof(CustomEntryPoint)} in {nameof(Lib)}"); + PrintLibArgs(libArgs); + } + + [UnmanagedCallersOnly] + public static void CustomEntryPointUnmanagedCallersOnly(LibArgs libArgs) + { + Console.WriteLine($"Hello, world! from {nameof(CustomEntryPointUnmanagedCallersOnly)} in {nameof(Lib)}"); + PrintLibArgs(libArgs); + } + + private static void PrintLibArgs(LibArgs libArgs) + { + string message = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Marshal.PtrToStringUni(libArgs.Message) + : Marshal.PtrToStringUTF8(libArgs.Message); + + Console.WriteLine($"-- message: {message}"); + Console.WriteLine($"-- number: {libArgs.Number}"); + } + } +} diff --git a/engine/src/scripting/managed/Nexo.csproj b/engine/src/scripting/managed/Nexo.csproj new file mode 100644 index 000000000..3da8ca61e --- /dev/null +++ b/engine/src/scripting/managed/Nexo.csproj @@ -0,0 +1,20 @@ + + + + Library + net9.0 + enable + enable + x64 + true + + + $(SolutionDir).bin\$(Configuration)-$(Platform)\ + x64 + + + $(SolutionDir).bin\$(Configuration)-$(Platform)\ + x64 + + + diff --git a/engine/src/scripting/managed/ObjectFactory.cs b/engine/src/scripting/managed/ObjectFactory.cs new file mode 100644 index 000000000..58cc82c61 --- /dev/null +++ b/engine/src/scripting/managed/ObjectFactory.cs @@ -0,0 +1,85 @@ +//// ObjectFactory.cs ///////////////////////////////////////////////////////// +// +// 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: Guillaume HEIN +// Date: 03/05/2025 +// Description: Source file for the scripting object factory +// +/////////////////////////////////////////////////////////////////////////////// + +namespace Nexo +{ + public static class ObjectFactory + { + private static Dictionary _instances = new Dictionary(); + private static int _nextId = 1; + + // Generic factory method using Activator + public static int CreateInstance(string typeName) + { + try + { + Type type = Type.GetType(typeName); + if (type == null) + return -1; + + object instance = Activator.CreateInstance(type); + int id = _nextId++; + _instances[id] = instance; + return id; + } + catch + { + return -1; + } + } + + // Create with parameters + public static int CreateInstanceWithParams(string typeName, object[] parameters) + { + try + { + Type type = Type.GetType(typeName); + if (type == null) + return -1; + + object instance = Activator.CreateInstance(type, parameters); + int id = _nextId++; + _instances[id] = instance; + return id; + } + catch + { + return -1; + } + } + + // Method to release the instance + public static bool ReleaseInstance(int id) + { + return _instances.Remove(id); + } + + // Helper to invoke methods on instances + public static bool InvokeMethod(int id, string methodName, object[] parameters) + { + if (!_instances.TryGetValue(id, out object instance)) + return false; + + try + { + instance.GetType().GetMethod(methodName).Invoke(instance, parameters); + return true; + } + catch + { + return false; + } + } + } +} From 2e8458f0e6b634b4c93286835bdecf51605fc162 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 02:18:11 +0900 Subject: [PATCH 368/450] feat(scripting): can get c# function ptr + fix invalid fptr --- engine/src/scripting/native/Scripting.cpp | 84 ++++++++++++----------- engine/src/scripting/native/Scripting.hpp | 61 ++++++++++++++-- 2 files changed, 102 insertions(+), 43 deletions(-) diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index ed71124a1..7020a6da9 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -42,30 +42,6 @@ #include #include -#ifdef WIN32 -#include - -#define STR(s) L ## s -#define CH(c) L ## c -#define DIR_SEPARATOR L'\\' - -#define string_compare wcscmp - -#else -#include -#include - -#define STR(s) s -#define CH(c) c -#define DIR_SEPARATOR '/' -#define MAX_PATH PATH_MAX - -#define string_compare strcmp - -#endif - -using string_t = std::basic_string; - namespace nexo::scripting { std::shared_ptr shared_library_handle = nullptr; @@ -170,9 +146,9 @@ namespace nexo::scripting { HostHandler::~HostHandler() { - if (shared_library_handle) { + if (m_dll_handle) { if (m_hostfxr_fn.close) { - close_fptr(nullptr); + m_hostfxr_fn.close(nullptr); } } } @@ -184,11 +160,6 @@ namespace nexo::scripting { m_params = std::move(parameters); - if (!m_params.errorCallback) - m_params.errorCallback = DEFAULT_ERROR_CALLBACK; - if (m_params.nexoManagedPath.empty()) - m_params.nexoManagedPath = DEFAULT_NEXO_MANAGED_PATH; - if (loadHostfxr() != SUCCESS) return m_status; @@ -200,6 +171,12 @@ namespace nexo::scripting { if (initRuntime() != SUCCESS) return m_status; + if (getRuntimeDelegates() != SUCCESS) + return m_status; + + if (loadManagedAssembly() != SUCCESS) + return m_status; + return m_status = SUCCESS; } @@ -221,7 +198,7 @@ namespace nexo::scripting { // Pre-allocate a large buffer for the path to hostfxr char_t buffer[MAX_PATH]; size_t buffer_size = sizeof(buffer) / sizeof(char_t); - if (int rc = get_hostfxr_path(buffer, &buffer_size, ¶ms)) { + if (unsigned int rc = get_hostfxr_path(buffer, &buffer_size, ¶ms)) { m_params.errorCallback(std::format("Failed to get hostfxr path. Error code 0x{:X}.", rc)); return m_status = HOSTFXR_NOT_FOUND; } @@ -263,11 +240,10 @@ namespace nexo::scripting { const HostString configPath = runtimeConfigPath.c_str(); // Load .NET Core - int rc = init_for_config_fptr(configPath.c_str(), nullptr, &m_host_ctx); - if (rc != 0 || m_host_ctx == nullptr) - { + unsigned int rc = m_hostfxr_fn.init_for_config(configPath.c_str(), nullptr, &m_host_ctx); + if (rc != 0 || m_host_ctx == nullptr) { m_params.errorCallback(std::format("Init failed: 0x{:X}", rc)); - close_fptr(m_host_ctx); + m_hostfxr_fn.close(m_host_ctx); return m_status = INIT_DOTNET_RUNTIME_ERROR; } return m_status = SUCCESS; @@ -275,15 +251,23 @@ namespace nexo::scripting { HostHandler::Status HostHandler::getRuntimeDelegates() { - int rc = 0; + unsigned int rc = 0; - rc = get_delegate_fptr(m_host_ctx, hdt_load_assembly, reinterpret_cast(&m_delegates.load_assembly)); + rc = m_hostfxr_fn.get_delegate(m_host_ctx, hdt_load_assembly, reinterpret_cast(&m_delegates.load_assembly)); if (rc != 0 || m_delegates.load_assembly == nullptr) { m_params.errorCallback(std::format("Failed to get 'load_assembly' delegate: 0x{:X}", rc)); return m_status = GET_DELEGATES_ERROR; } - rc = get_delegate_fptr(m_host_ctx, hdt_get_function_pointer, reinterpret_cast(&m_delegates.get_function_pointer)); + rc = m_hostfxr_fn.get_delegate(m_host_ctx, hdt_load_assembly_and_get_function_pointer, + reinterpret_cast(&m_delegates.load_assembly_and_get_function_pointer)); + if (rc != 0 || m_delegates.load_assembly_and_get_function_pointer == nullptr) { + m_params.errorCallback(std::format("Failed to get 'load_assembly_and_get_function_pointer' delegate: 0x{:X}", rc)); + return m_status = GET_DELEGATES_ERROR; + } + + rc = m_hostfxr_fn.get_delegate(m_host_ctx, hdt_get_function_pointer, + reinterpret_cast(&m_delegates.get_function_pointer)); if (rc != 0 || m_delegates.get_function_pointer == nullptr) { m_params.errorCallback(std::format("Failed to get 'get_function_pointer' delegate: 0x{:X}", rc)); return m_status = GET_DELEGATES_ERROR; @@ -291,6 +275,28 @@ namespace nexo::scripting { return m_status = SUCCESS; } + HostHandler::Status HostHandler::loadManagedAssembly() + { + unsigned int rc = 0; + + const std::filesystem::path assemblyPath = + m_params.nexoManagedPath / NEXO_ASSEMBLY_FILENAME; + + if (!std::filesystem::exists(assemblyPath)) { + m_params.errorCallback(std::format("Nexo assembly file not found: {}", assemblyPath.string())); + return m_status = ASSEMBLY_NOT_FOUND; + } + + const HostString assemblyPathStr = assemblyPath.c_str(); + + rc = m_delegates.load_assembly(assemblyPathStr.c_str(), nullptr, nullptr); + if (rc != 0) { + m_params.errorCallback(std::format("Failed to load assembly at {}: 0x{:X}", assemblyPathStr.to_utf8(), static_cast(rc))); + return m_status = LOAD_ASSEMBLY_ERROR; + } + return m_status = SUCCESS; + } + /******************************************************************************************** * Function used to load and activate .NET Core ********************************************************************************************/ diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index 798d227f0..90598fe83 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -23,6 +23,28 @@ #include "HostString.hpp" #include "Path.hpp" +#ifdef WIN32 + #include + + #define STR(s) L ## s + #define CH(c) L ## c + #define DIR_SEPARATOR L'\\' + + #define string_compare wcscmp + +#else + #include + #include + + #define STR(s) s + #define CH(c) c + #define DIR_SEPARATOR '/' + #define MAX_PATH PATH_MAX + + #define string_compare strcmp + +#endif + namespace nexo::scripting { using string_t = std::basic_string; @@ -53,6 +75,7 @@ namespace nexo::scripting { struct CoreclrDelegate { load_assembly_fn load_assembly; + load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer; get_function_pointer_fn get_function_pointer; }; @@ -65,8 +88,9 @@ namespace nexo::scripting { // Globals static inline const std::filesystem::path DEFAULT_NEXO_MANAGED_PATH = Path::resolvePathRelativeToExe("../engine/src/scripting/managed/bin/Debug/net9.0/"); // TODO: Change it later for packing - static inline const std::string NEXO_RUNTIMECONFIG_FILENAME = "Nexo.runtimeconfig.json"; - static inline const ErrorCallBackFn DEFAULT_ERROR_CALLBACK = HostHandler::defaultErrorCallback; + static inline const std::string NEXO_RUNTIMECONFIG_FILENAME = "Nexo.runtimeconfig.json"; + static inline const std::string NEXO_ASSEMBLY_FILENAME = "Nexo.dll"; + static inline const ErrorCallBackFn DEFAULT_ERROR_CALLBACK = HostHandler::defaultErrorCallback; protected: // Singleton: protected constructor and destructor @@ -88,12 +112,18 @@ namespace nexo::scripting { enum Status { SUCCESS, UNINITIALIZED, + HOSTFXR_NOT_FOUND, HOSTFXR_LOAD_ERROR, + RUNTIME_CONFIG_NOT_FOUND, INIT_DOTNET_RUNTIME_ERROR, + GET_DELEGATES_ERROR, + ASSEMBLY_NOT_FOUND, + LOAD_ASSEMBLY_ERROR, + }; @@ -103,19 +133,42 @@ namespace nexo::scripting { std::filesystem::path dotnetRoot; // Nexo - std::filesystem::path nexoManagedPath; + std::filesystem::path nexoManagedPath = DEFAULT_NEXO_MANAGED_PATH; - ErrorCallBackFn errorCallback; + ErrorCallBackFn errorCallback = DEFAULT_ERROR_CALLBACK; }; static inline ErrorCallBackFn currentErrorCallback = nullptr; Status initialize(Parameters parameters); + template + T getManagedFptr(const char_t *typeName, const char_t *methodName, const char_t *delegateTypeName) + { + if (m_status != SUCCESS) { + m_params.errorCallback("getManagedFptr: HostHandler not initialized"); + return nullptr; + } + void *fptr = nullptr; + unsigned int rc = m_delegates.get_function_pointer(typeName, methodName, delegateTypeName, m_host_ctx, nullptr, &fptr); + if (rc != 0 || fptr == nullptr) { + m_params.errorCallback(std::format("Failed to get function pointer Type({}) Method({}): 0x{:X}", + typeName ? HostString(typeName).to_utf8() : "", + methodName ? HostString(methodName).to_utf8() : "", + rc) + ); + return nullptr; + } + return reinterpret_cast(fptr); + } + protected: Status loadHostfxr(); Status initRuntime(); Status getRuntimeDelegates(); + Status loadManagedAssembly(); + + protected: Status m_status = UNINITIALIZED; From bb1c78f38f96620f97ef34573f80c7e6d8348f5e Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 02:18:33 +0900 Subject: [PATCH 369/450] feat(scripting): add example in main --- editor/main.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/editor/main.cpp b/editor/main.cpp index 4e726b76e..83a1ecce6 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -24,6 +24,84 @@ #include #include +#include "Path.hpp" +#include "scripting/native/Scripting.hpp" + +int runScriptExample(const nexo::scripting::HostHandler::Parameters& params) +{ + // Get the instance of the HostHandler singleton + nexo::scripting::HostHandler& host = nexo::scripting::HostHandler::getInstance(); + + // Initialize the host + nexo::scripting::HostHandler::Status status = host.initialize(params); + if (status != nexo::scripting::HostHandler::SUCCESS) { + return EXIT_FAILURE; + } + + // Get function pointers to managed methods + // Regular method + component_entry_point_fn hello = host.getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("Hello"), + nullptr + ); + + if (hello == nullptr) { + return EXIT_FAILURE; + } + + // Run managed code + struct lib_args { + const char_t* message; + int number; + }; + + // Call the Hello method multiple times + for (int i = 0; i < 3; ++i) { + lib_args args { + STR("from host!"), + i + }; + + hello(&args, sizeof(args)); + } + + // Get function pointer for UnmanagedCallersOnly method + typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_fn)(lib_args args); + custom_entry_point_fn custom_unmanaged = host.getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("CustomEntryPointUnmanagedCallersOnly"), + UNMANAGEDCALLERSONLY_METHOD + ); + + if (custom_unmanaged == nullptr) { + return EXIT_FAILURE; + } + + // Call UnmanagedCallersOnly method + lib_args args_unmanaged { + STR("from host!"), + -1 + }; + custom_unmanaged(args_unmanaged); + + // Get function pointer for custom delegate type method + custom_entry_point_fn custom = host.getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("CustomEntryPoint"), + STR("Nexo.Lib+CustomEntryPointDelegate, Nexo") + ); + + if (custom == nullptr) { + return EXIT_FAILURE; + } + + // Call custom delegate type method + custom(args_unmanaged); + + return EXIT_SUCCESS; +} + int main(int argc, char **argv) try { loguru::init(argc, argv); @@ -44,6 +122,16 @@ try { editor.init(); + /*if (int rc = nexo::scripting::load_hostfxr() != 0) { + LOG(NEXO_ERROR, "Failed to load hostfxr error code {}", rc); + }*/ + const nexo::scripting::HostHandler::Parameters params = { + .errorCallback = [](const nexo::scripting::HostString& message) { + LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); + }, + }; + runScriptExample(params); + while (editor.isOpen()) { auto start = std::chrono::high_resolution_clock::now(); From 1089ea2995d12288ab201aa1ecc674d6fcdaf378 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 03:00:59 +0900 Subject: [PATCH 370/450] feat(scripting): refactor runScriptExample and move to Scripting.cpp --- editor/main.cpp | 77 +------ engine/src/scripting/native/Scripting.cpp | 253 ++++++---------------- engine/src/scripting/native/Scripting.hpp | 25 +-- 3 files changed, 79 insertions(+), 276 deletions(-) diff --git a/editor/main.cpp b/editor/main.cpp index 83a1ecce6..f55183899 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -27,81 +27,6 @@ #include "Path.hpp" #include "scripting/native/Scripting.hpp" -int runScriptExample(const nexo::scripting::HostHandler::Parameters& params) -{ - // Get the instance of the HostHandler singleton - nexo::scripting::HostHandler& host = nexo::scripting::HostHandler::getInstance(); - - // Initialize the host - nexo::scripting::HostHandler::Status status = host.initialize(params); - if (status != nexo::scripting::HostHandler::SUCCESS) { - return EXIT_FAILURE; - } - - // Get function pointers to managed methods - // Regular method - component_entry_point_fn hello = host.getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("Hello"), - nullptr - ); - - if (hello == nullptr) { - return EXIT_FAILURE; - } - - // Run managed code - struct lib_args { - const char_t* message; - int number; - }; - - // Call the Hello method multiple times - for (int i = 0; i < 3; ++i) { - lib_args args { - STR("from host!"), - i - }; - - hello(&args, sizeof(args)); - } - - // Get function pointer for UnmanagedCallersOnly method - typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_fn)(lib_args args); - custom_entry_point_fn custom_unmanaged = host.getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("CustomEntryPointUnmanagedCallersOnly"), - UNMANAGEDCALLERSONLY_METHOD - ); - - if (custom_unmanaged == nullptr) { - return EXIT_FAILURE; - } - - // Call UnmanagedCallersOnly method - lib_args args_unmanaged { - STR("from host!"), - -1 - }; - custom_unmanaged(args_unmanaged); - - // Get function pointer for custom delegate type method - custom_entry_point_fn custom = host.getManagedFptr( - STR("Nexo.Lib, Nexo"), - STR("CustomEntryPoint"), - STR("Nexo.Lib+CustomEntryPointDelegate, Nexo") - ); - - if (custom == nullptr) { - return EXIT_FAILURE; - } - - // Call custom delegate type method - custom(args_unmanaged); - - return EXIT_SUCCESS; -} - int main(int argc, char **argv) try { loguru::init(argc, argv); @@ -130,7 +55,7 @@ try { LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); }, }; - runScriptExample(params); + nexo::scripting::runScriptExample(params); while (editor.isOpen()) { diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 7020a6da9..f7c6e2d20 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -12,137 +12,15 @@ // /////////////////////////////////////////////////////////////////////////////// -#include +#include +#include +#include #include "Scripting.hpp" #include "HostString.hpp" #include "Logger.hpp" - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// Standard headers -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Provided by the AppHost NuGet package and installed as an SDK pack -#include - -// Header files copied from https://github.com/dotnet/core-setup -#include -#include -#include - namespace nexo::scripting { - std::shared_ptr shared_library_handle = nullptr; - - hostfxr_initialize_for_dotnet_command_line_fn init_for_cmd_line_fptr = nullptr; - hostfxr_initialize_for_runtime_config_fn init_for_config_fptr = nullptr; - hostfxr_get_runtime_delegate_fn get_delegate_fptr = nullptr; - hostfxr_run_app_fn run_app_fptr = nullptr; - hostfxr_close_fn close_fptr = nullptr; - - int run_component_example(const HostString& root_path) - { - // - // STEP 1: Load HostFxr and get exported hosting functions - // - if (!load_hostfxr()) - { - assert(false && "Failure: load_hostfxr()"); - return EXIT_FAILURE; - } - - // - // STEP 2: Initialize and start the .NET Core runtime - // - const HostString config_path = root_path + STR("Nexo.runtimeconfig.json"); - load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = nullptr; - load_assembly_and_get_function_pointer = get_dotnet_load_assembly(config_path.c_str()); - assert(load_assembly_and_get_function_pointer != nullptr && "Failure: get_dotnet_load_assembly()"); - - // - // STEP 3: Load managed assembly and get function pointer to a managed method - // - const HostString dotnetlib_path = root_path + STR("Nexo.dll"); - const char_t *dotnet_type = STR("Nexo.Lib, Nexo"); - const char_t *dotnet_type_method = STR("Hello"); - // - // Function pointer to managed delegate - component_entry_point_fn hello = nullptr; - int rc = load_assembly_and_get_function_pointer( - dotnetlib_path.c_str(), - dotnet_type, - dotnet_type_method, - nullptr /*delegate_type_name*/, - nullptr, - (void**)&hello); - // - assert(rc == 0 && hello != nullptr && "Failure: load_assembly_and_get_function_pointer()"); - - // - // STEP 4: Run managed code - // - struct lib_args - { - const char_t *message; - int number; - }; - for (int i = 0; i < 3; ++i) - { - // - lib_args args - { - STR("from host!"), - i - }; - - hello(&args, sizeof(args)); - // - } - - // Function pointer to managed delegate with non-default signature - typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_fn)(lib_args args); - custom_entry_point_fn custom = nullptr; - lib_args args - { - STR("from host!"), - -1 - }; - - // UnmanagedCallersOnly - rc = load_assembly_and_get_function_pointer( - dotnetlib_path.c_str(), - dotnet_type, - STR("CustomEntryPointUnmanagedCallersOnly") /*method_name*/, - UNMANAGEDCALLERSONLY_METHOD, - nullptr, - (void**)&custom); - assert(rc == 0 && custom != nullptr && "Failure: load_assembly_and_get_function_pointer()"); - custom(args); - - // Custom delegate type - rc = load_assembly_and_get_function_pointer( - dotnetlib_path.c_str(), - dotnet_type, - STR("CustomEntryPoint") /*method_name*/, - STR("Nexo.Lib+CustomEntryPointDelegate, Nexo") /*delegate_type_name*/, - nullptr, - (void**)&custom); - assert(rc == 0 && custom != nullptr && "Failure: load_assembly_and_get_function_pointer()"); - custom(args); - - return EXIT_SUCCESS; - } HostHandler::~HostHandler() { @@ -291,75 +169,86 @@ namespace nexo::scripting { rc = m_delegates.load_assembly(assemblyPathStr.c_str(), nullptr, nullptr); if (rc != 0) { - m_params.errorCallback(std::format("Failed to load assembly at {}: 0x{:X}", assemblyPathStr.to_utf8(), static_cast(rc))); + m_params.errorCallback(std::format("Failed to load assembly at {}: 0x{:X}", assemblyPathStr.to_utf8(), rc)); return m_status = LOAD_ASSEMBLY_ERROR; } + m_assembly_path = assemblyPathStr; return m_status = SUCCESS; } -/******************************************************************************************** - * Function used to load and activate .NET Core - ********************************************************************************************/ - // - // Using the nethost library, discover the location of hostfxr and get exports - bool load_hostfxr(const HostString& assembly_path /* = "" */) + int runScriptExample(const HostHandler::Parameters& params) { - get_hostfxr_parameters params { - sizeof(get_hostfxr_parameters), - assembly_path.empty() ? nullptr : assembly_path.c_str(), - nullptr }; - // Pre-allocate a large buffer for the path to hostfxr - char_t buffer[MAX_PATH]; - size_t buffer_size = sizeof(buffer) / sizeof(char_t); - int rc = get_hostfxr_path(buffer, &buffer_size, ¶ms); - if (rc != 0) - return false; - - // Load hostfxr and get desired exports - // NOTE: The .NET Runtime does not support unloading any of its native libraries. Running - // dlclose/FreeLibrary on any .NET libraries produces undefined behavior. - shared_library_handle = std::make_shared(buffer); - if (shared_library_handle == nullptr) - { - std::cerr << "Failed to load hostfxr library." << std::endl; - return false; + // Get the instance of the HostHandler singleton + HostHandler& host = HostHandler::getInstance(); + + // Initialize the host + HostHandler::Status status = host.initialize(params); + if (status != HostHandler::SUCCESS) { + return EXIT_FAILURE; } - init_for_cmd_line_fptr = shared_library_handle->get>("hostfxr_initialize_for_dotnet_command_line"); - init_for_config_fptr = shared_library_handle->get>("hostfxr_initialize_for_runtime_config"); - get_delegate_fptr = shared_library_handle->get>("hostfxr_get_runtime_delegate"); - run_app_fptr = shared_library_handle->get>("hostfxr_run_app"); - close_fptr = shared_library_handle->get>("hostfxr_close"); + // Get function pointers to managed methods + // Regular method + component_entry_point_fn hello = host.getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("Hello"), + nullptr + ); - return (init_for_cmd_line_fptr && init_for_config_fptr && get_delegate_fptr && run_app_fptr && close_fptr); - } - // + if (hello == nullptr) { + return EXIT_FAILURE; + } - // - // Load and initialize .NET Core and get desired function pointer for scenario - load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const HostString& config_path) - { - // Load .NET Core - void *load_assembly_and_get_function_pointer = nullptr; - hostfxr_handle cxt = nullptr; - int rc = init_for_config_fptr(config_path.c_str(), nullptr, &cxt); - if (rc != 0 || cxt == nullptr) - { - std::cerr << "Init failed: " << std::hex << std::showbase << rc << std::endl; - close_fptr(cxt); - return nullptr; + // Run managed code + struct lib_args { + const char_t* message; + int number; + }; + + // Call the Hello method multiple times + for (int i = 0; i < 3; ++i) { + lib_args args { + STR("from host!"), + i + }; + + hello(&args, sizeof(args)); + } + + // Get function pointer for UnmanagedCallersOnly method + typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_fn)(lib_args args); + custom_entry_point_fn custom_unmanaged = host.getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("CustomEntryPointUnmanagedCallersOnly"), + UNMANAGEDCALLERSONLY_METHOD + ); + + if (custom_unmanaged == nullptr) { + return EXIT_FAILURE; + } + + // Call UnmanagedCallersOnly method + lib_args args_unmanaged { + STR("from host!"), + -1 + }; + custom_unmanaged(args_unmanaged); + + // Get function pointer for custom delegate type method + custom_entry_point_fn custom = host.getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("CustomEntryPoint"), + STR("Nexo.Lib+CustomEntryPointDelegate, Nexo") + ); + + if (custom == nullptr) { + return EXIT_FAILURE; } - // Get the load assembly function pointer - rc = get_delegate_fptr( - cxt, - hdt_load_assembly_and_get_function_pointer, - &load_assembly_and_get_function_pointer); - if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) - std::cerr << "Get delegate failed: " << std::hex << std::showbase << rc << std::endl; + // Call custom delegate type method + custom(args_unmanaged); - close_fptr(cxt); - return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer; + return EXIT_SUCCESS; } - // + } diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index 90598fe83..7d5fcc588 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -47,23 +47,6 @@ namespace nexo::scripting { - using string_t = std::basic_string; - - extern std::shared_ptr shared_library_handle; - - // Function pointers for hostfxr functions - extern hostfxr_initialize_for_dotnet_command_line_fn init_for_cmd_line_fptr; - extern hostfxr_initialize_for_runtime_config_fn init_for_config_fptr; - extern hostfxr_get_runtime_delegate_fn get_delegate_fptr; - extern hostfxr_run_app_fn run_app_fptr; - extern hostfxr_close_fn close_fptr; - - // Function to load the hostfxr library and retrieve function pointers - bool load_hostfxr(const HostString& assembly_path = ""); - load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const HostString& assembly); - - int run_component_example(const HostString& root_path); - struct HostfxrFn { hostfxr_set_error_writer_fn set_error_writer; hostfxr_initialize_for_dotnet_command_line_fn init_for_cmd_line; @@ -148,8 +131,10 @@ namespace nexo::scripting { m_params.errorCallback("getManagedFptr: HostHandler not initialized"); return nullptr; } + void *fptr = nullptr; - unsigned int rc = m_delegates.get_function_pointer(typeName, methodName, delegateTypeName, m_host_ctx, nullptr, &fptr); + unsigned int rc = m_delegates.load_assembly_and_get_function_pointer( + m_assembly_path.c_str(), typeName, methodName, delegateTypeName, nullptr, &fptr); if (rc != 0 || fptr == nullptr) { m_params.errorCallback(std::format("Failed to get function pointer Type({}) Method({}): 0x{:X}", typeName ? HostString(typeName).to_utf8() : "", @@ -173,6 +158,7 @@ namespace nexo::scripting { protected: Status m_status = UNINITIALIZED; Parameters m_params; + HostString m_assembly_path; HostfxrFn m_hostfxr_fn = {}; CoreclrDelegate m_delegates = {}; @@ -181,4 +167,7 @@ namespace nexo::scripting { hostfxr_handle m_host_ctx = nullptr; }; + + int runScriptExample(const HostHandler::Parameters& params); + } // namespace nexo::scripting From 6db4c7fa7380db804a1d0763f67d41e1c0c6b501 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 04:21:10 +0900 Subject: [PATCH 371/450] feat(scripting): add more interop tests with Add and AddToPtr functions --- editor/main.cpp | 6 ++++- engine/src/scripting/managed/Lib.cs | 18 ++++++++++++++ engine/src/scripting/native/Scripting.cpp | 30 +++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/editor/main.cpp b/editor/main.cpp index f55183899..3af510f17 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -55,7 +55,11 @@ try { LOG(NEXO_ERROR, "Scripting host error: {}", message.to_utf8()); }, }; - nexo::scripting::runScriptExample(params); + if (nexo::scripting::runScriptExample(params) == EXIT_FAILURE) { + LOG(NEXO_ERROR, "Error in runScriptExample"); + } else { + LOG(NEXO_INFO, "Successfully ran runScriptExample"); + } while (editor.isOpen()) { diff --git a/engine/src/scripting/managed/Lib.cs b/engine/src/scripting/managed/Lib.cs index 621cf3f68..0c1236edf 100644 --- a/engine/src/scripting/managed/Lib.cs +++ b/engine/src/scripting/managed/Lib.cs @@ -27,6 +27,24 @@ public static int Hello(IntPtr arg, int argLength) return 0; } + [UnmanagedCallersOnly] + public static int Add(int a, int b) + { + return a + b; + } + + [UnmanagedCallersOnly] + public static int AddToPtr(int a, int b, IntPtr result) + { + if (result == IntPtr.Zero) + { + return 1; + } + + Marshal.WriteInt32(result, a + b); + return 0; + } + public delegate void CustomEntryPointDelegate(LibArgs libArgs); public static void CustomEntryPoint(LibArgs libArgs) { diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index f7c6e2d20..342d5f657 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -248,6 +248,36 @@ namespace nexo::scripting { // Call custom delegate type method custom(args_unmanaged); + typedef int32_t (CORECLR_DELEGATE_CALLTYPE *add_fn)(int32_t a, int32_t b); + add_fn add = host.getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("Add"), + UNMANAGEDCALLERSONLY_METHOD + ); + if (add == nullptr) { + return EXIT_FAILURE; + } + + std::cout << "Testing Add(30, -10) = " << add(30, -10) << std::endl; + + typedef int32_t (CORECLR_DELEGATE_CALLTYPE *add_to_ptr_fn)(int32_t a, int32_t b, int32_t *result); + add_to_ptr_fn addToPtr = host.getManagedFptr( + STR("Nexo.Lib, Nexo"), + STR("AddToPtr"), + UNMANAGEDCALLERSONLY_METHOD + ); + if (addToPtr == nullptr) { + return EXIT_FAILURE; + } + + int32_t result = 0; + if (addToPtr(1000, 234, &result)) { + std::cout << "addToPtr returned an error" << std::endl; + } else { + std::cout << "Testing AddToPtr(1000, 234, ptr), *ptr = " << result << std::endl; + } + + return EXIT_SUCCESS; } From 2acf6b193dd1646ac6836b48b03924f1b25552ef Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 04:35:14 +0900 Subject: [PATCH 372/450] fix(scripting): add missing headers --- engine/src/scripting/native/HostString.cpp | 1 + engine/src/scripting/native/Scripting.hpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/engine/src/scripting/native/HostString.cpp b/engine/src/scripting/native/HostString.cpp index 2e5a0a981..931bf0712 100644 --- a/engine/src/scripting/native/HostString.cpp +++ b/engine/src/scripting/native/HostString.cpp @@ -13,6 +13,7 @@ /////////////////////////////////////////////////////////////////////////////// #include +#include #include "HostString.hpp" diff --git a/engine/src/scripting/native/Scripting.hpp b/engine/src/scripting/native/Scripting.hpp index 7d5fcc588..9c1189c8a 100644 --- a/engine/src/scripting/native/Scripting.hpp +++ b/engine/src/scripting/native/Scripting.hpp @@ -14,9 +14,11 @@ #pragma once +#include +#include + #include #include -#include #include #include From 495290b2161cd4ecea555ae8731725ba2c465ee2 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 18:02:22 +0900 Subject: [PATCH 373/450] fix(scripting): fix segv on boost::dll, it was using the wrong constructor --- engine/src/scripting/native/Scripting.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/engine/src/scripting/native/Scripting.cpp b/engine/src/scripting/native/Scripting.cpp index 342d5f657..168d434da 100644 --- a/engine/src/scripting/native/Scripting.cpp +++ b/engine/src/scripting/native/Scripting.cpp @@ -65,8 +65,8 @@ namespace nexo::scripting { HostHandler::Status HostHandler::loadHostfxr() { - auto assemblyPath = HostString(m_params.assemblyPath.c_str()); - auto dotnetRoot = HostString(m_params.dotnetRoot.c_str()); + const auto assemblyPath = HostString(m_params.assemblyPath.c_str()); + const auto dotnetRoot = HostString(m_params.dotnetRoot.c_str()); get_hostfxr_parameters params { sizeof(get_hostfxr_parameters), @@ -74,16 +74,15 @@ namespace nexo::scripting { dotnetRoot.empty() ? nullptr : dotnetRoot.c_str() }; // Pre-allocate a large buffer for the path to hostfxr - char_t buffer[MAX_PATH]; + char_t buffer[MAX_PATH] = {0}; size_t buffer_size = sizeof(buffer) / sizeof(char_t); if (unsigned int rc = get_hostfxr_path(buffer, &buffer_size, ¶ms)) { m_params.errorCallback(std::format("Failed to get hostfxr path. Error code 0x{:X}.", rc)); return m_status = HOSTFXR_NOT_FOUND; } - m_dll_handle = std::make_shared(buffer); - if (m_dll_handle == nullptr) - { + m_dll_handle = std::make_shared(buffer, boost::dll::load_mode::default_mode); + if (m_dll_handle == nullptr || !m_dll_handle->is_loaded()) { m_params.errorCallback(std::format("Failed to load hostfxr library from path: {}", HostString(buffer).to_utf8())); return m_status = HOSTFXR_LOAD_ERROR; } From 16b17b76ddddbb15ca5416f57644d3bd03a3e798 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 18:02:45 +0900 Subject: [PATCH 374/450] feat(scripting): add .clang-format --- .clang-format | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..b50f575c7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,79 @@ +--- +Language: Cpp +BasedOnStyle: Google +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: false +AlignOperands: true +AlignTrailingComments: true +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: None +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: BeforeColon +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ContinuationIndentWidth: 4 +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^<.*' + Priority: 1 + - Regex: '^".*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentAccessModifiers: True +IndentCaseBlocks: false +IndentCaseLabels: true +IndentGotoLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertNewlineAtEOF: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never +... From 32a712ef47a87b2a749b6d6a3556cf90659ab66a Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 18:03:09 +0900 Subject: [PATCH 375/450] feat(scripting): add README.md at scripting root to explain setup --- engine/src/scripting/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 engine/src/scripting/README.md diff --git a/engine/src/scripting/README.md b/engine/src/scripting/README.md new file mode 100644 index 000000000..91daea587 --- /dev/null +++ b/engine/src/scripting/README.md @@ -0,0 +1,27 @@ +# How to use scripting + +## Dependencies + +You first need to install .NET 9.0 SDK. You can download it from the official Microsoft website. + +## Build the managed C# NEXO Library + +1. Open a terminal and navigate to the `engine/src/scripting/managed` directory. +2. Run the following command to build the C# NEXO library: + +```bash +dotnet build +``` + +This will create a `Nexo.dll` file in the `bin/Debug/net9.0` directory. +It is important that the `Nexo.dll` file is in this directory, as the C++ code will look for it there. + +TODO: this should be done automatically when building the project, for now it's done manually. + +## Build the C++ NEXO Library + +The scripting C++ library is built using CMake when you build the project. + +## Run the project + +When running `nexoEditor` the script samples are automatically loaded and executed. You should see the output in the console. From c1601be161b16a71b6abd224225200c1fc524e86 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Mon, 5 May 2025 18:09:53 +0900 Subject: [PATCH 376/450] feat(scripting): add TODO for vulnerability in openFolder --- editor/src/utils/FileSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/utils/FileSystem.cpp b/editor/src/utils/FileSystem.cpp index 0fa55a28d..341fd4ee7 100644 --- a/editor/src/utils/FileSystem.cpp +++ b/editor/src/utils/FileSystem.cpp @@ -21,7 +21,7 @@ namespace nexo::editor::utils { ShellExecuteA(nullptr, "open", folderPath.c_str(), nullptr, nullptr, SW_SHOWDEFAULT); #else const std::string command = "xdg-open " + folderPath; - std::system(command.c_str()); + std::system(command.c_str()); // TODO: replace this system with safer commands, this is vulnerable to user injections #endif } } From b3ff615d38e39af62fdb4d5900ad38e7f80b01a3 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 6 May 2025 04:31:39 +0900 Subject: [PATCH 377/450] fix(scripting): invalid target for link --- engine/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index edce59a3e..2f86d2fbb 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -100,7 +100,7 @@ target_include_directories(nexoRenderer PUBLIC # loguru find_package(loguru CONFIG REQUIRED) -target_link_libraries(nexoEditor PRIVATE loguru::loguru) +target_link_libraries(nexoRenderer PRIVATE loguru::loguru) # Stb find_package(Stb REQUIRED) From db321889c348ef79729b2eb954b70da425c1ead7 Mon Sep 17 00:00:00 2001 From: Thyodas Date: Tue, 6 May 2025 04:33:36 +0900 Subject: [PATCH 378/450] feat(scripting): add native function call from C# and improve error handling --- CMakeLists.txt | 6 + engine/src/scripting/CMakeLists.txt | 25 ++++ engine/src/scripting/managed/CMakeLists.txt | 48 +++++++ engine/src/scripting/managed/Lib.cs | 31 ++++- engine/src/scripting/managed/NativeInterop.cs | 123 ++++++++++++++++++ engine/src/scripting/managed/ObjectFactory.cs | 22 +++- engine/src/scripting/native/NativeApi.cpp | 42 ++++++ engine/src/scripting/native/NativeApi.hpp | 47 +++++++ engine/src/scripting/native/Scripting.cpp | 20 +++ engine/src/scripting/native/Scripting.hpp | 5 +- 10 files changed, 358 insertions(+), 11 deletions(-) create mode 100644 engine/src/scripting/CMakeLists.txt create mode 100644 engine/src/scripting/managed/CMakeLists.txt create mode 100644 engine/src/scripting/managed/NativeInterop.cs create mode 100644 engine/src/scripting/native/NativeApi.cpp create mode 100644 engine/src/scripting/native/NativeApi.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b810efa9..593503346 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,12 @@ message(STATUS "VCPKG done.") include("${CMAKE_CURRENT_SOURCE_DIR}/editor/CMakeLists.txt") # SETUP ENGINE include("${CMAKE_CURRENT_SOURCE_DIR}/engine/CMakeLists.txt") +# SETUP ENGINE API +include("${CMAKE_CURRENT_SOURCE_DIR}/engine/src/scripting/CMakeLists.txt") +add_dependencies(nexoEditor nexoApi) +# SETUP MANAGED CSHARP LIB +include("${CMAKE_CURRENT_SOURCE_DIR}/engine/src/scripting/managed/CMakeLists.txt") +add_dependencies(nexoEditor nexoManaged) # SETUP EXAMPLE include("${CMAKE_CURRENT_SOURCE_DIR}/examples/CMakeLists.txt") # SETUP TESTS diff --git a/engine/src/scripting/CMakeLists.txt b/engine/src/scripting/CMakeLists.txt new file mode 100644 index 000000000..00a0cda52 --- /dev/null +++ b/engine/src/scripting/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) + +# Set project name +project(nexoApi VERSION 1.0.0) + +# Specify the C++ standard +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# Add source files +set(COMMON_SOURCES + engine/src/scripting/native/NativeApi.cpp +) + +# Create the library +add_library(nexoApi SHARED ${COMMON_SOURCES}) + +# Add include directories for this target +target_include_directories(nexoApi PUBLIC + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_SOURCE_DIR}/engine/src + ${CMAKE_SOURCE_DIR}/common) + +target_link_libraries(nexoApi PRIVATE nexoRenderer) +target_compile_definitions(nexoApi PRIVATE NEXO_EXPORT) diff --git a/engine/src/scripting/managed/CMakeLists.txt b/engine/src/scripting/managed/CMakeLists.txt new file mode 100644 index 000000000..62bd96984 --- /dev/null +++ b/engine/src/scripting/managed/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.20) + +# Set project name +project(nexoManaged LANGUAGES NONE) + +# Define variables for configuration +set(NEXO_MANAGED_OUTPUT_DIR ${CMAKE_BINARY_DIR}) +set(NEXO_FRAMEWORK "net9.0") +set(NEXO_PLATFORM "x64") + +set(SOURCES + Lib.cs + ObjectFactory.cs + NativeInterop.cs +) + +# Locate the dotnet executable +find_program(DOTNET_EXECUTABLE dotnet HINTS ENV PATH) + +if(NOT DOTNET_EXECUTABLE) + message(FATAL_ERROR "The .NET SDK (dotnet CLI) is not installed or not in the PATH. Please install it from https://dotnet.microsoft.com/download.") +endif() + +message(STATUS "Using dotnet executable: ${DOTNET_EXECUTABLE}") + +# Generate Nexo.csproj from a template (Nexo.csproj.in) +configure_file( + ${CMAKE_CURRENT_LIST_DIR}/Nexo.csproj.in # Input template + ${CMAKE_CURRENT_LIST_DIR}/Nexo.csproj # Output file + @ONLY # Only replace variables in @VAR@ format +) + +# Build step +add_custom_target(nexoManaged ALL + COMMAND ${DOTNET_EXECUTABLE} build Nexo.csproj + -c $ # Matches Debug/Release configuration + --output ${NEXO_MANAGED_OUTPUT_DIR} # Custom output directory + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} # Working directory for the build + COMMENT "Building .NET managed project (Nexo.csproj)..." +) + +# Clean step +set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES + ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.dll + ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.pdb + ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.runtimeconfig.json + ${NEXO_MANAGED_OUTPUT_DIR}/Nexo.deps.json +) diff --git a/engine/src/scripting/managed/Lib.cs b/engine/src/scripting/managed/Lib.cs index 0c1236edf..55be71354 100644 --- a/engine/src/scripting/managed/Lib.cs +++ b/engine/src/scripting/managed/Lib.cs @@ -5,7 +5,7 @@ namespace Nexo { public static class Lib { - private static int s_CallCount = 1; + private static int _sCallCount = 1; [StructLayout(LayoutKind.Sequential)] public struct LibArgs @@ -22,7 +22,7 @@ public static int Hello(IntPtr arg, int argLength) } LibArgs libArgs = Marshal.PtrToStructure(arg); - Console.WriteLine($"Hello, world! from {nameof(Lib)} [count: {s_CallCount++}]"); + Console.WriteLine($"Hello, world! from {nameof(Lib)} [count: {_sCallCount++}]"); PrintLibArgs(libArgs); return 0; } @@ -45,6 +45,29 @@ public static int AddToPtr(int a, int b, IntPtr result) return 0; } + [UnmanagedCallersOnly] + public static int AddNexoDllDirectory(IntPtr pPathString) + { + if (pPathString == IntPtr.Zero) + { + return 1; + } + string? pathString = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Marshal.PtrToStringUni(pPathString) + : Marshal.PtrToStringUTF8(pPathString); + if (string.IsNullOrEmpty(pathString)) + { + return 1; + } + string? path = System.IO.Path.GetDirectoryName(pathString); + if (string.IsNullOrEmpty(path)) + { + return 1; + } + + return 0; + } + public delegate void CustomEntryPointDelegate(LibArgs libArgs); public static void CustomEntryPoint(LibArgs libArgs) { @@ -61,9 +84,11 @@ public static void CustomEntryPointUnmanagedCallersOnly(LibArgs libArgs) private static void PrintLibArgs(LibArgs libArgs) { - string message = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + string? message = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Marshal.PtrToStringUni(libArgs.Message) : Marshal.PtrToStringUTF8(libArgs.Message); + + message = message ?? "[ERROR] Could not convert message"; Console.WriteLine($"-- message: {message}"); Console.WriteLine($"-- number: {libArgs.Number}"); diff --git a/engine/src/scripting/managed/NativeInterop.cs b/engine/src/scripting/managed/NativeInterop.cs new file mode 100644 index 000000000..d7f79a51d --- /dev/null +++ b/engine/src/scripting/managed/NativeInterop.cs @@ -0,0 +1,123 @@ +using System; +using System.Runtime.InteropServices; + +namespace Nexo +{ + ///

  • %4uRg#V64JmRpaho;x?WE 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

    n_ke}(^V>mi~ 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%0Ad{3co$`un9 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