From 78acb2e9d0d2c70215b5cbda4fc662b62b4f63f3 Mon Sep 17 00:00:00 2001 From: tristanklempka Date: Fri, 26 Jul 2024 17:12:48 +0200 Subject: [PATCH] Rework HierarchySystem -> NodeSystem + Add RenderingSystem --- CMakeLists.txt | 24 +- include/Component.h | 3 +- include/ComponentManager.h | 4 +- include/ComponentManager.tpp | 8 +- include/EntityFactory.h | 30 +++ include/EntityManager.h | 2 +- include/EventDispatcher.h | 3 +- include/{HierarchySystem.h => NodeSystem.h} | 13 +- include/PhysicSystem.h | 40 ++++ include/RenderingSystem.h | 38 +++ include/SystemManager.h | 2 +- include/TransformManager.h | 27 +++ include/World.h | 5 +- include/components/Node.h | 8 +- include/components/{Drawable.h => Shape.h} | 6 +- main.cpp | 50 +++- src/EntityFactory.cpp | 52 ++++ src/HierarchySystem.cpp | 91 ------- src/NodeSystem.cpp | 27 +++ src/RenderingSystem.cpp | 32 +++ src/TransformManager.cpp | 34 +++ src/World.cpp | 15 +- test/ecs_tests.cpp | 248 +++++--------------- 23 files changed, 425 insertions(+), 337 deletions(-) create mode 100644 include/EntityFactory.h rename include/{HierarchySystem.h => NodeSystem.h} (74%) create mode 100644 include/PhysicSystem.h create mode 100644 include/RenderingSystem.h create mode 100644 include/TransformManager.h rename include/components/{Drawable.h => Shape.h} (53%) create mode 100644 src/EntityFactory.cpp delete mode 100644 src/HierarchySystem.cpp create mode 100644 src/NodeSystem.cpp create mode 100644 src/RenderingSystem.cpp create mode 100644 src/TransformManager.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dec4ba0..3c03165 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,12 +69,18 @@ add_executable(Engine main.cpp include/World.tpp src/Window.cpp include/Window.h - include/components/Drawable.h + include/components/Shape.h include/components/Node.h include/EventDispatcher.h - include/HierarchySystem.h - src/HierarchySystem.cpp + include/NodeSystem.h + src/NodeSystem.cpp src/EventDispatcher.cpp + src/RenderingSystem.cpp + include/RenderingSystem.h + include/EntityFactory.h + include/TransformManager.h + src/TransformManager.cpp + src/EntityFactory.cpp ) target_include_directories(Engine PRIVATE include) @@ -103,12 +109,18 @@ add_executable(EngineTests include/World.tpp src/Window.cpp include/Window.h - include/components/Drawable.h + include/components/Shape.h include/components/Node.h include/EventDispatcher.h - include/HierarchySystem.h - src/HierarchySystem.cpp + include/NodeSystem.h + src/NodeSystem.cpp src/EventDispatcher.cpp + src/RenderingSystem.cpp + include/RenderingSystem.h + include/EntityFactory.h + include/TransformManager.h + src/TransformManager.cpp + src/EntityFactory.cpp ) target_include_directories(EngineTests PRIVATE include) diff --git a/include/Component.h b/include/Component.h index 47d2e31..5c16f8d 100644 --- a/include/Component.h +++ b/include/Component.h @@ -8,7 +8,7 @@ #include #include -#include "components/Drawable.h" +#include "components/Shape.h" #include "components/Node.h" #include "components/Velocity.h" @@ -33,6 +33,7 @@ class Component { static_assert(ValidComponent); static_assert(ValidComponent); +static_assert(ValidComponent); #endif //COMPONENT_H diff --git a/include/ComponentManager.h b/include/ComponentManager.h index 5195207..33c29b8 100644 --- a/include/ComponentManager.h +++ b/include/ComponentManager.h @@ -12,7 +12,7 @@ #include // for variant #include "Component.h" // for Component, ValidComponent #include "Entity.h" // for Entity -#include "components/Drawable.h" // for Drawable +#include "components/Shape.h" // for Drawable #include "components/Node.h" // for Node #include "components/Velocity.h" // for Velocity @@ -44,7 +44,7 @@ class ComponentManager { using ComponentVariant = std::variant< std::unique_ptr>, std::unique_ptr>, - std::unique_ptr> + std::unique_ptr> >; std::unordered_map> m_componentMaps; }; diff --git a/include/ComponentManager.tpp b/include/ComponentManager.tpp index 28a0ea0..3c4b10a 100644 --- a/include/ComponentManager.tpp +++ b/include/ComponentManager.tpp @@ -4,7 +4,13 @@ template void ComponentManager::addComponent(const Entity::Id entityId, T component) { - m_componentMaps[entityId][typeid(T)] = std::make_unique>(std::move(component)); + auto& entityComponents = m_componentMaps[entityId]; + const std::type_index typeIndex = typeid(T); + // Check if the component type already exists + if (entityComponents.contains(typeIndex)) { + throw std::runtime_error("Component already exists for this entity"); + } + entityComponents[typeIndex] = std::make_unique>(std::move(component)); } template diff --git a/include/EntityFactory.h b/include/EntityFactory.h new file mode 100644 index 0000000..be67360 --- /dev/null +++ b/include/EntityFactory.h @@ -0,0 +1,30 @@ +// +// Created by Tristan Klempka on 25/07/2024. +// + +#ifndef ENTITYFACTORY_H +#define ENTITYFACTORY_H + +#include // for CircleShape +#include // for Color +#include // for RectangleShape +#include // for Vector2f +#include // for unique_ptr +#include "Entity.h" // for Entity +class World; + +class EntityFactory { +public: + explicit EntityFactory(World& world); + + [[nodiscard]] Entity::Id createCircleEntity(float radius, sf::Color fillColor, sf::Color outlineColor = sf::Color::Transparent, float outlineThickness = 0.0f) const; + [[nodiscard]] Entity::Id createRectangleEntity(const sf::Vector2f& size, const sf::Color& fillColor, const sf::Color& outlineColor = sf::Color::Transparent, float outlineThickness = 0.0f) const; + +private: + World& world; + + static std::unique_ptr createCircle(float radius, sf::Color fillColor, sf::Color outlineColor = sf::Color::Transparent, float outlineThickness = 0.0f); + static std::unique_ptr createRectangle(const sf::Vector2f& size, const sf::Color& fillColor, const sf::Color& outlineColor = sf::Color::Transparent, float outlineThickness = 0.0f); +}; + +#endif // ENTITYFACTORY_H diff --git a/include/EntityManager.h b/include/EntityManager.h index 155b648..df79085 100644 --- a/include/EntityManager.h +++ b/include/EntityManager.h @@ -21,7 +21,7 @@ class EntityManager { [[nodiscard]] const std::unordered_set& getEntities() const; private: - Entity::Id m_nextId = 1; + Entity::Id m_nextId = 0; std::unordered_set m_entities; }; diff --git a/include/EventDispatcher.h b/include/EventDispatcher.h index 478695f..b7f815f 100644 --- a/include/EventDispatcher.h +++ b/include/EventDispatcher.h @@ -19,8 +19,7 @@ enum class EventType { EntityCreated, EntityDestroyed, - GlobalTransformChanged, - LocalTransformChanged + TransformChanged, }; struct Event { diff --git a/include/HierarchySystem.h b/include/NodeSystem.h similarity index 74% rename from include/HierarchySystem.h rename to include/NodeSystem.h index b15911a..0aeb50c 100644 --- a/include/HierarchySystem.h +++ b/include/NodeSystem.h @@ -14,14 +14,15 @@ #include "Entity.h" // for Entity #include "System.h" // for System #include "components/Node.h" // for Node -class EventDispatcher; +class EventDispatcher; // lines 18-18 +class World; -class HierarchySystem : public System { +class NodeSystem : public System { public: using QueryFunction = std::function, std::optional>)>; - explicit HierarchySystem(QueryFunction query, EventDispatcher& dispatcher); + explicit NodeSystem(World& world, EventDispatcher& dispatcher); void update() override; @@ -32,11 +33,7 @@ class HierarchySystem : public System { std::unordered_map m_parentMap; std::unordered_map> m_childNodes; - void onEventEntityCreated(Entity::Id entityId); - void onEventEntityChildAdded(Entity::Id parentId, Entity::Id childId); - void propagateTransform(Entity::Id parentId, const sf::Transform& parentTransform); - void onEventGlobalTransformChanged(Entity::Id entityId); - void onEventLocalTransformChanged(Entity::Id entityId); + void onEventEntityChildAdded(Entity::Id parentId, Entity::Id childId) const; }; #endif //HIERARCHYSYSTEM_H diff --git a/include/PhysicSystem.h b/include/PhysicSystem.h new file mode 100644 index 0000000..fa7cbd9 --- /dev/null +++ b/include/PhysicSystem.h @@ -0,0 +1,40 @@ +// +// Created by Tristan Klempka on 25/07/2024. +// + +#ifndef PHYSICSYSTEM_H +#define PHYSICSYSTEM_H + +#include +#include +#include +#include + +#include "System.h" +#include "Entity.h" +#include "EventDispatcher.h" +#include "World.h" +#include "components/Node.h" +#include "components/Velocity.h" + +class PhysicsSystem : public System { +public: + using QueryFunction = std::function, + std::optional>)>; + + explicit PhysicsSystem(World& world, EventDispatcher& dispatcher) + : m_query(world.getComponentQuery()), m_dispatcher(dispatcher) {} + + void update() override { + m_query([this](const Entity::Id id, Node& node, const Velocity& velocity) { + node.transform = node.transform.rotate(velocity.dtheta).translate(velocity.dx, velocity.dy); + m_dispatcher.dispatch(EventBuilder(EventType::TransformChanged, id).build()); + }, std::nullopt); + } + +private: + QueryFunction m_query; + EventDispatcher& m_dispatcher; +}; + +#endif //PHYSICSYSTEM_H diff --git a/include/RenderingSystem.h b/include/RenderingSystem.h new file mode 100644 index 0000000..9712846 --- /dev/null +++ b/include/RenderingSystem.h @@ -0,0 +1,38 @@ +// +// Created by Tristan Klempka on 25/07/2024. +// + +#ifndef RENDERINGSYSTEM_H +#define RENDERINGSYSTEM_H + +#include // for function +#include // for optional +#include // for unordered_map +#include // for unordered_set +#include "Entity.h" // for Entity +#include "System.h" // for System +#include "components/Node.h" // for Node +#include "components/Shape.h" // for Shape +class World; +namespace sf { class Drawable; } +namespace sf { class RenderWindow; } +namespace sf { class Transform; } + +class RenderingSystem : public System { +public: + using QueryFunction = std::function, + std::optional>)>; + + explicit RenderingSystem(World& world, sf::RenderWindow& window); + + void update() override; + +private: + QueryFunction m_query; + sf::RenderWindow& m_window; + std::unordered_map m_renderables; + + void doDraw(Entity::Id id, const sf::Transform& parentTransform); +}; + +#endif // RENDERINGSYSTEM_H diff --git a/include/SystemManager.h b/include/SystemManager.h index ab07e41..3561072 100644 --- a/include/SystemManager.h +++ b/include/SystemManager.h @@ -8,7 +8,7 @@ #include // for shared_ptr #include // for hash, type_index #include // for unordered_map -class System; +class System; // lines 12-12 class SystemManager { public: diff --git a/include/TransformManager.h b/include/TransformManager.h new file mode 100644 index 0000000..f491353 --- /dev/null +++ b/include/TransformManager.h @@ -0,0 +1,27 @@ +// +// Created by Tristan Klempka on 26/07/2024. +// + +#ifndef TRANSFORMMANAGER_H +#define TRANSFORMMANAGER_H + +#include // for function +#include // for optional +#include // for unordered_set +#include "Entity.h" // for Entity +class World; +struct Node; + +class TransformManager { +public: + explicit TransformManager(World& world); + + void applyRotation(Entity::Id entityId, float angle) const; + void applyTranslation(Entity::Id entityId, float x, float y) const; + void applyTransformation(Entity::Id entityId, float angle, float x, float y) const; + +private: + std::function, std::optional>)> m_query; +}; + +#endif // TRANSFORMMANAGER_H diff --git a/include/World.h b/include/World.h index bfb60a1..278c288 100644 --- a/include/World.h +++ b/include/World.h @@ -5,10 +5,7 @@ #ifndef COORDINATOR_H #define COORDINATOR_H -#include // for function #include // for unique_ptr, shared_ptr -#include // for optional -#include // for unordered_set #include "ComponentManager.h" // for ComponentType, ComponentManager #include "Entity.h" // for Entity #include "EntityManager.h" // for EntityManager @@ -19,7 +16,7 @@ class World { public: void init(); - [[nodiscard]] Entity::Id createEntity(); + [[nodiscard]] Entity::Id createEntity() const; void destroyEntity(Entity::Id entityId) const; diff --git a/include/components/Node.h b/include/components/Node.h index 9d4fa67..d1ff73a 100644 --- a/include/components/Node.h +++ b/include/components/Node.h @@ -5,11 +5,15 @@ #ifndef NODE_H #define NODE_H +#include #include +#include "Entity.h" + struct Node { - sf::Transform global_transform = sf::Transform::Identity; - sf::Transform local_transform = sf::Transform::Identity; + sf::Transform transform = sf::Transform::Identity; + Entity::Id parent = 0; + std::unordered_set children = {}; }; #endif //NODE_H diff --git a/include/components/Drawable.h b/include/components/Shape.h similarity index 53% rename from include/components/Drawable.h rename to include/components/Shape.h index faa5889..74c4fe0 100644 --- a/include/components/Drawable.h +++ b/include/components/Shape.h @@ -5,10 +5,10 @@ #ifndef DRAWABLE_H #define DRAWABLE_H -#include +#include -struct Drawable { - std::unique_ptr drawable; +struct Shape { + std::unique_ptr shape; }; #endif //DRAWABLE_H diff --git a/main.cpp b/main.cpp index 4457f76..776c7e2 100644 --- a/main.cpp +++ b/main.cpp @@ -1,22 +1,52 @@ -#include // for Event -#include // for VideoMode -#include // for basic_string -#include "Window.h" // for Window -#include "World.h" // for World +#include // for Color +#include // for RenderWindow +#include // for String +#include // for Event +#include // for VideoMode +#include // for shared_ptr +#include "EntityFactory.h" // for EntityFactory +#include "NodeSystem.h" // for NodeSystem +#include "RenderingSystem.h" // for RenderingSystem +#include "SystemManager.h" // for SystemManager::registerSystem +#include "TransformManager.h" // for TransformManager +#include "World.h" // for World int main() { - Window window(sf::VideoMode(800, 600), "My window"); + // Create a render window + sf::RenderWindow window(sf::VideoMode(800, 600), "Hierarchy Example"); + + // Create the world World world; world.init(); + // Register systems + const auto nodeSystem = world.registerSystem(world, world.getEventDispatcher()); + const auto renderingSystem = world.registerSystem(world, window); + + // Create an entity factory + const EntityFactory factory(world); + const TransformManager transformManager(world); + + // Create origin + auto origin = factory.createCircleEntity(1.0f, sf::Color::White); + transformManager.applyTranslation(origin, 400.f, 300.f); + + // Create blue circle + auto parentEntity = factory.createCircleEntity(25.0f, sf::Color::Transparent, sf::Color::Blue, 1.0f); + world.addChild(origin, parentEntity); + transformManager.applyTransformation(parentEntity, 90.f, 100.f, 0.f); + + // Main loop while (window.isOpen()) { sf::Event event{}; - while (window.pollEvent(event)) - { - // "close requested" event: we close the window + while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); } + + // physicsSystem->update(); + renderingSystem->update(); } + return 0; -} \ No newline at end of file +} diff --git a/src/EntityFactory.cpp b/src/EntityFactory.cpp new file mode 100644 index 0000000..fb8ca28 --- /dev/null +++ b/src/EntityFactory.cpp @@ -0,0 +1,52 @@ +// +// Created by Tristan Klempka on 26/07/2024. +// + +#include "EntityFactory.h" +#include // for Shape +#include // for move +#include "ComponentManager.h" // for ComponentManager::addComponent +#include "World.h" // for World +#include "World.tpp" // for World::addComponent +#include "components/Node.h" // for Node +#include "components/Shape.h" // for Shape + +EntityFactory::EntityFactory(World& world) : world(world) {} + +Entity::Id EntityFactory::createCircleEntity(const float radius, const sf::Color fillColor, const sf::Color outlineColor, const float outlineThickness) const { + const auto entityId = world.createEntity(); + const Node node; + world.addComponent(entityId, node); // Add Node component by default + Shape renderable; + renderable.shape = createCircle(radius, fillColor, outlineColor, outlineThickness); + world.addComponent(entityId, std::move(renderable)); + return entityId; +} + +Entity::Id EntityFactory::createRectangleEntity(const sf::Vector2f& size, const sf::Color& fillColor, const sf::Color& outlineColor, const float outlineThickness) const { + const auto entityId = world.createEntity(); + const Node node; + world.addComponent(entityId, node); // Add Node component by default + Shape renderable; + renderable.shape = createRectangle(size, fillColor, outlineColor, outlineThickness); + world.addComponent(entityId, std::move(renderable)); + return entityId; +} + +std::unique_ptr EntityFactory::createCircle(float radius, const sf::Color fillColor, const sf::Color outlineColor, const float outlineThickness) { + auto circle = std::make_unique(radius); + circle->setFillColor(fillColor); + circle->setOutlineColor(outlineColor); + circle->setOutlineThickness(outlineThickness); + circle->setOrigin(radius, radius); // Set origin to center of the circle + return circle; +} + +std::unique_ptr EntityFactory::createRectangle(const sf::Vector2f& size, const sf::Color& fillColor, const sf::Color& outlineColor, const float outlineThickness) { + auto rectangle = std::make_unique(size); + rectangle->setFillColor(fillColor); + rectangle->setOutlineColor(outlineColor); + rectangle->setOutlineThickness(outlineThickness); + rectangle->setOrigin(size.x / 2.0f, size.y / 2.0f); // Set origin to center of the rectangle + return rectangle; +} \ No newline at end of file diff --git a/src/HierarchySystem.cpp b/src/HierarchySystem.cpp deleted file mode 100644 index 0941e83..0000000 --- a/src/HierarchySystem.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// -// Created by Tristan Klempka on 25/07/2024. -// - -#include "HierarchySystem.h" - -#include // for basic_ostream, operator<<, char_traits -#include // for move -#include "EventDispatcher.h" // for Event, EventDispatcher, EventType - -HierarchySystem::HierarchySystem(QueryFunction query, EventDispatcher& dispatcher) - : m_query(std::move(query)) { - dispatcher.subscribe(EventType::EntityChildAdded, [this](const Event& event) { - onEventEntityChildAdded(event.parentId.value(), event.childId.value()); - }); - dispatcher.subscribe(EventType::EntityCreated, [this](const Event& event) { - onEventEntityCreated(event.entityId); - }); - dispatcher.subscribe(EventType::GlobalTransformChanged, [this](const Event& event) { - onEventGlobalTransformChanged(event.entityId); - }); - dispatcher.subscribe(EventType::LocalTransformChanged, [this](const Event& event) { - onEventLocalTransformChanged(event.entityId); - }); -} - -void HierarchySystem::update() { - // No operation -} - -void HierarchySystem::onEventEntityCreated(const Entity::Id entityId) { - std::cout << "Adding entity to HierarchySystem: " << entityId << std::endl; - m_query([this](const Entity::Id id, const Node& node) { - if (!m_parentMap.contains(id)) { - m_rootNodes.insert(id); - m_nodeTransforms[id] = node.global_transform; - std::cout << "Root node detected and added for entity: " << id << std::endl; - const auto matrix = node.global_transform.getMatrix(); - std::cout << "Created entity: " << id << " with global transform (" << matrix[12] << ", " << matrix[13] << ")\n"; - } - }, std::unordered_set{entityId}); -} - -void HierarchySystem::onEventEntityChildAdded(const Entity::Id parentId, const Entity::Id childId) { - std::cout << "ChildAdded event received. Parent: " << parentId << ", Child: " << childId << std::endl; - m_childNodes[parentId].push_back(childId); - m_parentMap[childId] = parentId; - propagateTransform(childId, m_nodeTransforms[parentId]); -} - -void HierarchySystem::propagateTransform(const Entity::Id parentId, const sf::Transform& parentTransform) { - std::cout << "Propagating transform for parent ID: " << parentId << std::endl; - m_query([this, &parentTransform](const Entity::Id id, Node& node) { - sf::Transform combinedTransform = parentTransform * node.local_transform; - m_nodeTransforms[id] = combinedTransform; - node.global_transform = combinedTransform; // Update the node's transform - const auto matrix = combinedTransform.getMatrix(); - std::cout << "Updated entity: " << id << " with combined transform (" << matrix[12] << ", " << matrix[13] << ")\n"; - - if (m_childNodes.contains(id)) { - for (auto childId : m_childNodes[id]) { - std::cout << "Propagating to child ID: " << childId << " of parent ID: " << id << std::endl; - propagateTransform(childId, combinedTransform); - } - } - }, std::unordered_set{parentId}); -} - -void HierarchySystem::onEventGlobalTransformChanged(const Entity::Id entityId) { - m_query([this](const Entity::Id id, const Node& node) { - if (m_rootNodes.contains(id)) { - m_nodeTransforms[id] = node.global_transform; - auto matrix = m_nodeTransforms[id].getMatrix(); - std::cout << "Updated entity: " << id << " with global transform (" << matrix[12] << ", " << matrix[13] << ")\n"; - if (m_childNodes.contains(id)) { - for (const auto childId : m_childNodes[id]) { - std::cout << "Propagating to child ID: " << childId << " of parent ID: " << id << std::endl; - propagateTransform(childId, m_nodeTransforms[id]); - } - } - } - }, std::unordered_set{entityId}); -} - -void HierarchySystem::onEventLocalTransformChanged(const Entity::Id entityId) { - std::cout << "LocalTransformChanged event received for entity: " << entityId << std::endl; - if (m_parentMap.contains(entityId)) { - const Entity::Id parentId = m_parentMap[entityId]; - propagateTransform(entityId, m_nodeTransforms[parentId]); - } -} \ No newline at end of file diff --git a/src/NodeSystem.cpp b/src/NodeSystem.cpp new file mode 100644 index 0000000..942b139 --- /dev/null +++ b/src/NodeSystem.cpp @@ -0,0 +1,27 @@ +// +// Created by Tristan Klempka on 25/07/2024. +// + +#include "NodeSystem.h" +#include // for basic_ostream, char_traits, operator<< +#include "ComponentManager.h" // for ComponentManager::getComponent, Comp... +#include "EventDispatcher.h" // for Event, EventDispatcher, EventType +#include "World.h" // for World + +NodeSystem::NodeSystem(World& world, EventDispatcher& dispatcher) + : m_query(world.getComponentQuery()) { + dispatcher.subscribe(EventType::EntityChildAdded, [this](const Event& event) { + onEventEntityChildAdded(event.parentId.value(), event.childId.value()); + }); +} + +void NodeSystem::update() { + // No operation +} + +void NodeSystem::onEventEntityChildAdded(const Entity::Id parentId, const Entity::Id childId) const { + std::cout << "ChildAdded event received. Parent: " << parentId << ", Child: " << childId << std::endl; + m_query([childId](const Entity::Id, Node& node) { + node.children.insert(childId); + }, std::unordered_set{parentId}); +} \ No newline at end of file diff --git a/src/RenderingSystem.cpp b/src/RenderingSystem.cpp new file mode 100644 index 0000000..b36fe41 --- /dev/null +++ b/src/RenderingSystem.cpp @@ -0,0 +1,32 @@ +// +// Created by Tristan Klempka on 25/07/2024. +// + +#include "RenderingSystem.h" +#include // for RenderWindow +#include // for Shape +#include // for Transform, operator* +#include // for unique_ptr +#include "ComponentManager.h" // for ComponentManager::getCompo... +#include "World.h" // for World +#include "World.h" // for World::getComponentQuery + +RenderingSystem::RenderingSystem(World& world, sf::RenderWindow& window) + : m_query(world.getComponentQuery()), m_window(window) {} + +void RenderingSystem::update() { + + m_window.clear(); + // TODO remove macgic cst + doDraw(Entity::Id{0}, sf::Transform::Identity); + m_window.display(); +} + +void RenderingSystem::doDraw(const Entity::Id id, const sf::Transform& parentTransform) { + m_query([this, parentTransform](Entity::Id, const Node& node, const Shape& renderable) { + m_window.draw(*renderable.shape, node.transform * parentTransform); + for (const auto child: node.children) { + doDraw(child, node.transform * parentTransform); + } + }, std::unordered_set{id}); +} diff --git a/src/TransformManager.cpp b/src/TransformManager.cpp new file mode 100644 index 0000000..3e3157a --- /dev/null +++ b/src/TransformManager.cpp @@ -0,0 +1,34 @@ +// +// Created by Tristan Klempka on 26/07/2024. +// + +#include "TransformManager.h" +#include // for Transform +#include "ComponentManager.h" // for ComponentManager::getComponent +#include "World.h" // for World +#include "components/Node.h" // for Node + +TransformManager::TransformManager(World& world) + : m_query(world.getComponentQuery()) {} + +void TransformManager::applyRotation(const Entity::Id entityId, const float angle) const { + m_query([this, angle](const Entity::Id, Node& node) { + m_query([&node, angle](const Entity::Id, const Node& parentNode) { + node.transform.rotate(angle, parentNode.transform.transformPoint(0, 0)); + }, std::unordered_set{node.parent}); + }, std::unordered_set{entityId}); +} + +void TransformManager::applyTranslation(const Entity::Id entityId, const float x, const float y) const { + m_query([x, y](const Entity::Id, Node& node) { + node.transform.translate(x, y); + }, std::unordered_set{entityId}); +} + +void TransformManager::applyTransformation(const Entity::Id entityId, const float angle, const float x, const float y) const { + m_query([this, angle, x, y](const Entity::Id, Node& node) { + m_query([&node, angle, x, y](const Entity::Id, const Node& parentNode) { + node.transform.rotate(angle, parentNode.transform.transformPoint(0, 0)).translate(x, y); + }, std::unordered_set{node.parent}); + }, std::unordered_set{entityId}); +} \ No newline at end of file diff --git a/src/World.cpp b/src/World.cpp index 76c6438..52a601a 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -3,11 +3,8 @@ // #include "World.h" - -#include // for runtime_error -#include "ComponentManager.h" // for ComponentManager::addComponent -#include "World.tpp" // for World::addComponent -#include "components/Node.h" // for Node +#include // for runtime_error +#include "ComponentManager.h" // for ComponentManager void World::init() { m_componentManager = std::make_unique(); @@ -16,11 +13,9 @@ void World::init() { m_eventDispatcher = std::make_unique(); } -Entity::Id World::createEntity() { - auto entityId = m_entityManager->createEntity(); - const Node defaultNode; - addComponent(entityId, defaultNode); // Add Node component by default - m_eventDispatcher->dispatch({EventType::EntityCreated, entityId}); +Entity::Id World::createEntity() const { + const auto entityId = m_entityManager->createEntity(); + m_eventDispatcher->dispatch(EventBuilder(EventType::EntityCreated, entityId).build()); return entityId; } diff --git a/test/ecs_tests.cpp b/test/ecs_tests.cpp index 94ed5a5..ef7b5ce 100644 --- a/test/ecs_tests.cpp +++ b/test/ecs_tests.cpp @@ -4,22 +4,26 @@ #include #include // for operator""_catch_sr, StringRef +#include #include // for shared_ptr #include // for move #include // for get #include "ComponentManager.h" // for ComponentManager::hasComponent -#include "HierarchySystem.h" +#include "EntityFactory.h" +#include "NodeSystem.h" #include "World.h" // for Coordinator #include "World.h" // for Coordinator::hasComponent #include "SystemManager.h" // for SystemManager::registerSystem -#include "components/Velocity.h" // for Velocity +#include "components/Velocity.h" +#include "TransformManager.h" +#include "EventDispatcher.h"// for Velocity TEST_CASE("CreateEntity") { World world; world.init(); const auto entityId = world.createEntity(); - REQUIRE(entityId == 1); + REQUIRE(entityId == 0); } TEST_CASE("AddAndGetComponent") { @@ -30,7 +34,7 @@ TEST_CASE("AddAndGetComponent") { const Node node; coordinator.addComponent(entityId, node); - REQUIRE(node.global_transform == sf::Transform()); + REQUIRE(node.transform == sf::Transform()); } TEST_CASE("HasComponent") { @@ -66,7 +70,7 @@ TEST_CASE("Event Dispatcher") { bool eventTriggered = false; dispatcher.subscribe(EventType::EntityUpdated, [&](const Event& event) { eventTriggered = true; - REQUIRE(event.entityId == 1); + REQUIRE(event.entityId == 0); }); auto entityId = world.createEntity(); @@ -76,228 +80,82 @@ TEST_CASE("Event Dispatcher") { } } -TEST_CASE("Node hierarchy and transform propagation") { - // Window window(sf::VideoMode(800, 600), "Test Window"); - World world; - world.init(); - auto hierarchyQuery = world.getComponentQuery(); - auto hierarchySystem = world.registerSystem(hierarchyQuery, world.getEventDispatcher()); - - // Create grandparent entity - auto grandparentEntity = world.createEntity(); - world.getComponent(grandparentEntity).global_transform.translate(5.0f, 5.0f); - world.getEventDispatcher().dispatch({EventType::GlobalTransformChanged, grandparentEntity}); - // Create parent entity - auto parentEntity = world.createEntity(); - world.getComponent(parentEntity).local_transform.translate(3.0f, 3.0f); - world.getEventDispatcher().dispatch({EventType::LocalTransformChanged, parentEntity}); - // Create child entity - auto childEntity = world.createEntity(); - world.getComponent(childEntity).local_transform.translate(1.0f, 1.0f); - world.getEventDispatcher().dispatch({EventType::LocalTransformChanged, childEntity}); - - std::cout << "Initial transforms:" << std::endl; - std::cout << "Grandparent transform: (" << world.getComponent(grandparentEntity).global_transform.getMatrix()[12] << ", " << world.getComponent(grandparentEntity).global_transform.getMatrix()[13] << ")" << std::endl; - std::cout << "Parent transform: (" << world.getComponent(parentEntity).local_transform.getMatrix()[12] << ", " << world.getComponent(parentEntity).local_transform.getMatrix()[13] << ")" << std::endl; - std::cout << "Child transform: (" << world.getComponent(childEntity).local_transform.getMatrix()[12] << ", " << world.getComponent(childEntity).local_transform.getMatrix()[13] << ")" << std::endl; - - // Add parent to grandparent - world.addChild(grandparentEntity, parentEntity); - - // Add child to parent - world.addChild(parentEntity, childEntity); - - // Check transforms - auto& grandparentTransform = world.getComponent(grandparentEntity).global_transform; - auto& parentTransform = world.getComponent(parentEntity).global_transform; - auto& childTransform = world.getComponent(childEntity).global_transform; - - // Calculate expected positions - sf::Transform expectedGrandparentTransform; - expectedGrandparentTransform.translate(5.0f, 5.0f); - - sf::Transform expectedParentTransform = expectedGrandparentTransform; - expectedParentTransform.translate(3.0f, 3.0f); - - sf::Transform expectedChildTransform = expectedParentTransform; - expectedChildTransform.translate(1.0f, 1.0f); - - sf::Vector2f grandparentPosition = grandparentTransform.transformPoint(0, 0); - sf::Vector2f parentPosition = parentTransform.transformPoint(0, 0); - sf::Vector2f childPosition = childTransform.transformPoint(0, 0); - - sf::Vector2f expectedGrandparentPosition = expectedGrandparentTransform.transformPoint(0, 0); - sf::Vector2f expectedParentPosition = expectedParentTransform.transformPoint(0, 0); - sf::Vector2f expectedChildPosition = expectedChildTransform.transformPoint(0, 0); - - std::cout << "Grandparent position: (" << grandparentPosition.x << ", " << grandparentPosition.y << ")\n"; - std::cout << "Expected Grandparent position: (" << expectedGrandparentPosition.x << ", " << expectedGrandparentPosition.y << ")\n"; - std::cout << "Parent position: (" << parentPosition.x << ", " << parentPosition.y << ")\n"; - std::cout << "Expected Parent position: (" << expectedParentPosition.x << ", " << expectedParentPosition.y << ")\n"; - std::cout << "Child position: (" << childPosition.x << ", " << childPosition.y << ")\n"; - std::cout << "Expected Child position: (" << expectedChildPosition.x << ", " << expectedChildPosition.y << ")\n"; - - REQUIRE(grandparentPosition == expectedGrandparentPosition); - REQUIRE(parentPosition == expectedParentPosition); - REQUIRE(childPosition == expectedChildPosition); -} - class MockWorld { public: - MockWorld() { - m_dispatcher = std::make_unique(); + void init() { + // Initialization logic if necessary } Entity::Id createEntity() { - Entity::Id id = ++m_lastId; - m_entities.insert(id); - return id; + Entity::Id newId = nextEntityId++; + entities[newId] = {}; + return newId; } - template + template void addComponent(Entity::Id entityId, T component) { - m_components[entityId] = component; + entities[entityId].emplace(typeid(T).hash_code(), std::make_any(std::move(component))); } - template + template T& getComponent(Entity::Id entityId) { - return std::any_cast(m_components[entityId]); + return std::any_cast(entities[entityId][typeid(T).hash_code()]); } void addChild(Entity::Id parentId, Entity::Id childId) { - m_dispatcher->dispatch(EventBuilder(EventType::EntityChildAdded, parentId).withParentId(parentId).withChildId(childId).build()); - + getComponent(parentId).children.insert(childId); + getComponent(childId).parent = parentId; } EventDispatcher& getEventDispatcher() { - return *m_dispatcher; + return dispatcher; } - std::function, std::optional>)> getComponentQuery() { - return [this](std::function callback, std::optional> ids) { - if (ids.has_value()) { - for (auto id : ids.value()) { - if (m_components.find(id) != m_components.end()) { - callback(id, std::any_cast(m_components[id])); + auto getComponentQuery() { + return [this](auto callback, auto filter) { + for (auto& [entityId, components] : entities) { + if (filter.has_value()) { + if (filter->count(entityId) == 0) { + continue; } } - } else { - for (auto& [id, component] : m_components) { - callback(id, std::any_cast(component)); + if (components.count(typeid(Node).hash_code()) > 0) { + callback(entityId, std::any_cast(components[typeid(Node).hash_code()])); } } }; } private: - Entity::Id m_lastId = 0; - std::unordered_set m_entities; - std::unordered_map m_components; - std::unique_ptr m_dispatcher; + Entity::Id nextEntityId = 1; + std::unordered_map> entities; + EventDispatcher dispatcher; }; -// Test for EntityCreated Event -TEST_CASE("HierarchySystem handles EntityCreated event") { - MockWorld world; - - auto hierarchyQuery = world.getComponentQuery(); - auto hierarchySystem = std::make_shared(hierarchyQuery, world.getEventDispatcher()); - - // Create an entity - auto entity = world.createEntity(); - Node node; - node.global_transform.translate(5.0f, 5.0f); - world.addComponent(entity, node); - world.getEventDispatcher().dispatch(Event{EventType::EntityCreated, entity}); - - // Verify the transform - auto& transform = world.getComponent(entity).global_transform; - sf::Vector2f position = transform.transformPoint(0, 0); - REQUIRE(position == sf::Vector2f(5.0f, 5.0f)); -} - -// Test for EntityChildAdded Event -TEST_CASE("HierarchySystem handles EntityChildAdded event") { - MockWorld world; - - auto hierarchyQuery = world.getComponentQuery(); - auto hierarchySystem = std::make_shared(hierarchyQuery, world.getEventDispatcher()); - - // Create parent and child entities - auto parentEntity = world.createEntity(); - Node parentNode; - parentNode.global_transform.translate(5.0f, 5.0f); - world.addComponent(parentEntity, parentNode); - world.getEventDispatcher().dispatch(Event{EventType::EntityCreated, parentEntity}); - - auto childEntity = world.createEntity(); - Node childNode; - childNode.local_transform.translate(3.0f, 3.0f); - world.addComponent(childEntity, childNode); - world.getEventDispatcher().dispatch(Event{EventType::EntityCreated, childEntity}); - - // Add child to parent - world.addChild(parentEntity, childEntity); - - // Verify the transform - auto& transform = world.getComponent(childEntity).global_transform; - sf::Vector2f position = transform.transformPoint(0, 0); - REQUIRE(position == sf::Vector2f(8.0f, 8.0f)); -} - -// Test for GlobalTransformChanged Event -TEST_CASE("HierarchySystem handles GlobalTransformChanged event") { - MockWorld world; - - auto hierarchyQuery = world.getComponentQuery(); - auto hierarchySystem = std::make_shared(hierarchyQuery, world.getEventDispatcher()); - - // Create an entity - auto entity = world.createEntity(); - Node node; - node.global_transform.translate(5.0f, 5.0f); - world.addComponent(entity, node); - world.getEventDispatcher().dispatch(Event{EventType::EntityCreated, entity}); - - // Change the global transform - world.getComponent(entity).global_transform.translate(2.0f, 2.0f); - world.getEventDispatcher().dispatch(Event{EventType::GlobalTransformChanged, entity}); - - // Verify the transform - auto& transform = world.getComponent(entity).global_transform; - sf::Vector2f position = transform.transformPoint(0, 0); - REQUIRE(position == sf::Vector2f(7.0f, 7.0f)); -} - -// Test for LocalTransformChanged Event -TEST_CASE("HierarchySystem handles LocalTransformChanged event") { - MockWorld world; - - auto hierarchyQuery = world.getComponentQuery(); - auto hierarchySystem = std::make_shared(hierarchyQuery, world.getEventDispatcher()); +TEST_CASE("TransformManager applyTransformation", "[TransformManager]") { + World world; + world.init(); + const TransformManager transformManager(world); + const EntityFactory factory(world); - // Create parent and child entities - auto parentEntity = world.createEntity(); - Node parentNode; - parentNode.global_transform.translate(5.0f, 5.0f); - world.addComponent(parentEntity, parentNode); - world.getEventDispatcher().dispatch(Event{EventType::EntityCreated, parentEntity}); + // Create origin + const auto origin = factory.createCircleEntity(1.0f, sf::Color::White); + transformManager.applyTranslation(origin, 400.f, 300.f); + transformManager.applyRotation(origin, 0.f); - auto childEntity = world.createEntity(); - Node childNode; - childNode.local_transform.translate(3.0f, 3.0f); - world.addComponent(childEntity, childNode); - world.getEventDispatcher().dispatch(Event{EventType::EntityCreated, childEntity}); + // Create blue circle + const auto parentEntity = factory.createCircleEntity(25.0f, sf::Color::Transparent, sf::Color::Blue, 1.0f); + world.addChild(origin, parentEntity); + transformManager.applyTransformation(parentEntity, 90.f, 100.f, 0.f); - // Add child to parent - world.addChild(parentEntity, childEntity); + // Get the transformed point + const auto& originTransform = world.getComponent(origin).transform; + const auto& childTransform = world.getComponent(parentEntity).transform; + const sf::Vector2f transformedPoint = childTransform.transformPoint(originTransform.transformPoint(0, 0)); - // Change the local transform - world.getComponent(childEntity).local_transform.translate(1.0f, 1.0f); - world.getEventDispatcher().dispatch(Event{EventType::LocalTransformChanged, childEntity}); + // Expected position after 90 degree rotation around the parent (400, 300) + const sf::Vector2f expectedPoint(400.f, 400.f); - // Verify the transform - auto& transform = world.getComponent(childEntity).global_transform; - sf::Vector2f position = transform.transformPoint(0, 0); - REQUIRE(position == sf::Vector2f(9.0f, 9.0f)); + REQUIRE(transformedPoint.x == Catch::Approx(expectedPoint.x).margin(1e-5)); + REQUIRE(transformedPoint.y == Catch::Approx(expectedPoint.y).margin(1e-5)); } \ No newline at end of file