From d40985c04aab355f53780e26e527467e4d99c990 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sun, 1 Feb 2026 01:41:56 +0200 Subject: [PATCH 1/5] Add getter methods for AABB and Circle colliders Signed-off-by: Mihai Dumitrescu --- engine/include/velos/physics/aabb_collider.h | 3 +++ engine/include/velos/physics/circle_collider.h | 3 +++ 2 files changed, 6 insertions(+) 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; From 5dcf2158ed7d96cda589f0ac3c4d8da1800a5d2c Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sun, 1 Feb 2026 01:45:39 +0200 Subject: [PATCH 2/5] Implement collision events for entities Signed-off-by: Mihai Dumitrescu --- engine/include/velos/entity/entity.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/include/velos/entity/entity.h b/engine/include/velos/entity/entity.h index 0170198..b9bf14a 100644 --- a/engine/include/velos/entity/entity.h +++ b/engine/include/velos/entity/entity.h @@ -14,6 +14,11 @@ namespace velos { virtual void update(float dt) { updateColliders(); } virtual void render(Renderer& renderer); + + virtual void onCollisionEnter(Entity& other) {}; + virtual void onCollisionStay(Entity& other) {}; + virtual void onCollisionExit(Entity& other) {}; + void debugRender(Renderer& renderer); bool containsPoint(const Vec2& worldPos) const; From 7af83b3fea48f8613568ab2b9389233cdf50eda4 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sun, 1 Feb 2026 12:57:44 +0200 Subject: [PATCH 3/5] Implement basic physics system Signed-off-by: Mihai Dumitrescu --- engine/include/velos/physics/physics_system.h | 30 +++++ engine/src/physics/physics_system.cpp | 111 ++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 engine/include/velos/physics/physics_system.h create mode 100644 engine/src/physics/physics_system.cpp 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/physics/physics_system.cpp b/engine/src/physics/physics_system.cpp new file mode 100644 index 0000000..4eeee80 --- /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->onCollisionEnter(*e2); + e2->onCollisionEnter(*e1); + } else { + e1->onCollisionStay(*e2); + e2->onCollisionStay(*e1); + } + } + + for (const auto& pair: m_previous) { + if (m_current.find(pair) == m_current.end()) { + Entity* e1 = pair.first; + Entity* e2 = pair.second; + + e1->onCollisionExit(*e2); + e2->onCollisionExit(*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(); + } +} From a096b947e5ae0368e1c2843f065f347b25e78ff3 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Mon, 2 Feb 2026 01:28:51 +0200 Subject: [PATCH 4/5] Add physics system inside entity manager Signed-off-by: Mihai Dumitrescu --- engine/include/velos/entity/entity_manager.h | 3 +++ engine/src/entity/entity_manager.cpp | 1 + 2 files changed, 4 insertions(+) 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/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) { From e6ba31b366b8f9017046f8f03277879205c080d4 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Tue, 3 Feb 2026 14:58:26 +0200 Subject: [PATCH 5/5] Introduce user given collision events Signed-off-by: Mihai Dumitrescu --- engine/include/velos/entity/entity.h | 15 +++++++++++++++ engine/src/entity/entity.cpp | 18 ++++++++++++++++++ engine/src/physics/physics_system.cpp | 12 ++++++------ examples/sandbox/src/main.cpp | 10 ++++++++++ 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/engine/include/velos/entity/entity.h b/engine/include/velos/entity/entity.h index b9bf14a..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" @@ -15,10 +16,20 @@ 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; @@ -34,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/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/physics/physics_system.cpp b/engine/src/physics/physics_system.cpp index 4eeee80..99bbf1e 100644 --- a/engine/src/physics/physics_system.cpp +++ b/engine/src/physics/physics_system.cpp @@ -25,11 +25,11 @@ namespace velos { Entity* e2 = pair.second; if (m_previous.find(pair) == m_previous.end()) { - e1->onCollisionEnter(*e2); - e2->onCollisionEnter(*e1); + e1->dispatchCollisionEnter(*e2); + e2->dispatchCollisionEnter(*e1); } else { - e1->onCollisionStay(*e2); - e2->onCollisionStay(*e1); + e1->dispatchCollisionStay(*e2); + e2->dispatchCollisionStay(*e1); } } @@ -38,8 +38,8 @@ namespace velos { Entity* e1 = pair.first; Entity* e2 = pair.second; - e1->onCollisionExit(*e2); - e2->onCollisionExit(*e1); + e1->dispatchCollisionExit(*e2); + e2->dispatchCollisionExit(*e1); } } 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); });