From f2e4eea4c7caf3b10112347f84cbbe3dc0995c93 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Sat, 28 Feb 2026 20:31:38 +0200 Subject: [PATCH] Introduce local transform into shapes and colliders Signed-off-by: Mihai Dumitrescu --- engine/include/velos/graphics/shapes/circle.h | 9 +++--- engine/include/velos/graphics/shapes/line.h | 4 +-- .../include/velos/graphics/shapes/polygon.h | 4 +-- .../include/velos/graphics/shapes/rectangle.h | 16 +++++----- engine/include/velos/graphics/shapes/shape.h | 7 ++-- engine/include/velos/math/transform.h | 16 ++++++++++ engine/include/velos/physics/aabb_collider.h | 10 +++--- .../include/velos/physics/circle_collider.h | 12 +++---- engine/include/velos/physics/collider.h | 2 ++ engine/src/entity/entity.cpp | 3 +- engine/src/graphics/renderer.cpp | 4 +-- engine/src/graphics/shapes/circle.cpp | 11 ++++--- engine/src/graphics/shapes/line.cpp | 12 +++++-- engine/src/graphics/shapes/polygon.cpp | 9 ++++-- engine/src/graphics/shapes/rectangle.cpp | 32 +++++++++++++------ engine/src/physics/aabb_collider.cpp | 12 ++++--- engine/src/physics/circle_collider.cpp | 11 ++++--- examples/example-camera/src/main.cpp | 5 +-- examples/sandbox/src/main.cpp | 20 ++++++++---- 19 files changed, 128 insertions(+), 71 deletions(-) diff --git a/engine/include/velos/graphics/shapes/circle.h b/engine/include/velos/graphics/shapes/circle.h index f019010..18c7686 100644 --- a/engine/include/velos/graphics/shapes/circle.h +++ b/engine/include/velos/graphics/shapes/circle.h @@ -5,13 +5,12 @@ namespace velos { class Circle: public Shape { public: - Circle(const Vec2& center, float radius, const Color& color) - : m_center(center), m_radius(radius) { m_color = color; } + Circle(float radius, const Color& color) + : m_radius(radius) { m_color = color; } - void render(Renderer& renderer, const Transform& transform) override; - bool containsPoint(const Vec2& worldPos, const Transform& transform) const override; + void render(Renderer& renderer, const Transform& entityTransform) override; + bool containsPoint(const Vec2& worldPos, const Transform& entityTransform) const override; private: - Vec2 m_center{0.f, 0.f}; float m_radius = 0; }; } diff --git a/engine/include/velos/graphics/shapes/line.h b/engine/include/velos/graphics/shapes/line.h index 8ea1201..9c15c53 100644 --- a/engine/include/velos/graphics/shapes/line.h +++ b/engine/include/velos/graphics/shapes/line.h @@ -9,8 +9,8 @@ namespace velos { Line(Vec2 start, Vec2 end, const Color& color) : m_start(start), m_end(end) { m_color = color; } - void render(Renderer& renderer, const Transform& transform) override; - bool containsPoint(const Vec2& worldPos, const Transform& transform) const override; + void render(Renderer& renderer, const Transform& entityTransform) override; + bool containsPoint(const Vec2& worldPos, const Transform& entityTransform) const override; private: Vec2 m_start, m_end; }; diff --git a/engine/include/velos/graphics/shapes/polygon.h b/engine/include/velos/graphics/shapes/polygon.h index f80e1a7..0a6453b 100644 --- a/engine/include/velos/graphics/shapes/polygon.h +++ b/engine/include/velos/graphics/shapes/polygon.h @@ -10,8 +10,8 @@ namespace velos { Polygon(const std::vector& points, const Color& color) : m_points(points) { m_color = color; } - void render(Renderer& renderer, const Transform& transform) override; - bool containsPoint(const Vec2& worldPos, const Transform& transform) const override; + void render(Renderer& renderer, const Transform& entityTransform) override; + bool containsPoint(const Vec2& worldPos, const Transform& entityTransform) const override; private: std::vector m_points; }; diff --git a/engine/include/velos/graphics/shapes/rectangle.h b/engine/include/velos/graphics/shapes/rectangle.h index ee44fe7..fef0536 100644 --- a/engine/include/velos/graphics/shapes/rectangle.h +++ b/engine/include/velos/graphics/shapes/rectangle.h @@ -6,18 +6,18 @@ namespace velos { class Rectangle: public Shape { public: - Rectangle(const Vec2& pos, const Vec2& size, const Color& color) - : m_pos(pos), m_size(size) { m_color = color; } + Rectangle(const Vec2& size, const Color& color) + : m_size(size) { m_color = color; } - void render(Renderer& renderer, const Transform& transform) override; - bool containsPoint(const Vec2& worldPos, const Transform& transform) const override; + void render(Renderer& renderer, const Transform& entityTransform) override; + bool containsPoint(const Vec2& worldPos, const Transform& entityTransform) const override; - void setPosition(const Vec2& pos) { m_pos = pos; } + void setPosition(const Vec2& pos) { m_localTransform.position = pos; } void setSize(const Vec2& size) { m_size = size; } - float getX() const { return m_pos.x; } - float getY() const { return m_pos.y; } + float getX() const { return m_localTransform.position.x; } + float getY() const { return m_localTransform.position.y; } private: - Vec2 m_pos, m_size; + Vec2 m_size; }; } diff --git a/engine/include/velos/graphics/shapes/shape.h b/engine/include/velos/graphics/shapes/shape.h index 7d51331..9765c3c 100644 --- a/engine/include/velos/graphics/shapes/shape.h +++ b/engine/include/velos/graphics/shapes/shape.h @@ -6,11 +6,14 @@ namespace velos { class Shape { public: virtual ~Shape() = default; - virtual void render(Renderer& renderer, const Transform& transform) = 0; - virtual bool containsPoint(const Vec2& worldPos, const Transform& transform) const = 0; + virtual void render(Renderer& renderer, const Transform& entityTransform) = 0; + virtual bool containsPoint(const Vec2& worldPos, const Transform& entityTransform) const = 0; void setColor(const Color& color) { m_color = color; } + + Transform& transform() { return m_localTransform; } protected: + Transform m_localTransform; Color m_color{255, 255, 255, 255}; }; } diff --git a/engine/include/velos/math/transform.h b/engine/include/velos/math/transform.h index 191010d..6e23706 100644 --- a/engine/include/velos/math/transform.h +++ b/engine/include/velos/math/transform.h @@ -27,6 +27,10 @@ namespace velos { return { x * s, y * s }; } + Vec2 operator*(const Vec2& o) { + return { x * o.x, y * o.y }; + } + Vec2 operator-(const Vec2& o) const { return { x - o.x, y - o.y }; } @@ -90,5 +94,17 @@ namespace velos { return { x / scale.x, y / scale.y }; } + + Transform combine(const Transform& o) const { + Transform transform; + + transform.scale.x = scale.x * o.scale.x; + transform.scale.y = scale.y + o.scale.y; + + transform.rotation = rotation + o.rotation; + + transform.position = o.apply(position); + return transform; + } }; } diff --git a/engine/include/velos/physics/aabb_collider.h b/engine/include/velos/physics/aabb_collider.h index 38289c0..e63f28b 100644 --- a/engine/include/velos/physics/aabb_collider.h +++ b/engine/include/velos/physics/aabb_collider.h @@ -4,21 +4,21 @@ namespace velos { class AABBCollider: public Collider { public: - AABBCollider(const Vec2& size, const Vec2& offset = {0.f, 0.f}) - : m_size(size), m_offset(offset) {} + AABBCollider(const Vec2& size) + : m_size(size), m_worldSize(size) {} ColliderType type() const override; - void computeWorld(const Transform& transform) override; + void computeWorld(const Transform& entityTransform) override; bool containsPoint(const Vec2& point) const override; void setSize(const Vec2 size); void debugRender(Renderer& renderer) const override; - const Vec2& size() const { return m_size; } + const Vec2& size() const { return m_worldSize; } const Vec2& worldPosition() const { return m_worldPosition; } private: Vec2 m_size; - Vec2 m_offset; + Vec2 m_worldSize; }; } diff --git a/engine/include/velos/physics/circle_collider.h b/engine/include/velos/physics/circle_collider.h index 06a187f..e377bd3 100644 --- a/engine/include/velos/physics/circle_collider.h +++ b/engine/include/velos/physics/circle_collider.h @@ -4,21 +4,21 @@ namespace velos { class CircleCollider: public Collider { public: - CircleCollider(float radius, const Vec2& offset = {0.f, 0.f}) - : m_radius(radius), m_offset(offset) {} + CircleCollider(float radius) + : m_radius(radius) {} ColliderType type() const override; - void computeWorld(const Transform& transform) override; + void computeWorld(const Transform& entityTransform) override; bool containsPoint(const Vec2& point) const override; - void setRadius(float radius); + void setRadius(float radius) { m_radius = radius; } void debugRender(Renderer& renderer) const override; - float radius() const { return m_radius; } + float radius() const { return m_worldRadius; } const Vec2& worldPosition() const { return m_worldPosition; } private: float m_radius; - Vec2 m_offset; + float m_worldRadius = 0.f; }; } diff --git a/engine/include/velos/physics/collider.h b/engine/include/velos/physics/collider.h index 91b423a..3d86422 100644 --- a/engine/include/velos/physics/collider.h +++ b/engine/include/velos/physics/collider.h @@ -23,6 +23,7 @@ namespace velos { void setSolidCollision(bool state) { m_isSolid = state; } bool isSolidCollision() { return m_isSolid; } + Transform& transform() { return m_localTransform; } Entity* owner() const { return m_owner; } void setOwner(Entity* owner) { m_owner = owner; } @@ -43,6 +44,7 @@ namespace velos { protected: Vec2 m_worldPosition{0.f, 0.f}; + Transform m_localTransform; bool m_isSolid = true; Entity* m_owner = nullptr; diff --git a/engine/src/entity/entity.cpp b/engine/src/entity/entity.cpp index 86d85d3..204a8ce 100644 --- a/engine/src/entity/entity.cpp +++ b/engine/src/entity/entity.cpp @@ -3,8 +3,8 @@ namespace velos { void Entity::update(float dt) { - updateColliders(); m_transform.position += m_velocity * dt; + updateColliders(); } void Entity::render(Renderer& renderer) { @@ -19,7 +19,6 @@ namespace velos { bool Entity::containsPoint(const Vec2& worldPos) const { for (auto& shape: m_shapes) { - Vec2 local = m_transform.applyInverse(worldPos); if (shape->containsPoint(worldPos, m_transform)) return true; } diff --git a/engine/src/graphics/renderer.cpp b/engine/src/graphics/renderer.cpp index d54f5eb..8f3ea2c 100644 --- a/engine/src/graphics/renderer.cpp +++ b/engine/src/graphics/renderer.cpp @@ -69,8 +69,8 @@ namespace velos { for (auto& point: points) t_points.push_back(applyCamera(point)); - float minY = points[0].y; - float maxY = points[0].y;; + float minY = t_points[0].y; + float maxY = t_points[0].y;; for (const auto& point: t_points) { minY = std::min(minY, point.y); maxY = std::max(maxY, point.y); diff --git a/engine/src/graphics/shapes/circle.cpp b/engine/src/graphics/shapes/circle.cpp index 2fb4afb..acade84 100644 --- a/engine/src/graphics/shapes/circle.cpp +++ b/engine/src/graphics/shapes/circle.cpp @@ -1,17 +1,18 @@ #include "velos/graphics/shapes/circle.h" namespace velos { - void Circle::render(Renderer& renderer, const Transform& transform) { - Vec2 center = transform.apply(m_center); + void Circle::render(Renderer& renderer, const Transform& entityTransform) { + Transform transform = m_localTransform.combine(entityTransform); + Vec2 center = transform.position; float radius = m_radius * transform.scale.x; renderer.drawCircle(center, radius, m_color); } - bool Circle::containsPoint(const Vec2& worldPos, const Transform& transform) const { + bool Circle::containsPoint(const Vec2& worldPos, const Transform& entityTransform) const { + Transform transform = m_localTransform.combine(entityTransform); Vec2 local = transform.applyInverse(worldPos); - Vec2 df = local - m_center; - return df.x * df.x + df.y * df.y <= m_radius * m_radius; + return local.lengthSquared() <= m_radius * m_radius; } } diff --git a/engine/src/graphics/shapes/line.cpp b/engine/src/graphics/shapes/line.cpp index 21b6d3c..8c12ba1 100644 --- a/engine/src/graphics/shapes/line.cpp +++ b/engine/src/graphics/shapes/line.cpp @@ -1,20 +1,26 @@ #include "velos/graphics/shapes/line.h" namespace velos { - void Line::render(Renderer& renderer, const Transform& transform) { + void Line::render(Renderer& renderer, const Transform& entityTransform) { + Transform transform = m_localTransform.combine(entityTransform); + Vec2 start = transform.apply(m_start); Vec2 end = transform.apply(m_end); renderer.drawLine(start, end, m_color); } - bool Line::containsPoint(const Vec2& worldPos, const Transform& transform) const { + bool Line::containsPoint(const Vec2& worldPos, const Transform& entityTransform) const { + Transform transform = m_localTransform.combine(entityTransform); Vec2 local = transform.applyInverse(worldPos); Vec2 p1 = m_end - m_start; Vec2 p2 = local - m_start; - float t = (p2.x * p1.x + p2.y * p1.y) / (p1.x * p1.x + p2.y * p2.y); + if (p1.lengthSquared() < 0.001f) + return false; + + float t = (p2.x * p1.x + p2.y * p1.y) / p1.lengthSquared(); if (t < 0.f || t > 1.f) return false; diff --git a/engine/src/graphics/shapes/polygon.cpp b/engine/src/graphics/shapes/polygon.cpp index b89fcfc..38e2a8a 100644 --- a/engine/src/graphics/shapes/polygon.cpp +++ b/engine/src/graphics/shapes/polygon.cpp @@ -1,15 +1,20 @@ #include "velos/graphics/shapes/polygon.h" namespace velos { - void Polygon::render(Renderer& renderer, const Transform& transform) { + void Polygon::render(Renderer& renderer, const Transform& entityTransform) { + Transform transform = m_localTransform.combine(entityTransform); + std::vector points; + points.reserve(m_points.size()); + for (const auto& point: m_points) points.emplace_back(transform.apply(point)); renderer.drawPolygon(points, m_color); } - bool Polygon::containsPoint(const Vec2& worldPos, const Transform& transform) const { + bool Polygon::containsPoint(const Vec2& worldPos, const Transform& entityTransform) const { + Transform transform = m_localTransform.combine(entityTransform); Vec2 local = transform.applyInverse(worldPos); int intersections = 0; diff --git a/engine/src/graphics/shapes/rectangle.cpp b/engine/src/graphics/shapes/rectangle.cpp index 9772f11..f17dc21 100644 --- a/engine/src/graphics/shapes/rectangle.cpp +++ b/engine/src/graphics/shapes/rectangle.cpp @@ -2,19 +2,33 @@ #include "velos/graphics/renderer.h" namespace velos { - void Rectangle::render(Renderer& renderer, const Transform& transform) { - Vec2 p1 = transform.apply(m_pos); - Vec2 p2 = transform.apply(m_pos + Vec2{m_size.x, 0.f}); - Vec2 p3 = transform.apply(m_pos + m_size); - Vec2 p4 = transform.apply(m_pos + Vec2(0, m_size.y)); + void Rectangle::render(Renderer& renderer, const Transform& entityTransform) { + Transform transform = m_localTransform.combine(entityTransform); - renderer.drawPolygon({ p1, p2, p3, p4 }, m_color); + if (std::abs(transform.rotation) < 0.001f) { + Vec2 topLeft = transform.apply({-m_size.x / 2.f, -m_size.y / 2.f}); + Vec2 scaled = m_size * transform.scale; + renderer.drawRect(topLeft, scaled, m_color); + } else { + float x = m_size.x / 2.f; + float y = m_size.y / 2.f; + + Vec2 p1 = transform.apply({-x, -y}); + Vec2 p2 = transform.apply({x, -y}); + Vec2 p3 = transform.apply({x, y}); + Vec2 p4 = transform.apply({-x, y}); + + renderer.drawPolygon({ p1, p2, p3, p4 }, m_color); + } } - bool Rectangle::containsPoint(const Vec2& worldPos, const Transform& transform) const { + bool Rectangle::containsPoint(const Vec2& worldPos, const Transform& entityTransform) const { + Transform transform = m_localTransform.combine(entityTransform); Vec2 local = transform.applyInverse(worldPos); - return local.x >= m_pos.x && local.x <= m_pos.x + m_size.x && - local.y >= m_pos.y && local.y <= m_pos.y + m_size.y; + float x = m_size.x / 2.f; + float y = m_size.y / 2.f; + return local.x >= -x && local.x <= x && + local.y >= -y && local.y <= y; } } diff --git a/engine/src/physics/aabb_collider.cpp b/engine/src/physics/aabb_collider.cpp index caff0aa..dbf1d6d 100644 --- a/engine/src/physics/aabb_collider.cpp +++ b/engine/src/physics/aabb_collider.cpp @@ -5,15 +5,17 @@ namespace velos { return ColliderType::AABB; } - void AABBCollider::computeWorld(const Transform& transform) { - m_worldPosition = transform.position + m_offset; + void AABBCollider::computeWorld(const Transform& entityTransform) { + Transform transform = m_localTransform.combine(entityTransform); + m_worldSize = m_size * transform.scale; + m_worldPosition = transform.position - (m_worldSize / 2.f); } bool AABBCollider::containsPoint(const Vec2& point) const { return point.x >= m_worldPosition.x && - point.x <= m_worldPosition.x + m_size.x && + point.x <= m_worldPosition.x + m_worldSize.x && point.y >= m_worldPosition.y && - point.y <= m_worldPosition.y + m_size.y; + point.y <= m_worldPosition.y + m_worldSize.y; } void AABBCollider::setSize(const Vec2 size) { @@ -21,6 +23,6 @@ namespace velos { } void AABBCollider::debugRender(Renderer& renderer) const { - renderer.drawRect(m_worldPosition, m_size, Color{117, 145, 166, 100}); + renderer.drawRect(m_worldPosition, m_worldSize, Color{117, 145, 166, 100}); } } diff --git a/engine/src/physics/circle_collider.cpp b/engine/src/physics/circle_collider.cpp index 9246627..6e0093a 100644 --- a/engine/src/physics/circle_collider.cpp +++ b/engine/src/physics/circle_collider.cpp @@ -5,16 +5,19 @@ namespace velos { return ColliderType::Circle; } - void CircleCollider::computeWorld(const Transform& transform) { - m_worldPosition = transform.position + m_offset; + void CircleCollider::computeWorld(const Transform& entityTransform) { + Transform transform = m_localTransform.combine(entityTransform); + + m_worldPosition = transform.position; + m_worldRadius = m_radius * transform.scale.x; } bool CircleCollider::containsPoint(const Vec2& point) const { Vec2 delta = point - m_worldPosition; - return delta.lengthSquared() <= m_radius * m_radius; + return delta.lengthSquared() <= m_worldRadius * m_worldRadius; } void CircleCollider::debugRender(Renderer& renderer) const { - renderer.drawCircle(m_worldPosition, m_radius, Color{117, 145, 166, 100}); + renderer.drawCircle(m_worldPosition, m_worldRadius, Color{117, 145, 166, 100}); } } diff --git a/examples/example-camera/src/main.cpp b/examples/example-camera/src/main.cpp index fb790a1..71db194 100644 --- a/examples/example-camera/src/main.cpp +++ b/examples/example-camera/src/main.cpp @@ -20,8 +20,9 @@ int main() { velos::Engine engine(config); - auto rect = std::make_shared(velos::Vec2{500.f, 500.f}, velos::Vec2{70.f, 70.f}, velos::Color{0, 0, 255}); - auto circle = std::make_shared(velos::Vec2{100.f, 100.f}, 50, velos::Color{255, 255, 0}); + auto rect = std::make_shared(velos::Vec2{70.f, 70.f}, velos::Color{0, 0, 255}); + auto circle = std::make_shared(50, velos::Color{255, 255, 0}); + circle->transform().position = velos::Vec2{100.f, 100.f}; auto entity = std::make_shared(); entity->addShape(rect); diff --git a/examples/sandbox/src/main.cpp b/examples/sandbox/src/main.cpp index f0ab2f9..53afcda 100644 --- a/examples/sandbox/src/main.cpp +++ b/examples/sandbox/src/main.cpp @@ -23,23 +23,29 @@ int main() { engine.camera()->transform().position = velos::Vec2{config.window_width / 2.f, config.window_height / 2.f}; velos::Vec2 size{50.f, 50.f}; - auto rect = std::make_shared(velos::Vec2{0.f, 0.f} - size / 2.f, size, velos::Color{255, 0, 0}); - auto circle = std::make_shared(velos::Vec2{100.f, 100.f}, 50, velos::Color{255, 255, 0}); - auto rect2 = std::make_shared(velos::Vec2{100.f, 220.f}, size * 1.5f, velos::Color{0, 0, 255}); + auto rect = std::make_shared(size, velos::Color{255, 0, 0}); + auto circle = std::make_shared(50, velos::Color{255, 255, 0}); + circle->transform().position = velos::Vec2{100.f, 0.f}; + + auto rect2 = std::make_shared(size * 1.5f, velos::Color{0, 0, 255}); + rect2->transform().position = velos::Vec2{100.f, 150.f}; auto entity = std::make_shared(); entity->addShape(rect); - entity->transform().position = {200, 200}; + entity->transform().position = {200.f, 200.f}; - auto collider = std::make_unique(size, velos::Vec2{0.f, 0.f} - size / 2.f); + auto collider = std::make_unique(size); entity->addCollider(std::move(collider)); auto stationary_entity = std::make_shared(); + stationary_entity->transform().position = {300.f, 300.f}; stationary_entity->addShape(circle); stationary_entity->addShape(rect2); - auto stationary_collider = std::make_unique(50, velos::Vec2{100.f, 100.f}); - auto stationary_collider2 = std::make_unique(size * 1.5f, velos::Vec2{100.f, 220.f}); + auto stationary_collider = std::make_unique(50.f); + stationary_collider->transform().position = circle->transform().position; + auto stationary_collider2 = std::make_unique(size * 1.5f); + stationary_collider2->transform().position = rect2->transform().position; /* stationary_collider->setOnCollisionEnter([](velos::Collider& other) { VELOS_INFO("Entered collision!");