diff --git a/CMakeLists.txt b/CMakeLists.txt index f43e51f3..f1f860c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,10 @@ set(SOURCES sources/Component/Renderer/CameraComponent.hpp sources/System/Renderer/Render2DScreenSystem.cpp sources/System/Renderer/Render2DScreenSystem.hpp + sources/Component/Collision/CollisionComponent.cpp + sources/Component/Collision/CollisionComponent.hpp + sources/System/Collision/CollisionSystem.hpp + sources/System/Collision/CollisionSystem.cpp ) add_executable(bomberman @@ -71,7 +75,9 @@ add_executable(unit_tests EXCLUDE_FROM_ALL tests/MainTest.cpp tests/EngineTests.cpp tests/CallbackTest.cpp - tests/MoveTests.cpp) + tests/CollisionTest.cpp + tests/MoveTests.cpp +) target_include_directories(unit_tests PUBLIC sources) target_link_libraries(unit_tests PUBLIC wal ray) diff --git a/sources/Component/Collision/CollisionComponent.cpp b/sources/Component/Collision/CollisionComponent.cpp new file mode 100644 index 00000000..22cbdef8 --- /dev/null +++ b/sources/Component/Collision/CollisionComponent.cpp @@ -0,0 +1,42 @@ +// +// Created by Louis Auzuret on 2021-05-20. +// + +#include "Component/Collision/CollisionComponent.hpp" + + +namespace BBM +{ + CollisionComponent::CollisionComponent(WAL::Entity &entity) + : WAL::Component(entity) + { } + + WAL::Component *CollisionComponent::clone(WAL::Entity &entity) const + { + return new CollisionComponent(entity); + } + + CollisionComponent::CollisionComponent(WAL::Entity &entity, std::function onCollide, std::function onCollided, Vector3f bound) + : WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound(bound) + { } + + CollisionComponent::CollisionComponent(WAL::Entity &entity, std::function onCollide, std::function onCollided, float boundSize) + : WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound({boundSize, boundSize, boundSize}) + { } + + CollisionComponent::CollisionComponent(WAL::Entity &entity, WAL::Callback onCollide, WAL::Callback onCollided, Vector3f bound) + : WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound(bound) + { } + + CollisionComponent::CollisionComponent(WAL::Entity &entity, WAL::Callback onCollide, WAL::Callback onCollided, float boundSize) + : WAL::Component(entity), onCollide(onCollide), onCollided(onCollided), bound({boundSize, boundSize, boundSize}) + { } + + CollisionComponent::CollisionComponent(WAL::Entity &entity, Vector3f bound) + : WAL::Component(entity), onCollide(), onCollided(), bound(bound) + { } + + CollisionComponent::CollisionComponent(WAL::Entity &entity, float boundSize) + : WAL::Component(entity), onCollide(), onCollided(), bound({boundSize, boundSize, boundSize}) + { } +} \ No newline at end of file diff --git a/sources/Component/Collision/CollisionComponent.hpp b/sources/Component/Collision/CollisionComponent.hpp new file mode 100644 index 00000000..4636c79a --- /dev/null +++ b/sources/Component/Collision/CollisionComponent.hpp @@ -0,0 +1,57 @@ +// +// Created by Louis Auzuret on 2021-05-20. +// + +#pragma once + +#include "Models/Callback.hpp" +#include "Models/Vector3.hpp" +#include "Component/Component.hpp" +#include "Entity/Entity.hpp" + +namespace BBM +{ + class CollisionComponent : public WAL::Component + { + private: + public: + //! @brief onCollide functions to be called + WAL::Callback onCollide; + //! @brief onCollided functions to be called + WAL::Callback onCollided; + //! @brief Bound size on all axis + Vector3f bound; + //! @inherit + WAL::Component *clone(WAL::Entity &entity) const override; + + //! @brief A component can't be instantiated, it should be derived. + explicit CollisionComponent(WAL::Entity &entity); + + //! @brief Constructor with a callback function + CollisionComponent(WAL::Entity &entity, std::function onCollide, std::function onCollided, Vector3f bound); + + //! @brief Constructor with a callback function, same boundSize for all axis + CollisionComponent(WAL::Entity &entity, std::function onCollide, std::function onCollided, float boundSize = 0); + + //! @brief Constructor with a WAL::Callback + CollisionComponent(WAL::Entity &entity, WAL::Callback onCollide, WAL::Callback onCollided,Vector3f bound); + + //! @brief Constructor with a WAL::Callback, same boundSize for all axis + CollisionComponent(WAL::Entity &entity, WAL::Callback onCollide, WAL::Callback onCollided, float boundSize = 0); + + //! @brief Constructor of collider with no callback + CollisionComponent(WAL::Entity &entity, Vector3f bound); + + //! @brief Constructor no callback, same boundSize for all axis + CollisionComponent(WAL::Entity &entity, float boundSize); + + //! @brief Default copy constructor + CollisionComponent(const CollisionComponent &) = default; + + //! @brief default destructor + ~CollisionComponent() override = default; + + //! @brief A component can't be assigned + CollisionComponent &operator=(const CollisionComponent &) = delete; + }; +} \ No newline at end of file diff --git a/sources/Component/Movable/MovableComponent.cpp b/sources/Component/Movable/MovableComponent.cpp index 4fc9bc0c..51f0707d 100644 --- a/sources/Component/Movable/MovableComponent.cpp +++ b/sources/Component/Movable/MovableComponent.cpp @@ -19,4 +19,15 @@ namespace BBM { this->_acceleration += force; } + + void MovableComponent::resetVelocity(void) + { + this->_velocity = {0, 0, 0}; + } + + const Vector3f &MovableComponent::getVelocity(void) const + { + return _velocity; + } + } // namespace WAL \ No newline at end of file diff --git a/sources/Component/Movable/MovableComponent.hpp b/sources/Component/Movable/MovableComponent.hpp index 7656a960..13c8bf90 100644 --- a/sources/Component/Movable/MovableComponent.hpp +++ b/sources/Component/Movable/MovableComponent.hpp @@ -18,10 +18,17 @@ namespace BBM //! @brief The velocity of the entity. Vector3f _velocity; public: + //! @brief Add an instant force to this entity. //! @param force The force to add to this entity's acceleration. The force is added instantly and in one go. void addForce(Vector3f force); + //! @brief Set velocity to 0 + void resetVelocity(void); + + //! @brief Get velocity + const Vector3f &getVelocity(void) const; + //! @inherit WAL::Component *clone(WAL::Entity &entity) const override; diff --git a/sources/Models/Vector3.hpp b/sources/Models/Vector3.hpp index def6d322..dc1752f1 100644 --- a/sources/Models/Vector3.hpp +++ b/sources/Models/Vector3.hpp @@ -166,6 +166,22 @@ namespace BBM { return RAY::Vector3(this->x, this->y, this->z); } + + static Vector3 min(Vector3 a, Vector3 b) + { + Vector3 min = { std::min(a.x, b.x), + std::min(a.y, b.y), + std::min(a.z, b.z)}; + return min; + } + + static Vector3 max(Vector3 a, Vector3 b) + { + Vector3 max = { std::max(a.x, b.x), + std::max(a.y, b.y), + std::max(a.z, b.z)}; + return max; + } }; typedef Vector3 Vector3f; diff --git a/sources/Runner/Runner.cpp b/sources/Runner/Runner.cpp index bb3e8e63..36b96603 100644 --- a/sources/Runner/Runner.cpp +++ b/sources/Runner/Runner.cpp @@ -10,11 +10,14 @@ #include #include #include +#include #include #include #include #include +#include #include +#include #include #include #include @@ -44,6 +47,7 @@ namespace BBM wal.addSystem() .addSystem() .addSystem() + .addSystem(wal) .addSystem(); } @@ -53,6 +57,7 @@ namespace BBM RAY::Window &window = RAY::Window::getInstance(600, 400, "Bomberman", FLAG_WINDOW_RESIZABLE); wal.addSystem>(); + wal.addSystem>(); wal.addSystem(window) .addSystem>(); @@ -62,18 +67,26 @@ namespace BBM std::shared_ptr loadGameScene() { auto scene = std::make_shared(); - scene->addEntity("cube") - .addComponent() - .addComponent>(Vector2f(), Vector2f(10, 10), RED) - .addComponent() - .addComponent() - .addComponent();; scene->addEntity("player") .addComponent() .addComponent>("assets/player/player.iqm", std::make_pair(MAP_DIFFUSE, "assets/player/blue.png")) .addComponent() .addComponent() + .addComponent(2) .addComponent(); + scene->addEntity("cube") + .addComponent(-5, 0, -5) + .addComponent>(Vector3f(-5, 0, -5), Vector3f(3, 3, 3), RED) + .addComponent() + .addComponent() + .addComponent([](WAL::Entity &, const WAL::Entity &){}, + [](WAL::Entity &actual, const WAL::Entity &) { + try { + auto &mov = actual.getComponent(); + mov.resetVelocity(); + } catch (std::exception &e) { }; + }, 3); + scene->addEntity("camera") .addComponent(8, 15, -15) .addComponent(Vector3f(8, 0, 8)); diff --git a/sources/System/Collision/CollisionSystem.cpp b/sources/System/Collision/CollisionSystem.cpp new file mode 100644 index 00000000..24971304 --- /dev/null +++ b/sources/System/Collision/CollisionSystem.cpp @@ -0,0 +1,52 @@ +// +// Created by Louis Auzuret on 5/20/21 +// + +#include "Component/Movable/MovableComponent.hpp" +#include "Component/Position/PositionComponent.hpp" +#include "Component/Collision/CollisionComponent.hpp" +#include "System/Collision/CollisionSystem.hpp" + +namespace BBM +{ + CollisionSystem::CollisionSystem(WAL::Wal &wal) + : WAL::System({typeid(PositionComponent), typeid(CollisionComponent)}), _wal(wal) + { + + } + + bool CollisionSystem::collide(Vector3f minA, Vector3f maxA, Vector3f minB, Vector3f maxB) + { + bool overlapX = (minA.x <= maxB.x && maxA.x >= minB.x) || (minB.x <= maxA.x && maxB.x >= minA.x); + bool overlapY = (minA.y <= maxB.y && maxA.y >= minB.y) || (minB.y <= maxA.y && maxB.y >= minA.y); + bool overlapZ = (minA.z <= maxB.z && maxA.z >= minB.z) || (minB.z <= maxA.z && maxB.z >= minA.z); + + return (overlapX && overlapY && overlapZ); + } + + void CollisionSystem::onFixedUpdate(WAL::Entity &entity) + { + auto &posA = entity.getComponent(); + auto &col = entity.getComponent(); + Vector3f position = posA.position; + if (entity.hasComponent(typeid(MovableComponent))) + position += entity.getComponent().getVelocity(); + Vector3f minA = Vector3f::min(position, position + col.bound); + Vector3f maxA = Vector3f::max(position, position + col.bound); + for (auto &other : _wal.scene->getEntities()) { + if (&other == &entity) + continue; + if (!other.hasComponent() || + !other.hasComponent()) + continue; + auto colB = other.getComponent(); + auto posB = other.getComponent().position; + Vector3f minB = Vector3f::min(posB, posB + colB.bound); + Vector3f maxB = Vector3f::max(posB, posB + colB.bound); + if (collide(minA, maxA, minB, maxB)) { + col.onCollide(entity, other); + colB.onCollided(entity, other); + } + } + } +} \ No newline at end of file diff --git a/sources/System/Collision/CollisionSystem.hpp b/sources/System/Collision/CollisionSystem.hpp new file mode 100644 index 00000000..c4c2de25 --- /dev/null +++ b/sources/System/Collision/CollisionSystem.hpp @@ -0,0 +1,36 @@ + +// +// Created by Louis Auzuret on 5/20/21 +// + +#pragma once + +#include +#include "Wal.hpp" +#include "System/System.hpp" + +namespace BBM +{ + //! @brief A system to handle collisions. + class CollisionSystem : public WAL::System + { + private: + //! @brief reference to the ECS engine to get other entities + WAL::Wal &_wal; + public: + //! @inherit + void onFixedUpdate(WAL::Entity &entity) override; + + //! @brief A default constructor + CollisionSystem(WAL::Wal &wal); + //! @brief A Collision system is copy constructable + CollisionSystem(const CollisionSystem &) = default; + //! @brief A default destructor + ~CollisionSystem() override = default; + //! @brief A Collision system is assignable. + CollisionSystem &operator=(const CollisionSystem &) = default; + + //! @brief check AABB collision + static bool collide(Vector3f minA, Vector3f maxA, Vector3f minB, Vector3f maxB); + }; +} \ No newline at end of file diff --git a/tests/CollisionTest.cpp b/tests/CollisionTest.cpp new file mode 100644 index 00000000..69c75207 --- /dev/null +++ b/tests/CollisionTest.cpp @@ -0,0 +1,97 @@ +// +// Created by Louis Auzuret on 5/31/21. +// + +#include +#include "Entity/Entity.hpp" +#include "Component/Position/PositionComponent.hpp" +#include "Component/Movable/MovableComponent.hpp" +#include "System/Movable/MovableSystem.hpp" +#include "System/Collision/CollisionSystem.hpp" +#include "Wal.hpp" + +#define private public +#include "Component/Collision/CollisionComponent.hpp" + +using namespace WAL; +using namespace BBM; + + +TEST_CASE("Collision test", "[Component][System]") +{ + Wal wal; + CollisionSystem collision(wal); + wal.scene = std::shared_ptr(new Scene); + wal.scene->addEntity("player") + .addComponent() + .addComponent([](Entity &actual, const Entity &) { + try { + auto &pos = actual.getComponent(); + pos.position.x = 1; + pos.position.y = 1; + pos.position.z = 1; + } catch (std::exception &e) {}; + }, [](Entity &, const Entity &){}, 5.0); + Entity &entity = wal.scene->getEntities()[0]; + REQUIRE(entity.getComponent().position == Vector3f()); + + entity.getComponent().bound.x = 5; + entity.getComponent().bound.y = 5; + entity.getComponent().bound.z = 5; + + collision.onUpdate(entity, std::chrono::nanoseconds(1)); + collision.onFixedUpdate(entity); + REQUIRE(entity.getComponent().position.x == 0.0); + REQUIRE(entity.getComponent().position.y == 0.0); + REQUIRE(entity.getComponent().position.z == 0.0); + + wal.scene->addEntity("block") + .addComponent(2,2,2) + .addComponent(1); + Entity &player = wal.scene->getEntities()[0]; + collision.onUpdate(entity, std::chrono::nanoseconds(1)); + REQUIRE(player.hasComponent(typeid(PositionComponent))); + collision.onFixedUpdate(player); + REQUIRE(wal.scene->getEntities().size() == 2); + REQUIRE(player.hasComponent(typeid(PositionComponent))); + REQUIRE(player.getComponent().position.x == 1.0); + REQUIRE(player.getComponent().position.y == 1); + REQUIRE(player.getComponent().position.z == 1); +} + + +TEST_CASE("Collsion test with movable", "[Component][System]") +{ + Wal wal; + CollisionSystem collision(wal); + MovableSystem movable; + wal.scene = std::shared_ptr(new Scene); + wal.scene->addEntity("player") + .addComponent() + .addComponent([](Entity &actual, const Entity &) {}, [](Entity &actual, const Entity &) {}, 5.0) + .addComponent(); + + wal.scene->addEntity("block") + .addComponent(0, 0, 0) + .addComponent([](Entity &actual, const Entity &){}, [](Entity &actual, const Entity &) { + try { + auto &mov = actual.getComponent(); + mov.resetVelocity(); + } catch (std::exception &e) {}; + }, 1); + Entity &entity = wal.scene->getEntities()[0]; + REQUIRE(entity.getComponent().position == Vector3f()); + + entity.getComponent().bound.x = 5; + entity.getComponent().bound.y = 5; + entity.getComponent().bound.z = 5; + + entity.getComponent().addForce({1, 1, 1}); + collision.onUpdate(entity, std::chrono::nanoseconds(1)); + collision.onFixedUpdate(entity); + movable.onUpdate(entity, std::chrono::nanoseconds(1)); + movable.onFixedUpdate(entity); + REQUIRE(entity.getComponent().position.x == 0.0); + REQUIRE(entity.getComponent().position.y == 0.0); + REQUIRE(entity.getComponent().position.z == 0.0); +} \ No newline at end of file