From f458468644f101ed9a533c5ac00d5b8b059b1065 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Sun, 5 Oct 2025 12:11:06 +0200 Subject: [PATCH] Add map pan --- cpp/src/camera.cpp | 33 ++++++++- cpp/src/camera.hpp | 12 ++-- cpp/src/gameloop.cpp | 17 +++-- cpp/src/map.cpp | 3 +- cpp/src/map.hpp | 4 +- cpp/src/math.hpp | 138 ++++++++++++++++++++++++++++++++++-- cpp/src/pathfindingdemo.cpp | 7 +- cpp/src/user_input.cpp | 26 ++++++- cpp/src/user_input.hpp | 8 +-- cpp/src/window.cpp | 2 +- cpp/src/window.hpp | 2 +- 11 files changed, 219 insertions(+), 33 deletions(-) diff --git a/cpp/src/camera.cpp b/cpp/src/camera.cpp index 322fe1f..3134796 100644 --- a/cpp/src/camera.cpp +++ b/cpp/src/camera.cpp @@ -1,16 +1,43 @@ #include "camera.hpp" #include "math.hpp" - +#include "log.hpp" // for now only pass-through placeholder functions, // since we draw the whole map + +void Camera::Pan(const WorldPos& delta) +{ + LOG_DEBUG("m_Pan before: ", m_Pan, " delta ", delta); + m_Pan += delta; + LOG_DEBUG("m_Pan after: ", m_Pan); +} + +void Camera::Zoom(const WorldPos&) +{ + +} + WindowPos Camera::WorldToWindow(WorldPos world) const { - return WindowPos{world[0], world[1]}; + const auto& v = world + m_Pan; + return WindowPos{v[0], v[1]}; } WorldPos Camera::WindowToWorld(WindowPos window) const { - return WorldPos{window[0], window[1]}; + return WorldPos{window[0], window[1]} - m_Pan; +} + + +WindowSize Camera::WorldToWindowSize(WorldSize world) const +{ + const auto& v = world; + // no zoom yet, just pass-through + return WindowSize{v[0], v[1]}; +} + +WorldSize Camera::WindowToWorldSize(WindowSize window) const +{ + return WorldSize{window[0], window[1]}; } diff --git a/cpp/src/camera.hpp b/cpp/src/camera.hpp index 1df41ec..310451b 100644 --- a/cpp/src/camera.hpp +++ b/cpp/src/camera.hpp @@ -5,12 +5,16 @@ class Camera { public: - WindowPos WorldToWindow(WorldPos) const; - WorldPos WindowToWorld(WindowPos) const; + void Pan(const WorldPos& delta); + void Zoom(const WorldPos& delta); + + WindowPos WorldToWindow(WorldPos) const; + WorldPos WindowToWorld(WindowPos) const; + WindowSize WorldToWindowSize(WorldSize) const; + WorldSize WindowToWorldSize(WindowSize) const; private: // TODO this should be replaced with a matrix float m_Zoom; - WorldPos m_RectCorner; // upper left corner (0,0) of the drawn regios - + WorldPos m_Pan; }; diff --git a/cpp/src/gameloop.cpp b/cpp/src/gameloop.cpp index 0c0466f..671548c 100644 --- a/cpp/src/gameloop.cpp +++ b/cpp/src/gameloop.cpp @@ -27,14 +27,19 @@ void GameLoop::Run() { for (size_t row = 0; row < tiles.size(); row++) { for (size_t col = 0; col < tiles[row].size(); col++) { const auto& camera = m_Game->GetCamera(); + const auto& position = camera.WorldToWindow( + map.TileEdgeToWorld( + TilePos{static_cast(row), static_cast(col)} + ) + ); + const auto& size = camera.WorldToWindowSize( + map.GetTileSize() + ); // LOG_DEBUG("Drawing rect (", row, ", ", col, ")"); m_Window->DrawRect( - camera.WorldToWindow( - map.TileEdgeToWorld( - TilePos{static_cast(row), static_cast(col)} - ) - ), - camera.WorldToWindow(map.GetTileSize()), tiles[row][col]->R, tiles[row][col]->G, + position, + size, + tiles[row][col]->R, tiles[row][col]->G, tiles[row][col]->B, tiles[row][col]->A); } } diff --git a/cpp/src/map.cpp b/cpp/src/map.cpp index 4ef7e51..902880f 100644 --- a/cpp/src/map.cpp +++ b/cpp/src/map.cpp @@ -28,8 +28,7 @@ TilePos Map::WorldToTile(WorldPos p) const { return TilePos{static_cast(p.x() / TILE_SIZE), static_cast(p.y() / TILE_SIZE)}; } -// TODO this should probably use something like WorldSize or WorldVec to make the distinction clear -WorldPos Map::GetTileSize() const { return WorldPos{TILE_SIZE, TILE_SIZE}; } +WorldSize Map::GetTileSize() const { return WorldSize{TILE_SIZE, TILE_SIZE}; } const Tile *Map::GetTileAt(TilePos p) const { assert(IsTilePosValid(p)); diff --git a/cpp/src/map.hpp b/cpp/src/map.hpp index 2be7b64..6b400f6 100644 --- a/cpp/src/map.hpp +++ b/cpp/src/map.hpp @@ -25,8 +25,8 @@ public: WorldPos TileToWorld(TilePos p) const; WorldPos TileEdgeToWorld(TilePos p) const; TilePos WorldToTile(WorldPos p) const; - - WorldPos GetTileSize() const; + + WorldSize GetTileSize() const; const Tile *GetTileAt(TilePos p) const; const Tile *GetTileAt(WorldPos p) const; diff --git a/cpp/src/math.hpp b/cpp/src/math.hpp index a6a3400..3cb0dda 100644 --- a/cpp/src/math.hpp +++ b/cpp/src/math.hpp @@ -238,14 +238,22 @@ using uvec3 = vec; using uvec4 = vec; // tags for differentiating between domains -struct WorldTag {}; -struct WindowTag {}; -struct TileTag {}; +struct WorldPosTag {}; +struct WorldSizeTag {}; +struct WindowPosTag {}; +struct WindowSizeTag {}; +struct TilePosTag {}; +struct TileSizeTag {}; // types for each domain -using WorldPos = vec; -using WindowPos = vec; -using TilePos = vec; +using WorldPos = vec; +using WindowPos = vec; +using TilePos = vec; +// Size +using WorldSize = vec; +using WindowSize = vec; +using TileSize = vec; + // // Utils @@ -259,3 +267,121 @@ struct TilePosHash { } }; +// old stuff - TODO delete + +// constexpr double EQUALITY_LIMIT = 1e-6; +// template struct Vec2D { +// public: +// Vec2D() = default; +// ~Vec2D() = default; +// +// template +// Vec2D(Vec2D other) { +// this->x = static_cast(other.x); +// this->y = static_cast(other.y); +// } +// +// Vec2D& operator+=(const Vec2D &other) { +// x += other.x; +// y += other.y; +// return *this; +// } +// +// template +// requires std::is_arithmetic_v +// Vec2D& operator/=(U k) { +// this->x /= static_cast(k); +// this->y /= static_cast(k); +// return *this; +// } +// +// friend Vec2D operator+(const Vec2D &a, const Vec2D &b) { +// return Vec2D{a.x + b.x, a.y + b.y}; +// } +// +// friend Vec2D operator-(const Vec2D &a, const Vec2D &b) { +// return Vec2D{a.x - b.x, a.y - b.y}; +// } +// +// template +// requires std::is_arithmetic_v +// friend Vec2D operator*(U k, const Vec2D &v) +// { +// return Vec2D{k * v.x, k * v.y}; +// } +// +// template +// requires std::is_arithmetic_v +// friend Vec2D operator/(const Vec2D &v, U k) +// { +// return Vec2D{v.x / k, v.y / k}; +// } +// +// friend bool operator==(const Vec2D &a, const Vec2D &b) { +// if constexpr (std::is_integral_v) { +// return a.x == b.x && a.y == b.y; +// } else if constexpr (std::is_floating_point_v) { +// return a.distance(b) < EQUALITY_LIMIT; +// } else { +// static_assert("Unhandled comparison"); +// } +// } +// +// Vec2D operator*(float b) const { return Vec2D{b * x, b * y}; } +// +// T distance_squared(const Vec2D &other) const { +// T dx = x - other.x; +// T dy = y - other.y; +// return dx * dx + dy * dy; +// } +// +// T distance(const Vec2D &other) const +// requires std::floating_point +// { +// return sqrt(distance_squared(other)); +// } +// +// void normalize() +// requires std::floating_point +// { +// auto length = sqrt(x * x + y * y); +// if (length < EQUALITY_LIMIT) { +// x = y = 0; +// } else { +// x /= length; +// y /= length; +// } +// } +// +// Vec2D normalized() +// requires std::floating_point +// { +// Vec2D v(*this); +// v.normalize(); +// return v; +// } +// +// Vec2D orthogonal() const +// { +// Vec2D v(*this); +// +// std::swap(v.x, v.y); +// v.x = -v.x; +// return v; +// } +// +// template Vec2D(std::initializer_list list) { +// assert(list.size() == 2); +// auto first_element = *list.begin(); +// auto second_element = *(list.begin() + 1); +// x = static_cast(first_element); +// y = static_cast(second_element); +// } +// +// T x, y; +// +// friend std::ostream &operator<<(std::ostream &os, const Vec2D &obj) { +// os << "( " << obj.x << ", " << obj.y << ")"; +// return os; +// } +// }; diff --git a/cpp/src/pathfindingdemo.cpp b/cpp/src/pathfindingdemo.cpp index 8fe7d8e..5e985dc 100644 --- a/cpp/src/pathfindingdemo.cpp +++ b/cpp/src/pathfindingdemo.cpp @@ -110,7 +110,7 @@ void PathFindingDemo::HandleActions(const std::vector &actions) { LOG_INFO("Exit requested"); m_ExitRequested = true; } else if (action.type == UserAction::Type::SET_MOVE_TARGET) { - WorldPos wp = action.Argument.position; + WorldPos wp = m_Camera.WindowToWorld(action.Argument.position); LOG_INFO("Calculating path to target: ", wp); m_Path = m_PathFinder->CalculatePath(m_Player->GetPosition(), wp); LOG_INFO("Done, path node count: ", m_Path.size()); @@ -119,6 +119,11 @@ void PathFindingDemo::HandleActions(const std::vector &actions) { PathFinderType type = static_cast(action.Argument.number); m_PathFinder = pathfinder::utils::create(type, (const Map*)&m_Map); LOG_INFO("Switched to path finding method: ", m_PathFinder->GetName()); + } else if (action.type == UserAction::Type::CAMERA_PAN) { + const auto& window_pan = action.Argument.position; + WorldPos world_pan{window_pan.x(), window_pan.y()}; + m_Camera.Pan(world_pan); + LOG_INFO("Camera pan delta: ", world_pan); } }; } diff --git a/cpp/src/user_input.cpp b/cpp/src/user_input.cpp index 2c252f3..fc98968 100644 --- a/cpp/src/user_input.cpp +++ b/cpp/src/user_input.cpp @@ -22,8 +22,12 @@ std::expected UserInput::Init() { return {}; } const std::vector &UserInput::GetActions() { m_Actions.clear(); SDL_Event event; + + static WindowPos mouse_pan_start; + static bool mouse_pan = false; while (SDL_PollEvent(&event)) { + // TODO refactor mouse / kbd handling into separate functions if (event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP) { bool key_down = event.type == SDL_EVENT_KEY_DOWN ? true : false; SDL_KeyboardEvent kbd_event = event.key; @@ -55,9 +59,25 @@ const std::vector &UserInput::GetActions() { } } else if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { SDL_MouseButtonEvent mouse_event = event.button; - LOG_DEBUG("Mouse down: ", mouse_event.x, ", ", mouse_event.y); - m_Actions.emplace_back(UserAction::Type::SET_MOVE_TARGET, - WorldPos{mouse_event.x, mouse_event.y}); + if (mouse_event.button == 1) { + LOG_DEBUG("Mouse down: ", mouse_event.x, ", ", mouse_event.y); + m_Actions.emplace_back(UserAction::Type::SET_MOVE_TARGET, + WindowPos{mouse_event.x, mouse_event.y}); + } else if (mouse_event.button == 2) { + mouse_pan = true; + } + } else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) { + SDL_MouseButtonEvent mouse_event = event.button; + if (mouse_event.button == 2) { + mouse_pan = false; + } + } else if (event.type == SDL_EVENT_MOUSE_MOTION) { + SDL_MouseMotionEvent motion_event = event.motion; + if (mouse_pan) { + m_Actions.emplace_back(UserAction::Type::CAMERA_PAN, + WindowPos{motion_event.xrel, motion_event.yrel}); + + } } else { // TODO uncomment, for now too much noise // LOG_WARNING("Action not processed"); diff --git a/cpp/src/user_input.hpp b/cpp/src/user_input.hpp index 5c328a5..62631e5 100644 --- a/cpp/src/user_input.hpp +++ b/cpp/src/user_input.hpp @@ -9,25 +9,25 @@ class UserAction { public: - enum class Type { NONE, EXIT, SET_MOVE_TARGET, SELECT_PATHFINDER }; + enum class Type { NONE, EXIT, SET_MOVE_TARGET, SELECT_PATHFINDER, CAMERA_PAN, CAMERA_ZOOM }; UserAction() : type(Type::NONE), Argument{.number = 0} {} UserAction(Type t) : type(t), Argument{.number = 0} {} UserAction(Type t, char key) : type(t), Argument{.key = key} {} - UserAction(Type t, WorldPos v) : type(t), Argument{.position = v} {} + UserAction(Type t, WindowPos v) : type(t), Argument{.position = v} {} UserAction(Type t, int arg) : type(t), Argument{.number = arg} {} ~UserAction() = default; Type type; union { - WorldPos position; + WindowPos position; char key; int number; } Argument; // TODO use std::variant - //std::variant Argument; + //std::variant Argument; }; class UserInput { diff --git a/cpp/src/window.cpp b/cpp/src/window.cpp index 27f013e..b49d15b 100644 --- a/cpp/src/window.cpp +++ b/cpp/src/window.cpp @@ -84,7 +84,7 @@ void Window::DrawSprite(const WindowPos &position, Sprite &s) { SDL_RenderTexture(m_Renderer.get(), s.GetTexture(), nullptr, &rect); } -void Window::DrawRect(const WindowPos &position, const WindowPos size, uint8_t R, +void Window::DrawRect(const WindowPos &position, const WindowSize size, uint8_t R, uint8_t G, uint8_t B, uint8_t A) { SDL_FRect rect = {position.x(), position.y(), size.x(), size.y()}; SDL_SetRenderDrawColor(m_Renderer.get(), R, G, B, A); diff --git a/cpp/src/window.hpp b/cpp/src/window.hpp index cd39983..5a16152 100644 --- a/cpp/src/window.hpp +++ b/cpp/src/window.hpp @@ -23,7 +23,7 @@ public: std::expected Init(); void DrawSprite(const WindowPos &position, Sprite &s); - void DrawRect(const WindowPos &position, const WindowPos size, uint8_t R, + void DrawRect(const WindowPos &position, const WindowSize size, uint8_t R, uint8_t G, uint8_t B, uint8_t A); void ClearWindow(); void Flush();