From 58c3e74eb6accdb96b5d7d14c3df86e4a41f6d7a Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Fri, 23 Jan 2026 00:35:00 +0200 Subject: [PATCH 1/3] Add camera modes Signed-off-by: Mihai Dumitrescu --- engine/include/velos/graphics/camera/camera_mode.h | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 engine/include/velos/graphics/camera/camera_mode.h diff --git a/engine/include/velos/graphics/camera/camera_mode.h b/engine/include/velos/graphics/camera/camera_mode.h new file mode 100644 index 0000000..674f8f2 --- /dev/null +++ b/engine/include/velos/graphics/camera/camera_mode.h @@ -0,0 +1,8 @@ +#pragma once + +namespace velos { + enum class CameraFollowMode { + Instant, + Smooth + }; +} From e7296806d37a2966ee4d6ae22613c141341c98d8 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Fri, 23 Jan 2026 00:39:49 +0200 Subject: [PATCH 2/3] Implement camera smoothing and look ahead mechanics Signed-off-by: Mihai Dumitrescu --- engine/include/velos/graphics/camera/camera.h | 25 +++++++++ engine/include/velos/math/math.h | 8 +++ engine/include/velos/math/transform.h | 23 ++++++++ engine/src/graphics/camera/camera.cpp | 56 +++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 engine/include/velos/math/math.h diff --git a/engine/include/velos/graphics/camera/camera.h b/engine/include/velos/graphics/camera/camera.h index fe0e00d..88fb8c8 100644 --- a/engine/include/velos/graphics/camera/camera.h +++ b/engine/include/velos/graphics/camera/camera.h @@ -1,5 +1,6 @@ #pragma once #include +#include "camera_mode.h" #include "../../math/transform.h" namespace velos { @@ -9,6 +10,18 @@ namespace velos { Transform& transform() { return m_transform; } + void follow(const Transform* target); + void unfollow(); + bool isFollowing(); + + void setFollowMode(CameraFollowMode mode); + void setSmoothSpeed(float speed); + + void setLookAhead(float distance); + void setLookAheadSmoothing(float speed); + + void update(float dt); + float zoom() { return m_zoom; } void setZoom(float zoom) { m_zoom = std::max(0.01f, zoom); } @@ -17,6 +30,18 @@ namespace velos { private: Transform m_transform; + const Transform* m_target = nullptr; + + CameraFollowMode m_mode = CameraFollowMode::Instant; + float m_smoothSpeed = 8.f; + + float m_lookAheadDistance = 0.f; + float m_lookAheadSmoothing = 6.f; + Vec2 m_currentLookAhead{0.f, 0.f}; + + Vec2 m_lastTargetPos{0.f, 0.f}; + + Vec2 m_offset{0.f, 0.f}; float m_zoom = 1.f; }; } diff --git a/engine/include/velos/math/math.h b/engine/include/velos/math/math.h new file mode 100644 index 0000000..20b453f --- /dev/null +++ b/engine/include/velos/math/math.h @@ -0,0 +1,8 @@ +#pragma once +#include "transform.h" + +namespace velos { + static Vec2 lerp(const Vec2& a, const Vec2& b, float t) { + return a + (b - a) * t; + } +} diff --git a/engine/include/velos/math/transform.h b/engine/include/velos/math/transform.h index 667dcee..227e196 100644 --- a/engine/include/velos/math/transform.h +++ b/engine/include/velos/math/transform.h @@ -24,6 +24,29 @@ namespace velos { Vec2 operator/(float s) const { return { x / s, y / s }; } + + float length() const { + return std::sqrt(x * x + y * y); + } + + float lengthSquared() const { + return x * x + y * y; + } + + Vec2 normalized() const { + float len = length(); + if (len < 0.00001f) + return {0.f, 0.f}; + return {x / len, y / len}; + } + + void normalize() { + float len = length(); + if (len < 0.00001f) + return; + x /= len; + y /= len; + } }; class Transform { diff --git a/engine/src/graphics/camera/camera.cpp b/engine/src/graphics/camera/camera.cpp index c452ba1..151be6a 100644 --- a/engine/src/graphics/camera/camera.cpp +++ b/engine/src/graphics/camera/camera.cpp @@ -1,6 +1,62 @@ #include "velos/graphics/camera/camera.h" +#include +#include "velos/math/math.h" namespace velos { + void Camera::follow(const Transform* target) { + m_target = target; + } + + void Camera::unfollow() { + m_target = nullptr; + } + + bool Camera::isFollowing() { + return m_target != nullptr; + } + + void Camera::setFollowMode(CameraFollowMode mode) { + m_mode = mode; + } + + void Camera::setSmoothSpeed(float speed) { + m_smoothSpeed = speed; + } + + void Camera::setLookAhead(float distance) { + m_lookAheadDistance = distance; + } + + void Camera::setLookAheadSmoothing(float speed) { + m_lookAheadSmoothing = speed; + } + + void Camera::update(float dt) { + if (!m_target) + return; + + Vec2 delta = m_target->position - m_lastTargetPos; + m_lastTargetPos = m_target->position; + Vec2 desiredLookAhead{0.f, 0.f}; + + if (delta.lengthSquared() > 0.001f) { + Vec2 dir = delta.normalized(); + desiredLookAhead = dir * m_lookAheadDistance; + } + + float lookT = 1.f - std::exp(-m_lookAheadSmoothing * dt); + m_currentLookAhead = lerp(m_currentLookAhead, desiredLookAhead, lookT); + + Vec2 desiredPos = m_target->position + m_offset + m_currentLookAhead; + + if (m_mode == CameraFollowMode::Instant) { + m_transform.position = m_target->position + m_offset; + } else { + float t = 1.f - std::exp(-m_smoothSpeed * dt); + m_transform.position = lerp(m_transform.position, desiredPos, t); + } + } + Vec2 Camera::worldToScreen(const Vec2& world, const Vec2& screenCenter) const { Vec2 relative = world - m_transform.position; return screenCenter + relative * m_zoom; From 39c05fc3dddfb139d514ea9c7be3e3f6d8cb1e41 Mon Sep 17 00:00:00 2001 From: Mihai Dumitrescu Date: Fri, 23 Jan 2026 00:41:03 +0200 Subject: [PATCH 3/3] Implement camera follow APIs Signed-off-by: Mihai Dumitrescu --- docs/USAGE.md | 9 +++++++++ engine/include/velos/app/engine.h | 3 +++ engine/src/app/engine.cpp | 9 +++++++++ examples/sandbox/src/main.cpp | 8 ++++++++ 4 files changed, 29 insertions(+) diff --git a/docs/USAGE.md b/docs/USAGE.md index 524784a..329c919 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -33,6 +33,15 @@ engine.setAction(velos::Key::W, "MoveForward", velos::ActionType::Hold, 0.05f, [ engine.camera()->transform().position.x += 50.f; engine.camera()->setZoom(engine.camera()->zoom() * 1.001f); ``` +- Attach to an entity and follow it: +```cpp +engine.attachCamera(*entity); +``` +- Configure camera mode and its parameters: +```cpp +engine.camera()->setFollowMode(velos::CameraFollowMode::Smooth); +engine.camera()->setLookAhead(80.f); +``` ## Rendering - Shapes added to entities are automatically rendered diff --git a/engine/include/velos/app/engine.h b/engine/include/velos/app/engine.h index aa2987a..d71ae8e 100644 --- a/engine/include/velos/app/engine.h +++ b/engine/include/velos/app/engine.h @@ -28,6 +28,9 @@ namespace velos { void setAction(Key key, const ActionId& action, ActionType type, float holdThreshold, std::function callback); void processActions(float dt); + void attachCamera(Entity& entity); + void detachCamera(); + static Engine* instance() { return s_instance; } Camera* camera() { return m_camera.get(); } EntityManager* entityManager() { return m_entityManager.get(); } diff --git a/engine/src/app/engine.cpp b/engine/src/app/engine.cpp index 2b682bf..ba90875 100644 --- a/engine/src/app/engine.cpp +++ b/engine/src/app/engine.cpp @@ -37,6 +37,7 @@ namespace velos { processActions(dt); m_entityManager->update(dt); + m_camera->update(dt); m_renderer->beginFrame(); m_entityManager->render(*m_renderer); @@ -92,4 +93,12 @@ namespace velos { return; } } + + void Engine::attachCamera(Entity& entity) { + m_camera->follow(&entity.transform()); + } + + void Engine::detachCamera() { + m_camera->unfollow(); + } } diff --git a/examples/sandbox/src/main.cpp b/examples/sandbox/src/main.cpp index ca56a69..d66b15f 100644 --- a/examples/sandbox/src/main.cpp +++ b/examples/sandbox/src/main.cpp @@ -23,11 +23,15 @@ int main() { 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 entity = std::make_shared(); entity->addShape(rect); entity->transform().position = {200, 200}; + auto stationary_entity = std::make_shared(); + stationary_entity->addShape(circle); + 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); }); @@ -60,6 +64,10 @@ int main() { engine.setAction(velos::Key::Space, "Charge", velos::ActionType::HoldCompleted, 1.f, [](float dt){ VELOS_INFO("Charged!"); }); engine.entityManager()->addEntity(entity); + engine.entityManager()->addEntity(stationary_entity); + + engine.attachCamera(*entity); + engine.camera()->setFollowMode(velos::CameraFollowMode::Smooth); engine.run(); }