diff --git a/engine/include/velos/entity/entity.h b/engine/include/velos/entity/entity.h index 0170198..6632539 100644 --- a/engine/include/velos/entity/entity.h +++ b/engine/include/velos/entity/entity.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "../graphics/shapes/shape.h" #include "../physics/collider.h" #include "../graphics/renderer.h" @@ -14,6 +15,21 @@ namespace velos { virtual void update(float dt) { updateColliders(); } virtual void render(Renderer& renderer); + + using CollisionCallback = std::function; + + virtual void onCollisionEnter(Entity& other) {}; + virtual void onCollisionStay(Entity& other) {}; + virtual void onCollisionExit(Entity& other) {}; + + void setOnCollisionEnter(CollisionCallback cb) { m_onEnter = std::move(cb); } + void setOnCollisionStay(CollisionCallback cb) { m_onStay = std::move(cb); } + void setOnCollisionExit(CollisionCallback cb) { m_onExit = std::move(cb); } + + void dispatchCollisionEnter(Entity& other); + void dispatchCollisionStay(Entity& other); + void dispatchCollisionExit(Entity& other); + void debugRender(Renderer& renderer); bool containsPoint(const Vec2& worldPos) const; @@ -29,5 +45,9 @@ namespace velos { Transform m_transform; std::vector> m_shapes; std::vector> m_colliders; + + CollisionCallback m_onEnter; + CollisionCallback m_onStay; + CollisionCallback m_onExit; }; } diff --git a/engine/include/velos/entity/entity_manager.h b/engine/include/velos/entity/entity_manager.h index 41f4434..58e10cb 100644 --- a/engine/include/velos/entity/entity_manager.h +++ b/engine/include/velos/entity/entity_manager.h @@ -2,6 +2,7 @@ #include "entity.h" #include #include +#include "../physics/physics_system.h" namespace velos { class EntityManager { @@ -15,6 +16,8 @@ namespace velos { std::vector> entities() { return m_entities; } private: std::vector> m_entities; + PhysicsSystem m_physics; + bool m_debugRender = false; }; } diff --git a/engine/include/velos/physics/aabb_collider.h b/engine/include/velos/physics/aabb_collider.h index 08a979c..38289c0 100644 --- a/engine/include/velos/physics/aabb_collider.h +++ b/engine/include/velos/physics/aabb_collider.h @@ -14,6 +14,9 @@ namespace velos { void setSize(const Vec2 size); void debugRender(Renderer& renderer) const override; + const Vec2& size() const { return m_size; } + const Vec2& worldPosition() const { return m_worldPosition; } + private: Vec2 m_size; Vec2 m_offset; diff --git a/engine/include/velos/physics/circle_collider.h b/engine/include/velos/physics/circle_collider.h index 91a152f..06a187f 100644 --- a/engine/include/velos/physics/circle_collider.h +++ b/engine/include/velos/physics/circle_collider.h @@ -14,6 +14,9 @@ namespace velos { void setRadius(float radius); void debugRender(Renderer& renderer) const override; + float radius() const { return m_radius; } + const Vec2& worldPosition() const { return m_worldPosition; } + private: float m_radius; Vec2 m_offset; diff --git a/engine/include/velos/physics/physics_system.h b/engine/include/velos/physics/physics_system.h new file mode 100644 index 0000000..d5e6daa --- /dev/null +++ b/engine/include/velos/physics/physics_system.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include +#include +#include "../entity/entity.h" +#include "aabb_collider.h" +#include "circle_collider.h" +#include "collider.h" + +namespace velos { + class PhysicsSystem { + public: + void update(const std::vector>& entities); + + private: + using CollisionPair = std::pair; + + std::set m_previous; + std::set m_current; + + bool checkCollision(Entity& e1, Entity& e2); + static CollisionPair makeOrderedPair(Entity* e1, Entity* e2); + + bool intersects(const Collider& c1, const Collider& c2); + bool aabbVsAabb(const AABBCollider& c1, const AABBCollider& c2); + bool circleVsCircle(const CircleCollider& c1, const CircleCollider& c2); + bool aabbVsCircle(const AABBCollider& c1, const CircleCollider& c2); + }; +} diff --git a/engine/src/entity/entity.cpp b/engine/src/entity/entity.cpp index 9189fc6..9d25797 100644 --- a/engine/src/entity/entity.cpp +++ b/engine/src/entity/entity.cpp @@ -37,4 +37,22 @@ namespace velos { for (auto& collider: m_colliders) collider->computeWorld(m_transform); } + + void Entity::dispatchCollisionEnter(Entity& other) { + onCollisionEnter(other); + if (m_onEnter) + m_onEnter(other); + } + + void Entity::dispatchCollisionStay(Entity& other) { + onCollisionStay(other); + if (m_onStay) + m_onStay(other); + } + + void Entity::dispatchCollisionExit(Entity& other) { + onCollisionExit(other); + if (m_onExit) + m_onExit(other); + } } diff --git a/engine/src/entity/entity_manager.cpp b/engine/src/entity/entity_manager.cpp index 416156e..bd9d0fb 100644 --- a/engine/src/entity/entity_manager.cpp +++ b/engine/src/entity/entity_manager.cpp @@ -4,6 +4,7 @@ namespace velos { void EntityManager::update(float dt) { for (auto& entity: m_entities) entity->update(dt); + m_physics.update(m_entities); } void EntityManager::render(Renderer& renderer) { diff --git a/engine/src/physics/physics_system.cpp b/engine/src/physics/physics_system.cpp new file mode 100644 index 0000000..99bbf1e --- /dev/null +++ b/engine/src/physics/physics_system.cpp @@ -0,0 +1,111 @@ +#include "velos/physics/physics_system.h" + +namespace velos { + PhysicsSystem::CollisionPair PhysicsSystem::makeOrderedPair(Entity* e1, Entity* e2) { + if (e1 < e2) + return std::make_pair(e1, e2); + return std::make_pair(e2, e1); + } + + void PhysicsSystem::update(const std::vector>& entities) { + m_current.clear(); + + for (size_t i = 0; i < entities.size(); ++i) { + for (size_t j = i + 1; j < entities.size(); ++j) { + Entity& e1 = *entities[i]; + Entity& e2 = *entities[j]; + + if (checkCollision(e1, e2)) + m_current.insert(makeOrderedPair(&e1, &e2)); + } + } + + for (const auto& pair: m_current) { + Entity* e1 = pair.first; + Entity* e2 = pair.second; + + if (m_previous.find(pair) == m_previous.end()) { + e1->dispatchCollisionEnter(*e2); + e2->dispatchCollisionEnter(*e1); + } else { + e1->dispatchCollisionStay(*e2); + e2->dispatchCollisionStay(*e1); + } + } + + for (const auto& pair: m_previous) { + if (m_current.find(pair) == m_current.end()) { + Entity* e1 = pair.first; + Entity* e2 = pair.second; + + e1->dispatchCollisionExit(*e2); + e2->dispatchCollisionExit(*e1); + } + } + + m_previous = m_current; + } + + bool PhysicsSystem::checkCollision(Entity& e1, Entity& e2) { + for (const auto& c1: e1.colliders()) + for (const auto& c2: e2.colliders()) + if (intersects(*c1, *c2)) + return true; + return false; + } + + bool PhysicsSystem::intersects(const Collider& c1, const Collider& c2) { + if (c1.type() == ColliderType::AABB && c2.type() == ColliderType::AABB) + return aabbVsAabb( + static_cast(c1), + static_cast(c2) + ); + + if (c1.type() == ColliderType::Circle && c2.type() == ColliderType::Circle) + return circleVsCircle( + static_cast(c1), + static_cast(c2) + ); + + if (c1.type() == ColliderType::AABB && c2.type() == ColliderType::Circle) + return aabbVsCircle( + static_cast(c1), + static_cast(c2) + ); + + if (c1.type() == ColliderType::Circle && c2.type() == ColliderType::AABB) + return aabbVsCircle( + static_cast(c2), + static_cast(c1) + ); + + return false; + } + + bool PhysicsSystem::aabbVsAabb(const AABBCollider& c1, const AABBCollider& c2) { + return c1.worldPosition().x < c2.worldPosition().x + c2.size().x && + c1.worldPosition().x + c1.size().x > c2.worldPosition().x && + c1.worldPosition().y < c2.worldPosition().y + c2.size().y && + c1.worldPosition().y + c1.size().y > c2.worldPosition().y; + } + + bool PhysicsSystem::circleVsCircle(const CircleCollider& c1, const CircleCollider& c2) { + Vec2 d = c1.worldPosition() - c2.worldPosition(); + float r = c1.radius() + c2.radius(); + return d.lengthSquared() <= r * r; + } + + bool PhysicsSystem::aabbVsCircle(const AABBCollider& c1, const CircleCollider& c2) { + Vec2 closest{ + std::clamp(c2.worldPosition().x, + c1.worldPosition().x, + c1.worldPosition().x + c1.size().x), + std::clamp(c2.worldPosition().y, + c1.worldPosition().y, + c1.worldPosition().y + c1.size().y) + }; + + Vec2 delta = c2.worldPosition() - closest; + return delta.lengthSquared() <= c2.radius() * c2.radius(); + } +} diff --git a/examples/sandbox/src/main.cpp b/examples/sandbox/src/main.cpp index 295b68f..aac2a94 100644 --- a/examples/sandbox/src/main.cpp +++ b/examples/sandbox/src/main.cpp @@ -36,6 +36,16 @@ int main() { auto stationary_entity = std::make_shared(); stationary_entity->addShape(circle); + auto stationary_collider = std::make_unique(50, velos::Vec2{100.f, 100.f}); + stationary_entity->addCollider(std::move(stationary_collider)); + + stationary_entity->setOnCollisionEnter([](velos::Entity& other) { + VELOS_INFO("Entered collision!"); + }); + stationary_entity->setOnCollisionExit([](velos::Entity& other) { + VELOS_INFO("Exited collision!"); + }); + float speed = 200.f; engine.setAction(velos::Key::W, "MoveForward", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->move(0, -speed * dt); }); engine.setAction(velos::Key::S, "MoveBackward", velos::ActionType::Hold, 0.05f, [entity, speed](float dt){ entity->move(0, speed * dt); });