diff --git a/cpp/src/gameloop.cpp b/cpp/src/gameloop.cpp index 5541b0d..e73d82c 100644 --- a/cpp/src/gameloop.cpp +++ b/cpp/src/gameloop.cpp @@ -28,7 +28,7 @@ void GameLoop::Run() { for (size_t col = 0; col < tiles[row].size(); col++) { // LOG_DEBUG("Drawing rect (", row, ", ", col, ")"); m_Window->DrawRect( - map.TileToWorld(TilePos{row, col}), + map.TileEdgeToWorld(TilePos{row, col}), map.GetTileSize(), tiles[row][col]->R, tiles[row][col]->G, tiles[row][col]->B, tiles[row][col]->A); } diff --git a/cpp/src/main.cpp b/cpp/src/main.cpp index 6da7157..fde36f4 100644 --- a/cpp/src/main.cpp +++ b/cpp/src/main.cpp @@ -5,7 +5,7 @@ #include "window.hpp" #include -int main(int argc, char **argv) { +int main() { constexpr int error = -1; /* diff --git a/cpp/src/map.cpp b/cpp/src/map.cpp index cd439df..12d3b4d 100644 --- a/cpp/src/map.cpp +++ b/cpp/src/map.cpp @@ -23,7 +23,11 @@ Map::Map(int rows, int cols) : m_Cols(cols), m_Rows(rows) { } WorldPos Map::TileToWorld(TilePos p) const { - return WorldPos{p.x * TILE_SIZE, p.y * TILE_SIZE}; + return WorldPos{(p.x + 0.5) * TILE_SIZE, (p.y + 0.5) * TILE_SIZE}; +} + +WorldPos Map::TileEdgeToWorld(TilePos p) const { + return WorldPos{p.x * TILE_SIZE, p.y * TILE_SIZE}; } TilePos Map::WorldToTile(WorldPos p) const { @@ -50,3 +54,33 @@ bool Map::IsTilePosValid(TilePos p) const { return row < m_Tiles.size() && col < m_Tiles[0].size(); } + + +std::vector Map::GetNeighbors(TilePos center) const +{ + std::vector neighbours; + neighbours.reserve(4); + + std::array candidates = { + center + TilePos{1,0}, + center + TilePos{-1,0}, + center + TilePos{0, 1}, + center + TilePos{0, -1}, + }; + + for (const auto& c : candidates) { + if (IsTilePosValid(c)) + neighbours.push_back(c); + } + return neighbours; +} +// std::vector neighbours; +// neighbours.reserve(8); +// for (int dx = -1; dx <= 1; ++dx) { +// for (int dy = -1; dy <= 1; ++dy) { +// if (dx == 0 && dy == 0) continue; +// TilePos p{center.x + dx, center.y + dy}; +// if (IsTilePosValid(p)) neighbours.push_back(std::move(p)); +// } +// } +// return neighbours; diff --git a/cpp/src/map.hpp b/cpp/src/map.hpp index c284994..7650157 100644 --- a/cpp/src/map.hpp +++ b/cpp/src/map.hpp @@ -21,7 +21,9 @@ public: const TileGrid &GetMapTiles() const { return m_Tiles; } + // coordinate conversion functions WorldPos TileToWorld(TilePos p) const; + WorldPos TileEdgeToWorld(TilePos p) const; TilePos WorldToTile(WorldPos p) const; WorldPos GetTileSize() const; @@ -30,6 +32,9 @@ public: bool IsTilePosValid(TilePos p) const; + + std::vector GetNeighbors(TilePos center) const; + template double GetTileVelocityCoeff(T p) const { return 1.0 / GetTileAt(p)->cost; } diff --git a/cpp/src/math.hpp b/cpp/src/math.hpp index 6a0c6cb..002a73a 100644 --- a/cpp/src/math.hpp +++ b/cpp/src/math.hpp @@ -7,6 +7,7 @@ #include #include +constexpr double EQUALITY_LIMIT = 1e-6; template struct Vec2D { public: @@ -31,6 +32,16 @@ public: return Vec2D{k * v.x, k * v.y}; } + 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 { @@ -49,7 +60,7 @@ public: requires std::floating_point { auto length = sqrt(x * x + y * y); - if (length < 1e-6) { + if (length < EQUALITY_LIMIT) { x = y = 0; } else { x /= length; @@ -92,3 +103,12 @@ public: using TilePos = Vec2D; using WorldPos = Vec2D; + +struct TilePosHash { + std::size_t operator()(const TilePos& p) const noexcept { + std::size_t h1 = std::hash{}(p.x); + std::size_t h2 = std::hash{}(p.y); + return h1 ^ (h2 + 0x9e3779b9 + (h1<<6) + (h1>>2)); + } +}; + diff --git a/cpp/src/pathfinder.cpp b/cpp/src/pathfinder.cpp index 365284e..0e44dad 100644 --- a/cpp/src/pathfinder.cpp +++ b/cpp/src/pathfinder.cpp @@ -1,34 +1,84 @@ #include #include +#include #include "pathfinder.hpp" #include "log.hpp" namespace pathfinder { -void PathFinderBase::SetMap(const Map* map) -{ - m_Map = map; -} -Path LinearPathFinder::CalculatePath(WorldPos target) +PathFinderBase::PathFinderBase(const Map* map) : m_Map(map) {} + + +Path LinearPathFinder::CalculatePath(WorldPos start, WorldPos end) { - auto path = Path{target}; + auto path = Path{end}; return path; } -Path BFS::CalculatePath(WorldPos target) -{ - auto path = Path{target}; - return path; + +Path BFS::CalculatePath(WorldPos start_world, WorldPos end_world) { + if (m_Map == nullptr) return {}; + + const TilePos start = m_Map->WorldToTile(start_world); + const TilePos end = m_Map->WorldToTile(end_world); + + if (!m_Map->IsTilePosValid(start) || !m_Map->IsTilePosValid(end)) + return {}; + if (start == end) { + return {}; + } + // clear previous run + m_CameFrom.clear(); + m_Distance.clear(); + + std::queue frontier; + frontier.push(start); + m_CameFrom[start] = start; + m_Distance[start] = 0.0f; + + // ---------------- build flow-field ---------------- + bool early_exit = false; + while (!frontier.empty() && !early_exit) { + TilePos current = frontier.front(); + frontier.pop(); + + for (TilePos next : m_Map->GetNeighbors(current)) { + if (m_CameFrom.find(next) == m_CameFrom.end()) { // not visited + frontier.push(next); + m_Distance[next] = m_Distance[current] + 1.0f; + m_CameFrom[next] = current; + + if (next == end) { // early exit + early_exit = true; + break; + } + } + } + } + + // --------------- reconstruct path ----------------- + if (m_CameFrom.find(end) == m_CameFrom.end()) + return {}; // end not reached + + Path path; + TilePos cur = end; + path.push_back(m_Map->TileToWorld(cur)); + while (cur != start) { + cur = m_CameFrom[cur]; + path.push_back(m_Map->TileToWorld(cur)); + } + std::reverse(path.begin(), path.end()); + return path; } -std::unique_ptr create(PathFinderType type) { +std::unique_ptr create(PathFinderType type, const Map* map) { switch (type) { case PathFinderType::LINEAR: - return std::move(std::make_unique()); + return std::move(std::make_unique(map)); case PathFinderType::BFS: - return std::move(std::make_unique()); + return std::move(std::make_unique(map)); case PathFinderType::COUNT: LOG_WARNING("Incorrect pathfinder type"); return nullptr; diff --git a/cpp/src/pathfinder.hpp b/cpp/src/pathfinder.hpp index ee19b6e..72eefe3 100644 --- a/cpp/src/pathfinder.hpp +++ b/cpp/src/pathfinder.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "math.hpp" #include "map.hpp" @@ -18,7 +19,7 @@ enum class PathFinderType { class PathFinderBase { public: - PathFinderBase() = default; + PathFinderBase(const Map* m); ~PathFinderBase() = default; PathFinderBase(const PathFinderBase&) = delete; @@ -26,11 +27,10 @@ public: PathFinderBase& operator=(const PathFinderBase&) = delete; PathFinderBase& operator=(PathFinderBase&&) = delete; - void SetMap(const Map* map); virtual const std::string_view& GetName() const = 0; - virtual Path CalculatePath(WorldPos target) = 0; + virtual Path CalculatePath(WorldPos start, WorldPos end) = 0; -private: +protected: const Map* m_Map; }; @@ -38,24 +38,29 @@ private: class LinearPathFinder : public PathFinderBase { public: - Path CalculatePath(WorldPos target) override; + LinearPathFinder(const Map* m): PathFinderBase(m) {} + Path CalculatePath(WorldPos start, WorldPos end) override; const std::string_view& GetName() const override { return m_Name; } private: const std::string_view m_Name = "Linear Path"; }; + class BFS: public PathFinderBase { public: - Path CalculatePath(WorldPos target) override; + BFS(const Map* m): PathFinderBase(m) {} + Path CalculatePath(WorldPos start, WorldPos end) override; const std::string_view& GetName() const override { return m_Name; } private: const std::string_view m_Name = "Breadth First Search"; + std::unordered_map m_Distance; + std::unordered_map m_CameFrom; }; -std::unique_ptr create(PathFinderType type); +std::unique_ptr create(PathFinderType type, const Map* map); } // pathfinder namespace diff --git a/cpp/src/pathfindingdemo.cpp b/cpp/src/pathfindingdemo.cpp index 4d80d1e..f58f9dd 100644 --- a/cpp/src/pathfindingdemo.cpp +++ b/cpp/src/pathfindingdemo.cpp @@ -16,8 +16,7 @@ PathFindingDemo::PathFindingDemo(int width, int height) : { LOG_DEBUG("."); // set default pathfinder method - m_PathFinder = pathfinder::create(pathfinder::PathFinderType::LINEAR); - m_PathFinder->SetMap(&m_Map); + m_PathFinder = pathfinder::create(pathfinder::PathFinderType::LINEAR, (const Map*)&m_Map); } PathFindingDemo::~PathFindingDemo() { LOG_DEBUG("."); } @@ -84,13 +83,12 @@ void PathFindingDemo::HandleActions(const std::vector &actions) { } else if (action.type == UserAction::Type::SET_MOVE_TARGET) { WorldPos wp = action.Argument.position; LOG_INFO("Calculating path to target: ", wp); - m_Path = m_PathFinder->CalculatePath(wp); + m_Path = m_PathFinder->CalculatePath(m_Player->GetPosition(), wp); LOG_INFO("Done, path node count: ", m_Path.size()); } else if (action.type == UserAction::Type::SELECT_PATHFINDER) { using namespace pathfinder; PathFinderType type = static_cast(action.Argument.number); - m_PathFinder = create(type); - m_PathFinder->SetMap(&m_Map); + m_PathFinder = pathfinder::create(type, (const Map*)&m_Map); LOG_INFO("Switched to path finding method: ", m_PathFinder->GetName()); } };