From 5d35a3e92d5f58fd5d77d4f14ce855811b3db9b8 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 16 Oct 2025 18:01:29 +0200 Subject: [PATCH 01/23] vec class: structured binding support --- cpp/src/math.hpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/cpp/src/math.hpp b/cpp/src/math.hpp index be6e290..706d32d 100644 --- a/cpp/src/math.hpp +++ b/cpp/src/math.hpp @@ -265,6 +265,21 @@ public: return vec(m_Array); } + // Structured binding support for N == 2 + template + const T& get() const + requires(N == 2 && I < 2) + { + return m_Array[I]; + } + + template + T& get() + requires(N == 2 && I < 2) + { + return m_Array[I]; + } + private: std::array m_Array; }; @@ -409,3 +424,25 @@ public: private: std::array m_Array; }; + +// Structured binding support for vec +namespace std { + template + struct tuple_size> : integral_constant {}; + + template + struct tuple_element> { + using type = T; + }; +} + +// ADL-based get for structured bindings +template +const T& get(const vec& v) { + return v.template get(); +} + +template +T& get(vec& v) { + return v.template get(); +} From 5209e054e59cf1bfb779bedcbf835e59290bb01c Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 16 Oct 2025 18:02:31 +0200 Subject: [PATCH 02/23] Positional container WIP --- cpp/src/positional_container.hpp | 172 +++++++++++++++++++++++++++++ cpp/test/collision_performance.cpp | 24 ++++ 2 files changed, 196 insertions(+) create mode 100644 cpp/src/positional_container.hpp diff --git a/cpp/src/positional_container.hpp b/cpp/src/positional_container.hpp new file mode 100644 index 0000000..0b4e94d --- /dev/null +++ b/cpp/src/positional_container.hpp @@ -0,0 +1,172 @@ +#pragma once + +#include +#include +#include + +#include "math.hpp" +#include "log.hpp" + +template +concept HasPosition = requires(T t, WorldPos pos) { + { t.GetPosition() } -> std::convertible_to; + t.SetPosition(pos); +}; + +template +concept HasCollisions = requires(T t) { + t.Dummy(); // TODO +}; + +template +requires HasPosition +class IPositionalContainer +{ +public: + virtual ~IPositionalContainer() = default; + virtual bool Add(const T& t) = 0; + virtual std::vector> Get(const WorldPos& p, float radius) = 0; + virtual void UpdateAll() = 0; + virtual void Update(std::shared_ptr item) = 0; +}; + + +template +class IColliderContainer : public IPositionalContainer +{ +public: + virtual std::vector> GetCollisions() = 0; +}; + +template +class SimpleContainer : IPositionalContainer +{ +public: + bool Add(const T& t) override + { + m_Items.push_back(std::make_shared(t)); + } + + std::vector> Get(const WorldPos& center, float radius) override + { + std::vector> matched_items; + for (const auto& item : m_Items) + { + const auto& A = item->GetPosition(); + if (center.DistanceTo(item->GetPosition()) < radius) + { + matched_items.push_back(item); + } + } + return matched_items; + } + + // no update needed here, as we have no smart lookup scheme + void UpdateAll() override {} + void Update(std::shared_ptr item) override {} + +private: + std::vector> m_Items; +}; + + +template +class PositionalContainer : IPositionalContainer +{ +public: + + PositionalContainer(const WorldSize& size, size_t chunks) : + m_GridSize{size}, + m_GridStep{size / chunks}, + m_ChunksPerAxis{chunks} + { + LOG_INFO("Size: ", m_GridSize, " step: ", m_GridStep); + m_Grid.reserve(chunks); + for (size_t i = 0; i < chunks; i++) + { + //m_Grid.push_back(std::vector{chunks}); + m_Grid.emplace_back(chunks); + for (size_t j = 0; j < chunks; j++) + { + // let's try reserving place for 16 items + m_Grid[i][j].reserve(16); + } + } + } + + // calling Add on object that is already in the container is UB + bool Add(const T& item) override + { + const auto& world_pos = item.GetPosition(); + if (!CheckBounds(world_pos)) + { + return false; + } + auto ptr = std::make_shared(item); + m_Items.push_back(ptr); + auto coords = GetCoords(world_pos); + // here we assume that the object is not already in the grid! + m_Grid[coords.x()][coords.y()].push_back(ptr); + + // TODO should we call Update instead? + //Update(ptr); + } + + std::vector> Get(const WorldPos& center, float radius) override + { + return vector_wptr{}; + } + + void Get(std::vector>& output_vec, const WorldPos& center, float radius) + { + + } + + void UpdateAll() override + { + for (auto ptr : m_Items) + { + // TODO is this efficient? Maybe use const ref? + Update(ptr); + } + } + void Update(std::shared_ptr item) override + { + auto coords = GetCoords(item->GetPosition()); + // remove the old weak ptr from the map + + // add new weak ptr to the map + m_Grid[coords.x()][coords.y()].push_back(item); + } + +private: + + using coord_type = vec; + using vector_wptr = std::vector>; + using grid_type = std::vector>; + + coord_type GetCoords(const WorldPos &wp) + { + auto coord_float = wp / m_ChunksPerAxis; + return coord_type{ + static_cast(coord_float.x()), + static_cast(coord_float.y()) + }; + } + + bool CheckBounds(const WorldPos& pos) + { + auto [x,y] = pos; + bool x_in_bounds = 0.0f < x && x < m_GridSize.x(); + bool y_in_bounds = 0.0f < y && y < m_GridSize.y(); + return x_in_bounds && y_in_bounds; + } + + WorldSize m_GridSize; + WorldSize m_GridStep; + size_t m_ChunksPerAxis; + std::vector> m_Items; + + + grid_type m_Grid; +}; diff --git a/cpp/test/collision_performance.cpp b/cpp/test/collision_performance.cpp index 3860f9f..3db6c73 100644 --- a/cpp/test/collision_performance.cpp +++ b/cpp/test/collision_performance.cpp @@ -3,6 +3,9 @@ #include #include + +#include "positional_container.hpp" + // TODO: Add necessary includes for collision testing // #include "collision_shapes.hpp" // #include "entities.hpp" @@ -74,8 +77,29 @@ void benchmark_function(const std::string& name, int iterations, Func func) { << " ops/sec" << std::endl; } +/** + * @brief Simple dummy class that conforms to HasPosition concept + * Used for testing PositionalContainer without heavy dependencies + */ +class Dummy { +public: + Dummy() : m_Position(0.0f, 0.0f) {} + Dummy(float x, float y) : m_Position(x, y) {} + Dummy(WorldPos pos) : m_Position(pos) {} + + WorldPos GetPosition() const { return m_Position; } + void SetPosition(WorldPos pos) { m_Position = pos; } + +private: + WorldPos m_Position; +}; + // Example test function 1 void test_function_1() { + + PositionalContainer pos_cont{WorldSize{10.0f, 10.0f}, 10}; + SimpleContainer simp_cont; + // TODO: Implement actual collision test volatile int sum = 0; for (int i = 0; i < 1000; ++i) { From 836731b971b20bb6d5d0bb4b49c9baa37aebd906 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 16 Oct 2025 18:02:47 +0200 Subject: [PATCH 03/23] Tests for positional containers --- cpp/test/test.cpp | 151 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/cpp/test/test.cpp b/cpp/test/test.cpp index b94b846..9a5dad8 100644 --- a/cpp/test/test.cpp +++ b/cpp/test/test.cpp @@ -7,6 +7,7 @@ #include "log.hpp" #include "math.hpp" +#include "positional_container.hpp" TEST(vec, DefaultConstruction) { @@ -752,6 +753,156 @@ TEST(Matrix, ChainedOperations) { ASSERT_FLOAT_EQ(m3[0][0], 2.0f); } +// Helper class for SimpleContainer tests +class TestEntity { +public: + TestEntity() : m_Position(0.0f, 0.0f) {} + TestEntity(float x, float y) : m_Position(x, y) {} + TestEntity(WorldPos pos) : m_Position(pos) {} + + WorldPos GetPosition() const { return m_Position; } + void SetPosition(WorldPos pos) { m_Position = pos; } + +private: + WorldPos m_Position; +}; + +TEST(SimpleContainer, DefaultConstruction) { + // Test that SimpleContainer can be default constructed + SimpleContainer container; + SUCCEED(); +} + +TEST(SimpleContainer, AddSingleItem) { + // Test adding a single item + SimpleContainer container; + TestEntity entity(5.0f, 10.0f); + + container.Add(entity); + + // Verify by getting items near the position + auto results = container.Get(WorldPos(5.0f, 10.0f), 1.0f); + ASSERT_EQ(results.size(), 1); +} + +TEST(SimpleContainer, AddMultipleItems) { + // Test adding multiple items + SimpleContainer container; + + container.Add(TestEntity(0.0f, 0.0f)); + container.Add(TestEntity(10.0f, 10.0f)); + container.Add(TestEntity(20.0f, 20.0f)); + + // Verify by getting items near a position + auto results = container.Get(WorldPos(10.0f, 10.0f), 5.0f); + ASSERT_GE(results.size(), 1); +} + +TEST(SimpleContainer, GetItemsInRadius) { + // Test getting items within a radius + SimpleContainer container; + + // Add items in a known pattern + container.Add(TestEntity(0.0f, 0.0f)); // At origin + container.Add(TestEntity(1.0f, 0.0f)); // 1 unit away + container.Add(TestEntity(0.0f, 1.0f)); // 1 unit away + container.Add(TestEntity(10.0f, 10.0f)); // Far away + + // Get items within 2.0 units of origin + auto results = container.Get(WorldPos(0.0f, 0.0f), 2.0f); + + // Should find the 3 nearby items (if Get is working correctly) + // Note: This depends on the actual implementation of Get + ASSERT_GE(results.size(), 0); +} + +TEST(SimpleContainer, GetItemsEmptyContainer) { + // Test getting items from empty container + SimpleContainer container; + + auto results = container.Get(WorldPos(0.0f, 0.0f), 10.0f); + ASSERT_EQ(results.size(), 0); +} + +TEST(SimpleContainer, WeakPtrValidAfterGet) { + // Test that weak_ptr returned from Get can be locked + SimpleContainer container; + container.Add(TestEntity(5.0f, 5.0f)); + + auto results = container.Get(WorldPos(5.0f, 5.0f), 10.0f); + + if (!results.empty()) { + auto locked = results[0].lock(); + ASSERT_NE(locked, nullptr); + + // Verify the position + WorldPos pos = locked->GetPosition(); + ASSERT_FLOAT_EQ(pos.x(), 5.0f); + ASSERT_FLOAT_EQ(pos.y(), 5.0f); + } +} + +TEST(SimpleContainer, UpdateAllNoThrow) { + // Test that UpdateAll doesn't throw (it's a no-op for SimpleContainer) + SimpleContainer container; + container.Add(TestEntity(1.0f, 2.0f)); + container.Add(TestEntity(3.0f, 4.0f)); + + ASSERT_NO_THROW(container.UpdateAll()); +} + +TEST(SimpleContainer, UpdateNoThrow) { + // Test that Update doesn't throw (it's a no-op for SimpleContainer) + SimpleContainer container; + auto item = std::make_shared(1.0f, 2.0f); + + ASSERT_NO_THROW(container.Update(item)); +} + +TEST(SimpleContainer, AddItemsWithSamePosition) { + // Test adding multiple items at the same position + SimpleContainer container; + + container.Add(TestEntity(5.0f, 5.0f)); + container.Add(TestEntity(5.0f, 5.0f)); + container.Add(TestEntity(5.0f, 5.0f)); + + auto results = container.Get(WorldPos(5.0f, 5.0f), 1.0f); + // All three items should be found (if Get is working) + ASSERT_GE(results.size(), 0); +} + +TEST(SimpleContainer, ConformsToHasPosition) { + // Test that TestEntity conforms to HasPosition concept + // This is a compile-time test - if it compiles, it passes + static_assert(HasPosition, + "TestEntity should satisfy HasPosition concept"); + SUCCEED(); +} + +TEST(PositionalContainer, DefaultConstruction) { + // Test that PositionalContainer can be constructed with size and chunks + WorldSize size(100.0f, 100.0f); + size_t chunks = 10; + + PositionalContainer container(size, chunks); + SUCCEED(); +} + +TEST(PositionalContainer, AddSingleItem) { + // Test adding a single item + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + TestEntity entity(5.0f, 10.0f); + container.Add(entity); + + // Verify by getting items near the position + auto results = container.Get(WorldPos(5.0f, 10.0f), 1.0f); + // Note: Get implementation may not be complete yet + ASSERT_GE(results.size(), 1); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); From a4e44e6cb8e0b7482065283becde298397372610 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 16 Oct 2025 20:37:09 +0200 Subject: [PATCH 04/23] CMake: use ggdb3 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7e9d45..10c5760 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,7 +185,7 @@ if(MSVC) set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/O2 /Zi /DNDEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "/O1 /DNDEBUG") else() - # GCC/Clang flags - set(CMAKE_CXX_FLAGS_DEBUG "-g -O0") + # GCC/Clang flags with extended debugging symbols + set(CMAKE_CXX_FLAGS_DEBUG "-ggdb3 -O0") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") endif() From f9790052081546000c1b3987b96de24491f27168 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 16 Oct 2025 20:37:45 +0200 Subject: [PATCH 05/23] vec: add operator+ and - for scalars --- cpp/src/math.hpp | 16 ++++++ cpp/test/test.cpp | 122 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 111 insertions(+), 27 deletions(-) diff --git a/cpp/src/math.hpp b/cpp/src/math.hpp index 706d32d..a44638e 100644 --- a/cpp/src/math.hpp +++ b/cpp/src/math.hpp @@ -101,6 +101,14 @@ public: return c; } + friend vec operator+(const vec& a, T b) + { + vec c; + std::ranges::transform(a.m_Array, std::views::repeat(b), c.m_Array.begin(), + std::plus{}); + return c; + } + friend vec operator-(const vec &a, const vec &b) { vec c; std::ranges::transform(a.m_Array, b.m_Array, c.m_Array.begin(), @@ -108,6 +116,14 @@ public: return c; } + friend vec operator-(const vec& a, T b) + { + vec c; + std::ranges::transform(a.m_Array, std::views::repeat(b), c.m_Array.begin(), + std::minus{}); + return c; + } + friend vec operator*(const vec &a, const T &scalar) { vec c; std::ranges::transform(a.m_Array, std::views::repeat(scalar), diff --git a/cpp/test/test.cpp b/cpp/test/test.cpp index 9a5dad8..f4e2d04 100644 --- a/cpp/test/test.cpp +++ b/cpp/test/test.cpp @@ -173,6 +173,86 @@ TEST(vec, Sub) ASSERT_FLOAT_EQ(negative_result[2], -3.0f); } +TEST(vec, ScalarAddition) +{ + // Test operator+ with float vector and scalar + vec3 v1{1.0f, 2.0f, 3.0f}; + vec3 result = v1 + 5.0f; + + ASSERT_FLOAT_EQ(result[0], 6.0f); + ASSERT_FLOAT_EQ(result[1], 7.0f); + ASSERT_FLOAT_EQ(result[2], 8.0f); + + // Test operator+ with integer vector and scalar + ivec3 iv1{10, 20, 30}; + ivec3 iresult = iv1 + 5; + + ASSERT_EQ(iresult[0], 15); + ASSERT_EQ(iresult[1], 25); + ASSERT_EQ(iresult[2], 35); + + // Test that original vector is unchanged + ASSERT_FLOAT_EQ(v1[0], 1.0f); + ASSERT_FLOAT_EQ(v1[1], 2.0f); + ASSERT_FLOAT_EQ(v1[2], 3.0f); + + // Test addition with negative scalar + vec3 v2{5.0f, 10.0f, 15.0f}; + vec3 negative_result = v2 + (-3.0f); + + ASSERT_FLOAT_EQ(negative_result[0], 2.0f); + ASSERT_FLOAT_EQ(negative_result[1], 7.0f); + ASSERT_FLOAT_EQ(negative_result[2], 12.0f); + + // Test addition with zero + vec3 v3{1.0f, 2.0f, 3.0f}; + vec3 zero_result = v3 + 0.0f; + + ASSERT_FLOAT_EQ(zero_result[0], 1.0f); + ASSERT_FLOAT_EQ(zero_result[1], 2.0f); + ASSERT_FLOAT_EQ(zero_result[2], 3.0f); +} + +TEST(vec, ScalarSubtraction) +{ + // Test operator- with float vector and scalar + vec3 v1{10.0f, 15.0f, 20.0f}; + vec3 result = v1 - 5.0f; + + ASSERT_FLOAT_EQ(result[0], 5.0f); + ASSERT_FLOAT_EQ(result[1], 10.0f); + ASSERT_FLOAT_EQ(result[2], 15.0f); + + // Test operator- with integer vector and scalar + ivec3 iv1{50, 40, 30}; + ivec3 iresult = iv1 - 10; + + ASSERT_EQ(iresult[0], 40); + ASSERT_EQ(iresult[1], 30); + ASSERT_EQ(iresult[2], 20); + + // Test that original vector is unchanged + ASSERT_FLOAT_EQ(v1[0], 10.0f); + ASSERT_FLOAT_EQ(v1[1], 15.0f); + ASSERT_FLOAT_EQ(v1[2], 20.0f); + + // Test subtraction with negative scalar (equivalent to addition) + vec3 v2{5.0f, 10.0f, 15.0f}; + vec3 negative_result = v2 - (-3.0f); + + ASSERT_FLOAT_EQ(negative_result[0], 8.0f); + ASSERT_FLOAT_EQ(negative_result[1], 13.0f); + ASSERT_FLOAT_EQ(negative_result[2], 18.0f); + + // Test subtraction resulting in negative values + vec3 v3{1.0f, 2.0f, 3.0f}; + vec3 negative_vals_result = v3 - 5.0f; + + ASSERT_FLOAT_EQ(negative_vals_result[0], -4.0f); + ASSERT_FLOAT_EQ(negative_vals_result[1], -3.0f); + ASSERT_FLOAT_EQ(negative_vals_result[2], -2.0f); +} + TEST(vec, ScalarMultiplication) { // Test scalar * vector with float vectors @@ -776,8 +856,7 @@ TEST(SimpleContainer, DefaultConstruction) { TEST(SimpleContainer, AddSingleItem) { // Test adding a single item SimpleContainer container; - TestEntity entity(5.0f, 10.0f); - + auto entity = std::make_shared(5.0f, 10.0f); container.Add(entity); // Verify by getting items near the position @@ -788,10 +867,9 @@ TEST(SimpleContainer, AddSingleItem) { TEST(SimpleContainer, AddMultipleItems) { // Test adding multiple items SimpleContainer container; - - container.Add(TestEntity(0.0f, 0.0f)); - container.Add(TestEntity(10.0f, 10.0f)); - container.Add(TestEntity(20.0f, 20.0f)); + container.Add(std::make_shared(0.0f, 0.0f)); + container.Add(std::make_shared(10.0f, 10.0f)); + container.Add(std::make_shared(20.0f, 20.0f)); // Verify by getting items near a position auto results = container.Get(WorldPos(10.0f, 10.0f), 5.0f); @@ -803,10 +881,10 @@ TEST(SimpleContainer, GetItemsInRadius) { SimpleContainer container; // Add items in a known pattern - container.Add(TestEntity(0.0f, 0.0f)); // At origin - container.Add(TestEntity(1.0f, 0.0f)); // 1 unit away - container.Add(TestEntity(0.0f, 1.0f)); // 1 unit away - container.Add(TestEntity(10.0f, 10.0f)); // Far away + container.Add(std::make_shared(0.0f, 0.0f)); // At origin + container.Add(std::make_shared(1.0f, 0.0f)); // 1 unit away + container.Add(std::make_shared(0.0f, 1.0f)); // 1 unit away + container.Add(std::make_shared(10.0f, 10.0f)); // Far away // Get items within 2.0 units of origin auto results = container.Get(WorldPos(0.0f, 0.0f), 2.0f); @@ -827,7 +905,7 @@ TEST(SimpleContainer, GetItemsEmptyContainer) { TEST(SimpleContainer, WeakPtrValidAfterGet) { // Test that weak_ptr returned from Get can be locked SimpleContainer container; - container.Add(TestEntity(5.0f, 5.0f)); + container.Add(std::make_shared(5.0f, 5.0f)); auto results = container.Get(WorldPos(5.0f, 5.0f), 10.0f); @@ -845,8 +923,8 @@ TEST(SimpleContainer, WeakPtrValidAfterGet) { TEST(SimpleContainer, UpdateAllNoThrow) { // Test that UpdateAll doesn't throw (it's a no-op for SimpleContainer) SimpleContainer container; - container.Add(TestEntity(1.0f, 2.0f)); - container.Add(TestEntity(3.0f, 4.0f)); + container.Add(std::make_shared(1.0f, 2.0f)); + container.Add(std::make_shared(3.0f, 4.0f)); ASSERT_NO_THROW(container.UpdateAll()); } @@ -863,23 +941,14 @@ TEST(SimpleContainer, AddItemsWithSamePosition) { // Test adding multiple items at the same position SimpleContainer container; - container.Add(TestEntity(5.0f, 5.0f)); - container.Add(TestEntity(5.0f, 5.0f)); - container.Add(TestEntity(5.0f, 5.0f)); + container.Add(std::make_shared(5.0f, 5.0f)); + container.Add(std::make_shared(5.0f, 5.0f)); + container.Add(std::make_shared(5.0f, 5.0f)); auto results = container.Get(WorldPos(5.0f, 5.0f), 1.0f); - // All three items should be found (if Get is working) ASSERT_GE(results.size(), 0); } -TEST(SimpleContainer, ConformsToHasPosition) { - // Test that TestEntity conforms to HasPosition concept - // This is a compile-time test - if it compiles, it passes - static_assert(HasPosition, - "TestEntity should satisfy HasPosition concept"); - SUCCEED(); -} - TEST(PositionalContainer, DefaultConstruction) { // Test that PositionalContainer can be constructed with size and chunks WorldSize size(100.0f, 100.0f); @@ -893,8 +962,7 @@ TEST(PositionalContainer, AddSingleItem) { // Test adding a single item WorldSize size(100.0f, 100.0f); PositionalContainer container(size, 10); - - TestEntity entity(5.0f, 10.0f); + auto entity = std::make_shared(5.0f, 10.0f); container.Add(entity); // Verify by getting items near the position From 6d040cb61f3529e6f3c506ec92ea4e1266f22890 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 16 Oct 2025 20:56:55 +0200 Subject: [PATCH 06/23] Positional container WIP --- cpp/src/positional_container.hpp | 55 ++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/cpp/src/positional_container.hpp b/cpp/src/positional_container.hpp index 0b4e94d..29fa484 100644 --- a/cpp/src/positional_container.hpp +++ b/cpp/src/positional_container.hpp @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include "math.hpp" #include "log.hpp" @@ -20,11 +23,12 @@ concept HasCollisions = requires(T t) { template requires HasPosition + class IPositionalContainer { public: virtual ~IPositionalContainer() = default; - virtual bool Add(const T& t) = 0; + virtual bool Add(std::shared_ptr t) = 0; virtual std::vector> Get(const WorldPos& p, float radius) = 0; virtual void UpdateAll() = 0; virtual void Update(std::shared_ptr item) = 0; @@ -42,9 +46,10 @@ template class SimpleContainer : IPositionalContainer { public: - bool Add(const T& t) override + bool Add(std::shared_ptr t) override { - m_Items.push_back(std::make_shared(t)); + m_Items.push_back(t); + return true; } std::vector> Get(const WorldPos& center, float radius) override @@ -84,42 +89,62 @@ public: m_Grid.reserve(chunks); for (size_t i = 0; i < chunks; i++) { - //m_Grid.push_back(std::vector{chunks}); m_Grid.emplace_back(chunks); for (size_t j = 0; j < chunks; j++) { - // let's try reserving place for 16 items m_Grid[i][j].reserve(16); } } } // calling Add on object that is already in the container is UB - bool Add(const T& item) override + bool Add(std::shared_ptr item) override { - const auto& world_pos = item.GetPosition(); + const auto& world_pos = item->GetPosition(); if (!CheckBounds(world_pos)) { return false; } - auto ptr = std::make_shared(item); - m_Items.push_back(ptr); + m_Items.push_back(item); auto coords = GetCoords(world_pos); - // here we assume that the object is not already in the grid! - m_Grid[coords.x()][coords.y()].push_back(ptr); - + m_Grid[coords.x()][coords.y()].push_back(item); // TODO should we call Update instead? - //Update(ptr); + //Update(item); + return true; } std::vector> Get(const WorldPos& center, float radius) override { - return vector_wptr{}; + vector_wptr output_vec{}; + + Get(output_vec, center, radius); } void Get(std::vector>& output_vec, const WorldPos& center, float radius) { + output_vec.clear(); + const WorldPos A = center + radius; + const WorldPos B = center - radius; + if (!CheckBounds(A) || !CheckBounds(B)) + { + return; + } + auto [x_min_f, x_max_f] = std::minmax(A.x(), B.x()); + auto [y_min_f, y_max_f] = std::minmax(A.y(), B.y()); + size_t x_min = static_cast(std::floor(x_min_f)); + size_t x_max = static_cast(std::ceil(x_max_f)); + size_t y_min = static_cast(std::floor(y_min_f)); + size_t y_max = static_cast(std::ceil(y_max_f)); + + // TODO this goes through more positions than we need + for (size_t x = x_min; x < x_max; x++) + { + for (size_t y = y_min; y < y_max; y++) + { + std::ranges::copy(m_Grid[x][y], std::back_inserter(output_vec)); + } + } } void UpdateAll() override @@ -166,7 +191,5 @@ private: WorldSize m_GridStep; size_t m_ChunksPerAxis; std::vector> m_Items; - - grid_type m_Grid; }; From 370fc98588786c70a04465bfc2b235a217439c0e Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 17 Oct 2025 07:47:12 +0200 Subject: [PATCH 07/23] Positional container: basic implementation, added tests --- cpp/src/positional_container.hpp | 17 ++-- cpp/test/test.cpp | 146 ++++++++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 6 deletions(-) diff --git a/cpp/src/positional_container.hpp b/cpp/src/positional_container.hpp index 29fa484..ce6f685 100644 --- a/cpp/src/positional_container.hpp +++ b/cpp/src/positional_container.hpp @@ -118,17 +118,24 @@ public: vector_wptr output_vec{}; Get(output_vec, center, radius); + + return output_vec; } void Get(std::vector>& output_vec, const WorldPos& center, float radius) { output_vec.clear(); - const WorldPos A = center + radius; - const WorldPos B = center - radius; - if (!CheckBounds(A) || !CheckBounds(B)) + const WorldPos corner_1 = center + radius; + const WorldPos corner_2 = center - radius; + + if (!CheckBounds(corner_1) || !CheckBounds(corner_2)) { return; } + + const auto A = GetCoords(corner_1); + const auto B = GetCoords(corner_2); + auto [x_min_f, x_max_f] = std::minmax(A.x(), B.x()); auto [y_min_f, y_max_f] = std::minmax(A.y(), B.y()); @@ -138,9 +145,9 @@ public: size_t y_max = static_cast(std::ceil(y_max_f)); // TODO this goes through more positions than we need - for (size_t x = x_min; x < x_max; x++) + for (size_t x = x_min; x <= x_max; x++) { - for (size_t y = y_min; y < y_max; y++) + for (size_t y = y_min; y <= y_max; y++) { std::ranges::copy(m_Grid[x][y], std::back_inserter(output_vec)); } diff --git a/cpp/test/test.cpp b/cpp/test/test.cpp index f4e2d04..2944889 100644 --- a/cpp/test/test.cpp +++ b/cpp/test/test.cpp @@ -967,10 +967,154 @@ TEST(PositionalContainer, AddSingleItem) { // Verify by getting items near the position auto results = container.Get(WorldPos(5.0f, 10.0f), 1.0f); - // Note: Get implementation may not be complete yet ASSERT_GE(results.size(), 1); } +TEST(PositionalContainer, AddMultipleItems) { + // Test adding multiple items + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + container.Add(std::make_shared(10.0f, 10.0f)); + container.Add(std::make_shared(20.0f, 20.0f)); + container.Add(std::make_shared(30.0f, 30.0f)); + + // Verify by getting items near a position + auto results = container.Get(WorldPos(20.0f, 20.0f), 5.0f); + ASSERT_GE(results.size(), 1); +} + +TEST(PositionalContainer, GetItemsInRadius) { + // Test getting items within a radius + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + // Add items in a known pattern + container.Add(std::make_shared(50.0f, 50.0f)); // At center + container.Add(std::make_shared(51.0f, 50.0f)); // 1 unit away + container.Add(std::make_shared(50.0f, 51.0f)); // 1 unit away + container.Add(std::make_shared(90.0f, 90.0f)); // Far away + + // Get items within 2.0 units of center position + auto results = container.Get(WorldPos(50.0f, 50.0f), 2.0f); + + // Should find the 3 nearby items + ASSERT_GE(results.size(), 0); +} + +TEST(PositionalContainer, GetItemsEmptyContainer) { + // Test getting items from empty container + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + auto results = container.Get(WorldPos(50.0f, 50.0f), 10.0f); + ASSERT_EQ(results.size(), 0); +} + +TEST(PositionalContainer, WeakPtrValidAfterGet) { + // Test that weak_ptr returned from Get can be locked + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + container.Add(std::make_shared(50.0f, 50.0f)); + + auto results = container.Get(WorldPos(50.0f, 50.0f), 10.0f); + + if (!results.empty()) { + auto locked = results[0].lock(); + ASSERT_NE(locked, nullptr); + + // Verify the position + WorldPos pos = locked->GetPosition(); + ASSERT_FLOAT_EQ(pos.x(), 50.0f); + ASSERT_FLOAT_EQ(pos.y(), 50.0f); + } +} + +TEST(PositionalContainer, UpdateAllNoThrow) { + // Test that UpdateAll doesn't throw + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + container.Add(std::make_shared(10.0f, 20.0f)); + container.Add(std::make_shared(30.0f, 40.0f)); + + ASSERT_NO_THROW(container.UpdateAll()); +} + +TEST(PositionalContainer, UpdateNoThrow) { + // Test that Update doesn't throw + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + auto item = std::make_shared(10.0f, 20.0f); + container.Add(item); + + ASSERT_NO_THROW(container.Update(item)); +} + +TEST(PositionalContainer, AddItemsWithSamePosition) { + // Test adding multiple items at the same position + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + container.Add(std::make_shared(50.0f, 50.0f)); + container.Add(std::make_shared(50.0f, 50.0f)); + container.Add(std::make_shared(50.0f, 50.0f)); + + auto results = container.Get(WorldPos(50.0f, 50.0f), 1.0f); + ASSERT_GE(results.size(), 0); +} + +TEST(PositionalContainer, AddOutOfBounds) { + // Test adding items outside the grid bounds + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + // Try to add items outside bounds + auto result1 = container.Add(std::make_shared(-5.0f, 50.0f)); + auto result2 = container.Add(std::make_shared(105.0f, 50.0f)); + auto result3 = container.Add(std::make_shared(50.0f, -5.0f)); + auto result4 = container.Add(std::make_shared(50.0f, 105.0f)); + + // All should return false + ASSERT_FALSE(result1); + ASSERT_FALSE(result2); + ASSERT_FALSE(result3); + ASSERT_FALSE(result4); +} + +TEST(PositionalContainer, GetOutOfBounds) { + // Test getting items with center or radius extending out of bounds + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + container.Add(std::make_shared(50.0f, 50.0f)); + + // Query that extends out of bounds should return empty + auto results = container.Get(WorldPos(95.0f, 95.0f), 10.0f); + ASSERT_EQ(results.size(), 0); +} + +TEST(PositionalContainer, ItemsInDifferentChunks) { + // Test that items in different grid chunks can be retrieved correctly + WorldSize size(100.0f, 100.0f); + PositionalContainer container(size, 10); + + // Add items in different chunks (grid is 10x10, so each chunk is 10x10 units) + container.Add(std::make_shared(15.0f, 15.0f)); // Chunk (1,1) + container.Add(std::make_shared(25.0f, 25.0f)); // Chunk (2,2) + container.Add(std::make_shared(75.0f, 75.0f)); // Chunk (7,7) + + // Query in chunk (1,1) + auto results1 = container.Get(WorldPos(15.0f, 15.0f), 3.0f); + ASSERT_GE(results1.size(), 1); + + // Query in chunk (7,7) + auto results2 = container.Get(WorldPos(75.0f, 75.0f), 3.0f); + ASSERT_GE(results2.size(), 1); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); From 057842ca7b11b8ef2b91ac9703baf76c6e7b54d2 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 17 Oct 2025 07:54:51 +0200 Subject: [PATCH 08/23] Fixed warnings --- cpp/src/positional_container.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cpp/src/positional_container.hpp b/cpp/src/positional_container.hpp index ce6f685..6067114 100644 --- a/cpp/src/positional_container.hpp +++ b/cpp/src/positional_container.hpp @@ -57,7 +57,6 @@ public: std::vector> matched_items; for (const auto& item : m_Items) { - const auto& A = item->GetPosition(); if (center.DistanceTo(item->GetPosition()) < radius) { matched_items.push_back(item); @@ -68,7 +67,7 @@ public: // no update needed here, as we have no smart lookup scheme void UpdateAll() override {} - void Update(std::shared_ptr item) override {} + void Update(std::shared_ptr) override {} private: std::vector> m_Items; From b2e9c1b55e143c7b451277c62fd0cc0025cf2233 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 17 Oct 2025 08:10:53 +0200 Subject: [PATCH 09/23] Positional container: tests for Update method --- cpp/test/test.cpp | 125 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 19 deletions(-) diff --git a/cpp/test/test.cpp b/cpp/test/test.cpp index 2944889..d786e84 100644 --- a/cpp/test/test.cpp +++ b/cpp/test/test.cpp @@ -920,21 +920,67 @@ TEST(SimpleContainer, WeakPtrValidAfterGet) { } } -TEST(SimpleContainer, UpdateAllNoThrow) { - // Test that UpdateAll doesn't throw (it's a no-op for SimpleContainer) +TEST(SimpleContainer, UpdateAll) { + // Test that UpdateAll properly updates item positions + // For SimpleContainer, Update is a no-op but Get should still find moved items SimpleContainer container; - container.Add(std::make_shared(1.0f, 2.0f)); - container.Add(std::make_shared(3.0f, 4.0f)); - ASSERT_NO_THROW(container.UpdateAll()); + auto item1 = std::make_shared(10.0f, 10.0f); + auto item2 = std::make_shared(20.0f, 20.0f); + container.Add(item1); + container.Add(item2); + + // Verify items exist at original positions + auto results_old1 = container.Get(WorldPos(10.0f, 10.0f), 1.0f); + auto results_old2 = container.Get(WorldPos(20.0f, 20.0f), 1.0f); + ASSERT_GE(results_old1.size(), 1); + ASSERT_GE(results_old2.size(), 1); + + // Change positions + item1->SetPosition(WorldPos(50.0f, 50.0f)); + item2->SetPosition(WorldPos(60.0f, 60.0f)); + + // Call UpdateAll + container.UpdateAll(); + + // For SimpleContainer, items should still be found at new positions + // (since Get checks actual item positions, not cached locations) + auto results_new1 = container.Get(WorldPos(50.0f, 50.0f), 1.0f); + auto results_new2 = container.Get(WorldPos(60.0f, 60.0f), 1.0f); + ASSERT_GE(results_new1.size(), 1); + ASSERT_GE(results_new2.size(), 1); + + // Items should NOT be found at old positions anymore + auto results_old_check1 = container.Get(WorldPos(10.0f, 10.0f), 1.0f); + auto results_old_check2 = container.Get(WorldPos(20.0f, 20.0f), 1.0f); + ASSERT_EQ(results_old_check1.size(), 0); + ASSERT_EQ(results_old_check2.size(), 0); } -TEST(SimpleContainer, UpdateNoThrow) { - // Test that Update doesn't throw (it's a no-op for SimpleContainer) +TEST(SimpleContainer, Update) { + // Test that Update properly handles a single item position change SimpleContainer container; - auto item = std::make_shared(1.0f, 2.0f); - ASSERT_NO_THROW(container.Update(item)); + auto item = std::make_shared(15.0f, 15.0f); + container.Add(item); + + // Verify item exists at original position + auto results_old = container.Get(WorldPos(15.0f, 15.0f), 1.0f); + ASSERT_GE(results_old.size(), 1); + + // Change position + item->SetPosition(WorldPos(75.0f, 75.0f)); + + // Call Update + container.Update(item); + + // Item should be found at new position + auto results_new = container.Get(WorldPos(75.0f, 75.0f), 1.0f); + ASSERT_GE(results_new.size(), 1); + + // Item should NOT be found at old position + auto results_old_check = container.Get(WorldPos(15.0f, 15.0f), 1.0f); + ASSERT_EQ(results_old_check.size(), 0); } TEST(SimpleContainer, AddItemsWithSamePosition) { @@ -946,7 +992,7 @@ TEST(SimpleContainer, AddItemsWithSamePosition) { container.Add(std::make_shared(5.0f, 5.0f)); auto results = container.Get(WorldPos(5.0f, 5.0f), 1.0f); - ASSERT_GE(results.size(), 0); + ASSERT_EQ(results.size(), 3); } TEST(PositionalContainer, DefaultConstruction) { @@ -1031,26 +1077,67 @@ TEST(PositionalContainer, WeakPtrValidAfterGet) { } } -TEST(PositionalContainer, UpdateAllNoThrow) { - // Test that UpdateAll doesn't throw +TEST(PositionalContainer, UpdateAll) { + // Test that UpdateAll properly updates spatial index after position changes WorldSize size(100.0f, 100.0f); PositionalContainer container(size, 10); - container.Add(std::make_shared(10.0f, 20.0f)); - container.Add(std::make_shared(30.0f, 40.0f)); + auto item1 = std::make_shared(15.0f, 15.0f); + auto item2 = std::make_shared(25.0f, 25.0f); + container.Add(item1); + container.Add(item2); - ASSERT_NO_THROW(container.UpdateAll()); + // Verify items exist at original positions + auto results_old1 = container.Get(WorldPos(15.0f, 15.0f), 2.0f); + auto results_old2 = container.Get(WorldPos(25.0f, 25.0f), 2.0f); + ASSERT_GE(results_old1.size(), 1); + ASSERT_GE(results_old2.size(), 1); + + // Change positions to different grid chunks + item1->SetPosition(WorldPos(65.0f, 65.0f)); + item2->SetPosition(WorldPos(75.0f, 75.0f)); + + // Call UpdateAll to refresh spatial index + container.UpdateAll(); + + // Items should be found at new positions + auto results_new1 = container.Get(WorldPos(65.0f, 65.0f), 2.0f); + auto results_new2 = container.Get(WorldPos(75.0f, 75.0f), 2.0f); + ASSERT_GE(results_new1.size(), 1); + ASSERT_GE(results_new2.size(), 1); + + // Items should NOT be found at old positions anymore + auto results_old_check1 = container.Get(WorldPos(15.0f, 15.0f), 2.0f); + auto results_old_check2 = container.Get(WorldPos(25.0f, 25.0f), 2.0f); + ASSERT_EQ(results_old_check1.size(), 0); + ASSERT_EQ(results_old_check2.size(), 0); } -TEST(PositionalContainer, UpdateNoThrow) { - // Test that Update doesn't throw +TEST(PositionalContainer, Update) { + // Test that Update properly updates spatial index for a single item WorldSize size(100.0f, 100.0f); PositionalContainer container(size, 10); - auto item = std::make_shared(10.0f, 20.0f); + auto item = std::make_shared(20.0f, 20.0f); container.Add(item); - ASSERT_NO_THROW(container.Update(item)); + // Verify item exists at original position + auto results_old = container.Get(WorldPos(20.0f, 20.0f), 2.0f); + ASSERT_GE(results_old.size(), 1); + + // Change position to different grid chunk + item->SetPosition(WorldPos(80.0f, 80.0f)); + + // Call Update to refresh spatial index + container.Update(item); + + // Item should be found at new position + auto results_new = container.Get(WorldPos(80.0f, 80.0f), 2.0f); + ASSERT_GE(results_new.size(), 1); + + // Item should NOT be found at old position + auto results_old_check = container.Get(WorldPos(20.0f, 20.0f), 2.0f); + ASSERT_EQ(results_old_check.size(), 0); } TEST(PositionalContainer, AddItemsWithSamePosition) { From b94b18993d5c9a50c058847d2d98f26f2ef5daa4 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 17 Oct 2025 09:43:12 +0200 Subject: [PATCH 10/23] Positional container: implemented Update --- cpp/src/positional_container.hpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/cpp/src/positional_container.hpp b/cpp/src/positional_container.hpp index 6067114..38cf5c8 100644 --- a/cpp/src/positional_container.hpp +++ b/cpp/src/positional_container.hpp @@ -107,6 +107,7 @@ public: m_Items.push_back(item); auto coords = GetCoords(world_pos); m_Grid[coords.x()][coords.y()].push_back(item); + m_ReverseGridLookup[item] = coords; // TODO should we call Update instead? //Update(item); return true; @@ -158,16 +159,29 @@ public: for (auto ptr : m_Items) { // TODO is this efficient? Maybe use const ref? + + Update(ptr); } } void Update(std::shared_ptr item) override { - auto coords = GetCoords(item->GetPosition()); + coord_type current_coords = GetCoords(item->GetPosition()); + coord_type last_known_coords = m_ReverseGridLookup[item]; + if (current_coords == last_known_coords) + { + return; + } + vector_wptr& vec = m_Grid[last_known_coords.x()][last_known_coords.y()]; // remove the old weak ptr from the map - + vec.erase(std::remove_if(vec.begin(), vec.end(), + [&](const std::weak_ptr& w) + { + return !w.owner_before(item) && !item.owner_before(w); + }), + vec.end()); // add new weak ptr to the map - m_Grid[coords.x()][coords.y()].push_back(item); + m_Grid[current_coords.x()][current_coords.y()].push_back(item); } private: @@ -196,6 +210,16 @@ private: WorldSize m_GridSize; WorldSize m_GridStep; size_t m_ChunksPerAxis; + // TODO it would be better to have vector - contiguous memory, more cache-friendly? std::vector> m_Items; grid_type m_Grid; + + // normal lookup: WorldPos -> coord_type -> vector_wptr -> std::shared_ptr + // reverse lookup: std::shared_ptr -> vector_wptr -> coord_type + // we need the reverse lookup because T.GetPosition() may change and we need to delete + // the old weak_ptr from vector_wptr (without iterating through all of them). + // Also it might be useful to have T -> location lookup + // Note: hash of std::shared_ptr may give us trouble if we free the memory and new one points + // to the same location, maybe it would be better to hash the object itself? + std::unordered_map, coord_type> m_ReverseGridLookup; }; From cfe48661d81d618635697545fcef71c51c68dbc0 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 17 Oct 2025 10:11:15 +0200 Subject: [PATCH 11/23] vec: Added no-overhead constructor for tag change --- cpp/src/math.hpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cpp/src/math.hpp b/cpp/src/math.hpp index a44638e..f6c45f7 100644 --- a/cpp/src/math.hpp +++ b/cpp/src/math.hpp @@ -46,6 +46,9 @@ public: vec(std::array array) : m_Array{array} {} + template + vec(vec&& other) : m_Array{std::move(other.m_Array)} {} + // // Access to elements & data // @@ -276,11 +279,17 @@ public: } template - vec ChangeTag() + vec ChangeTag() const & { return vec(m_Array); } + template + vec ChangeTag() && + { + return vec(std::move(*this)); + } + // Structured binding support for N == 2 template const T& get() const From 3c2b636ea81700825047f7824701103091941e60 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 17 Oct 2025 10:21:45 +0200 Subject: [PATCH 12/23] Positional container: add Get overload --- cpp/src/positional_container.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cpp/src/positional_container.hpp b/cpp/src/positional_container.hpp index 38cf5c8..e131d2d 100644 --- a/cpp/src/positional_container.hpp +++ b/cpp/src/positional_container.hpp @@ -122,6 +122,16 @@ public: return output_vec; } + void Get(std::vector>& output_vec, const WorldPos& corner, const WorldSize& size) + { + const WorldSize half_size = size / 2.0f; + const WorldPos center = corner + half_size.ChangeTag(); + float radius = half_size.x(); + Get(output_vec, center, radius); + } + + // TODO add those Get methods to the interface + void Get(std::vector>& output_vec, const WorldPos& center, float radius) { output_vec.clear(); From d1cb6dbac7648576fa4b68386ba22623e9315aa5 Mon Sep 17 00:00:00 2001 From: Mrna Date: Tue, 21 Oct 2025 07:51:46 +0200 Subject: [PATCH 13/23] TODO comment --- cpp/src/positional_container.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/src/positional_container.hpp b/cpp/src/positional_container.hpp index e131d2d..e8fd57e 100644 --- a/cpp/src/positional_container.hpp +++ b/cpp/src/positional_container.hpp @@ -231,5 +231,6 @@ private: // Also it might be useful to have T -> location lookup // Note: hash of std::shared_ptr may give us trouble if we free the memory and new one points // to the same location, maybe it would be better to hash the object itself? + // TODO how about using counting bloom filter for this? std::unordered_map, coord_type> m_ReverseGridLookup; }; From a2c77966b8fb632cc57814f78e7da3cb0a475a27 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 30 Oct 2025 14:19:59 +0100 Subject: [PATCH 14/23] Positional container: performance tests --- cpp/src/math.hpp | 12 ++ cpp/src/positional_container.hpp | 36 ++++-- cpp/test/collision_performance.cpp | 179 ++++++++++++++++++++++++----- 3 files changed, 190 insertions(+), 37 deletions(-) diff --git a/cpp/src/math.hpp b/cpp/src/math.hpp index f6c45f7..fea1c92 100644 --- a/cpp/src/math.hpp +++ b/cpp/src/math.hpp @@ -37,6 +37,11 @@ static inline bool equalEpsilon(const T &a, const T &b) { struct Any {}; template class vec { + + // Friend declaration for move constructor from different tag types + template + friend class vec; + public: vec() : m_Array{} {} @@ -143,6 +148,13 @@ public: return c; } + friend vec operator/(const vec &a, const vec& b) { + vec c; + std::ranges::transform(a.m_Array, b.m_Array, + c.m_Array.begin(), std::divides{}); + return c; + } + // // compound-assignment operators // diff --git a/cpp/src/positional_container.hpp b/cpp/src/positional_container.hpp index e8fd57e..51419b1 100644 --- a/cpp/src/positional_container.hpp +++ b/cpp/src/positional_container.hpp @@ -138,11 +138,6 @@ public: const WorldPos corner_1 = center + radius; const WorldPos corner_2 = center - radius; - if (!CheckBounds(corner_1) || !CheckBounds(corner_2)) - { - return; - } - const auto A = GetCoords(corner_1); const auto B = GetCoords(corner_2); @@ -154,12 +149,30 @@ public: size_t y_min = static_cast(std::floor(y_min_f)); size_t y_max = static_cast(std::ceil(y_max_f)); - // TODO this goes through more positions than we need for (size_t x = x_min; x <= x_max; x++) { for (size_t y = y_min; y <= y_max; y++) { + if (!CheckBounds(x, y)) + { + continue; + } +#if 0 + // TODO this is approx 2x faster, but inserts items outside of radius; + // We can use this is a Get(rectangle) function std::ranges::copy(m_Grid[x][y], std::back_inserter(output_vec)); +#else + for (auto item_wptr : m_Grid[x][y]) + { + if (auto shared = item_wptr.lock()) + { + if (center.DistanceTo(shared->GetPosition()) < radius) + { + output_vec.push_back(item_wptr); + } + } + } +#endif } } } @@ -202,14 +215,21 @@ private: coord_type GetCoords(const WorldPos &wp) { - auto coord_float = wp / m_ChunksPerAxis; + auto coord_float = wp / m_GridStep.ChangeTag(); return coord_type{ static_cast(coord_float.x()), static_cast(coord_float.y()) }; } - bool CheckBounds(const WorldPos& pos) + bool CheckBounds(size_t x, size_t y) const + { + bool x_in_bounds = x < m_Grid.size(); + bool y_in_bounds = y < m_Grid.size(); + return x_in_bounds && y_in_bounds; + } + + bool CheckBounds(const WorldPos& pos) const { auto [x,y] = pos; bool x_in_bounds = 0.0f < x && x < m_GridSize.x(); diff --git a/cpp/test/collision_performance.cpp b/cpp/test/collision_performance.cpp index 3db6c73..8a2179d 100644 --- a/cpp/test/collision_performance.cpp +++ b/cpp/test/collision_performance.cpp @@ -2,14 +2,12 @@ #include #include #include - +#include +#include +#include #include "positional_container.hpp" -// TODO: Add necessary includes for collision testing -// #include "collision_shapes.hpp" -// #include "entities.hpp" - /** * @file collision_performance.cpp * @brief Performance tests for collision detection systems @@ -83,48 +81,171 @@ void benchmark_function(const std::string& name, int iterations, Func func) { */ class Dummy { public: - Dummy() : m_Position(0.0f, 0.0f) {} - Dummy(float x, float y) : m_Position(x, y) {} - Dummy(WorldPos pos) : m_Position(pos) {} + Dummy() : m_Position{0.0f, 0.0f}, m_Id(next_id++) {} + Dummy(float x, float y) : m_Position{x, y}, m_Id(next_id++) {} + Dummy(WorldPos pos) : m_Position(pos), m_Id(next_id++) {} WorldPos GetPosition() const { return m_Position; } void SetPosition(WorldPos pos) { m_Position = pos; } + int GetId() const { return m_Id; } private: WorldPos m_Position; + int m_Id; + static int next_id; }; -// Example test function 1 -void test_function_1() { - - PositionalContainer pos_cont{WorldSize{10.0f, 10.0f}, 10}; - SimpleContainer simp_cont; +int Dummy::next_id = 0; - // TODO: Implement actual collision test - volatile int sum = 0; - for (int i = 0; i < 1000; ++i) { - sum += i; - } +/** + * @brief Helper function to generate random float in range [min, max] + */ +float random_float(std::mt19937& gen, float min, float max) { + std::uniform_real_distribution dist(min, max); + return dist(gen); } -// Example test function 2 -void test_function_2() { - // TODO: Implement actual collision test - volatile int product = 1; - for (int i = 1; i < 100; ++i) { - product *= (i % 10 + 1); +/** + * @brief Compare two sets of weak_ptrs by comparing the IDs of the objects they point to + */ +bool compare_results(const std::vector>& a, + const std::vector>& b) { + std::set ids_a, ids_b; + + for (const auto& weak : a) { + if (auto shared = weak.lock()) { + ids_a.insert(shared->GetId()); + } } + + for (const auto& weak : b) { + if (auto shared = weak.lock()) { + ids_b.insert(shared->GetId()); + } + } + + return ids_a == ids_b; } -TEST(CollisionPerformance, CompareAlgorithms) { +TEST(CollisionPerformance, CompareContainers) { std::cout << "\n=== Collision Performance Comparison ===\n" << std::endl; - const int iterations = 10000; + // Configuration + const int NUM_OBJECTS = 1000; + const int NUM_LOOKUPS = 100; + const float WORLD_SIZE = 1000.0f; + const float LOOKUP_RADIUS = 50.0f; + const size_t CHUNKS = 20; - benchmark_function("Algorithm 1 (test_function_1)", iterations, test_function_1); - benchmark_function("Algorithm 2 (test_function_2)", iterations, test_function_2); + // Random number generator + std::random_device rd; + std::mt19937 gen(rd()); + + // Create containers + PositionalContainer pos_cont{WorldSize{WORLD_SIZE, WORLD_SIZE}, CHUNKS}; + SimpleContainer simp_cont; + + // Create and add dummy objects with random positions + std::vector> objects; + objects.reserve(NUM_OBJECTS); + + std::cout << "Creating " << NUM_OBJECTS << " objects with random positions..." << std::endl; + for (int i = 0; i < NUM_OBJECTS; ++i) { + float x = random_float(gen, 10.0f, WORLD_SIZE - 10.0f); + float y = random_float(gen, 10.0f, WORLD_SIZE - 10.0f); + auto obj = std::make_shared(x, y); + objects.push_back(obj); + pos_cont.Add(obj); + simp_cont.Add(obj); + } + std::cout << "Objects created and added to containers." << std::endl; + + // Generate random lookup positions + std::vector lookup_positions; + lookup_positions.reserve(NUM_LOOKUPS); + for (int i = 0; i < NUM_LOOKUPS; ++i) { + float x = random_float(gen, 0.0f, WORLD_SIZE); + float y = random_float(gen, 0.0f, WORLD_SIZE); + lookup_positions.push_back(WorldPos{x, y}); + } + + // Benchmark SimpleContainer + double simple_total_time = 0.0; + std::vector>> simple_results; + simple_results.reserve(NUM_LOOKUPS); + + std::cout << "\nBenchmarking SimpleContainer with " << NUM_LOOKUPS << " lookups..." << std::endl; + for (const auto& pos : lookup_positions) { + auto start = PerformanceTimer::Clock::now(); + auto result = simp_cont.Get(pos, LOOKUP_RADIUS); + auto end = PerformanceTimer::Clock::now(); + + PerformanceTimer::Duration duration = end - start; + simple_total_time += duration.count(); + simple_results.push_back(result); + } + + double simple_avg_time = simple_total_time / NUM_LOOKUPS; + std::cout << std::fixed << std::setprecision(6) + << "[BENCHMARK] SimpleContainer:\n" + << " Total time: " << simple_total_time << " ms\n" + << " Average time per lookup: " << simple_avg_time << " ms\n" + << " Throughput: " << (NUM_LOOKUPS / (simple_total_time / 1000.0)) + << " lookups/sec" << std::endl; + + // Benchmark PositionalContainer + double positional_total_time = 0.0; + std::vector>> positional_results; + positional_results.reserve(NUM_LOOKUPS); + + std::cout << "\nBenchmarking PositionalContainer with " << NUM_LOOKUPS << " lookups..." << std::endl; + for (const auto& pos : lookup_positions) { + auto start = PerformanceTimer::Clock::now(); + auto result = pos_cont.Get(pos, LOOKUP_RADIUS); + auto end = PerformanceTimer::Clock::now(); + + PerformanceTimer::Duration duration = end - start; + positional_total_time += duration.count(); + positional_results.push_back(result); + } + + double positional_avg_time = positional_total_time / NUM_LOOKUPS; + std::cout << std::fixed << std::setprecision(6) + << "[BENCHMARK] PositionalContainer:\n" + << " Total time: " << positional_total_time << " ms\n" + << " Average time per lookup: " << positional_avg_time << " ms\n" + << " Throughput: " << (NUM_LOOKUPS / (positional_total_time / 1000.0)) + << " lookups/sec" << std::endl; + + // Verify results match + std::cout << "\nVerifying results correctness..." << std::endl; + int mismatches = 0; + for (size_t i = 0; i < NUM_LOOKUPS; ++i) { + if (!compare_results(simple_results[i], positional_results[i])) { + mismatches++; + std::cout << "Mismatch at lookup " << i + << " (pos: " << lookup_positions[i] << ")" << std::endl; + } + } + + if (mismatches == 0) { + std::cout << "✓ All " << NUM_LOOKUPS << " lookups produced identical results!" << std::endl; + } else { + std::cout << "✗ Found " << mismatches << " mismatches out of " + << NUM_LOOKUPS << " lookups" << std::endl; + } + + // Performance comparison + std::cout << "\n=== Performance Summary ===\n"; + double speedup = simple_avg_time / positional_avg_time; + std::cout << std::fixed << std::setprecision(2) + << "PositionalContainer is " << speedup << "x " + << (speedup > 1.0 ? "faster" : "slower") + << " than SimpleContainer" << std::endl; std::cout << "\n======================================\n" << std::endl; - SUCCEED(); + // Assertions + EXPECT_EQ(mismatches, 0) << "Results should match between containers"; + EXPECT_GT(speedup, 1.0) << "PositionalContainer should be faster than SimpleContainer"; } From fc15b132f267582952ad435e374faae00fb3973e Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 30 Oct 2025 15:09:08 +0100 Subject: [PATCH 15/23] Added clang-format file (LLVM default --- .clang-format | 297 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e9ca3f7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,297 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: true + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseArrows: false + AlignCaseColons: false +AlignConsecutiveTableGenBreakingDAGArgColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenCondOperatorColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenDefinitionColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseExpressionOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AllowShortNamespacesOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AttributeMacros: + - __capability +BinPackArguments: true +BinPackLongBracedList: true +BinPackParameters: BinPack +BitFieldColonSpacing: Both +BracedInitializerIndentWidth: -1 +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakAfterReturnType: None +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTemplateCloser: false +BreakBeforeTernaryOperators: true +BreakBinaryOperations: Never +BreakConstructorInitializers: BeforeColon +BreakFunctionDefinitionParameters: false +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +BreakTemplateDeclarations: MultiLine +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +EnumTrailingComma: Leave +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExportBlock: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLines: + AtEndOfFile: false + AtStartOfBlock: true + AtStartOfFile: true +KeepFormFeed: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MainIncludeChar: Quote +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +OneLineFormatOffRegex: '' +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakBeforeMemberAccess: 150 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: Always +RemoveBracesLLVM: false +RemoveEmptyLinesInUnwrappedLines: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: + Enabled: true + IgnoreCase: false +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterOperatorKeyword: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterNot: false + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + ExceptDoubleParentheses: false + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TableGenBreakInsideDAGArg: DontBreak +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +WrapNamespaceBodyWithEmptyLines: Leave +... + From 193310f704ee5ccab772146f36033e788491f2b8 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 30 Oct 2025 15:09:31 +0100 Subject: [PATCH 16/23] Added format target --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 10c5760..8c26e04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,3 +189,12 @@ else() set(CMAKE_CXX_FLAGS_DEBUG "-ggdb3 -O0") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") endif() + + +list(TRANSFORM MAIN_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/") +list(TRANSFORM HEADERS PREPEND "${CMAKE_SOURCE_DIR}/") + +# Formatting target (clang only) +add_custom_target(format + COMMAND clang-format -i ${MAIN_SOURCES} ${HEADERS} +) \ No newline at end of file From 9c1ec01ce0fca0c1878282cc1636ba3da72cc9a8 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 30 Oct 2025 15:10:00 +0100 Subject: [PATCH 17/23] Re-formatted files --- cpp/src/camera.cpp | 34 ++--- cpp/src/camera.hpp | 21 ++- cpp/src/entities.cpp | 23 ++-- cpp/src/entities.hpp | 12 +- cpp/src/gameloop.cpp | 34 ++--- cpp/src/gameloop.hpp | 16 +-- cpp/src/log.hpp | 6 +- cpp/src/map.cpp | 67 +++++----- cpp/src/map.hpp | 13 +- cpp/src/math.hpp | 221 ++++++++++++++------------------ cpp/src/pathfinder/base.cpp | 9 +- cpp/src/pathfinder/base.hpp | 26 ++-- cpp/src/pathfinder/bfs.cpp | 87 ++++++------- cpp/src/pathfinder/bfs.hpp | 10 +- cpp/src/pathfinder/dijkstra.cpp | 64 +++++---- cpp/src/pathfinder/dijkstra.hpp | 8 +- cpp/src/pathfinder/gbfs.cpp | 58 ++++----- cpp/src/pathfinder/gbfs.hpp | 10 +- cpp/src/pathfinder/utils.cpp | 30 ++--- cpp/src/pathfinder/utils.hpp | 18 +-- cpp/src/pathfindingdemo.cpp | 185 +++++++++++--------------- cpp/src/pathfindingdemo.hpp | 19 +-- cpp/src/tile.cpp | 10 +- cpp/src/tile.hpp | 2 +- cpp/src/user_input.cpp | 135 ++++++++----------- cpp/src/user_input.hpp | 12 +- cpp/src/window.cpp | 18 +-- cpp/src/window.hpp | 11 +- 28 files changed, 523 insertions(+), 636 deletions(-) diff --git a/cpp/src/camera.cpp b/cpp/src/camera.cpp index 8e04ae7..c09d8cc 100644 --- a/cpp/src/camera.cpp +++ b/cpp/src/camera.cpp @@ -1,45 +1,35 @@ #include "camera.hpp" -#include "math.hpp" #include "log.hpp" +#include "math.hpp" // for now only pass-through placeholder functions, -// since we draw the whole map +// since we draw the whole map +void Camera::Pan(const WorldPos &delta) { m_Pan += (delta / m_Zoom); } -void Camera::Pan(const WorldPos& delta) -{ - m_Pan += (delta / m_Zoom); -} - -void Camera::Zoom(float delta) -{ +void Camera::Zoom(float delta) { constexpr float ZOOM_SCALE = 0.1f; m_Zoom += delta * ZOOM_SCALE; LOG_DEBUG("Zoom: ", m_Zoom); } -WindowPos Camera::WorldToWindow(WorldPos world) const -{ - const auto& v = world + m_Pan; +WindowPos Camera::WorldToWindow(WorldPos world) const { + const auto &v = world + m_Pan; return WindowPos{v[0], v[1]} * m_Zoom; } -WorldPos Camera::WindowToWorld(WindowPos window) const -{ +WorldPos Camera::WindowToWorld(WindowPos window) const { window /= m_Zoom; return WorldPos{window[0], window[1]} - m_Pan; } - -WindowSize Camera::WorldToWindowSize(WorldSize world) const -{ - const auto& v = world; +WindowSize Camera::WorldToWindowSize(WorldSize world) const { + const auto &v = world; // no zoom yet, just pass-through return WindowSize{v[0], v[1]} * m_Zoom; } -WorldSize Camera::WindowToWorldSize(WindowSize window) const -{ - window /= m_Zoom; - return WorldSize{window[0], window[1]}; +WorldSize Camera::WindowToWorldSize(WindowSize window) const { + window /= m_Zoom; + return WorldSize{window[0], window[1]}; } diff --git a/cpp/src/camera.hpp b/cpp/src/camera.hpp index b321baa..37b2527 100644 --- a/cpp/src/camera.hpp +++ b/cpp/src/camera.hpp @@ -2,28 +2,27 @@ #include "math.hpp" -class Camera -{ +class Camera { public: - void Pan(const WorldPos& delta); + void Pan(const WorldPos &delta); void Zoom(float delta); WorldPos GetPan() const { return m_Pan; } float GetZoom() const { return m_Zoom; } - WindowPos WorldToWindow(WorldPos) const; - WorldPos WindowToWorld(WindowPos) const; - WindowSize WorldToWindowSize(WorldSize) const; - WorldSize WindowToWorldSize(WindowSize) const; - + WindowPos WorldToWindow(WorldPos) const; + WorldPos WindowToWorld(WindowPos) const; + WindowSize WorldToWindowSize(WorldSize) const; + WorldSize WindowToWorldSize(WindowSize) const; + template - requires std::floating_point + requires std::floating_point T WindowToWorldSize(T window_size) const { return window_size / static_cast(m_Zoom); } - + template - requires std::floating_point + requires std::floating_point T WorldToWindowSize(T world_size) const { return world_size * static_cast(m_Zoom); } diff --git a/cpp/src/entities.cpp b/cpp/src/entities.cpp index 453b003..cd919f5 100644 --- a/cpp/src/entities.cpp +++ b/cpp/src/entities.cpp @@ -30,7 +30,8 @@ void Entity::ZeroActualVelocityInDirection(WorldPos direction) { // q1 * e1 + q2 * e2 = v, from this follows: auto &v = GetActualVelocity(); - float q2 = (v.y() * e1.x() - v.x() * e1.y()) / (e2.y() * e1.x() - e2.x() * e1.y()); + float q2 = + (v.y() * e1.x() - v.x() * e1.y()) / (e2.y() * e1.x() - e2.x() * e1.y()); float q1 = (v.x() - q2 * e2.x()) / e1.x(); // We then zero-out the q1, but only if it's positive - meaning @@ -46,9 +47,8 @@ void Entity::Update(float time_delta) { m_Position += m_ActualVelocity * time_delta; } -std::optional Entity::GetMoveTarget() -{ - auto& path = GetPath(); +std::optional Entity::GetMoveTarget() { + auto &path = GetPath(); if (path.empty()) { return {}; } @@ -61,27 +61,24 @@ std::optional Entity::GetMoveTarget() return next_pos; } // target reached, pop it - //m_MoveQueue.pop(); + // m_MoveQueue.pop(); path.erase(path.begin()); // return nothing - if there's next point in the queue, // we'll get it in the next iteration return {}; } -bool Entity::CollidesWith(const Entity& other) const -{ - const auto& A = *this; - const auto& B = other; +bool Entity::CollidesWith(const Entity &other) const { + const auto &A = *this; + const auto &B = other; auto position_A = A.GetPosition(); auto position_B = B.GetPosition(); auto distance_sq = position_A.DistanceSquared(position_B); auto collision_distance_sq = - A.GetCollisionRadiusSquared() + - B.GetCollisionRadiusSquared() + + A.GetCollisionRadiusSquared() + B.GetCollisionRadiusSquared() + 2 * A.GetCollisionRadius() * B.GetCollisionRadius(); - if (distance_sq < collision_distance_sq) - { + if (distance_sq < collision_distance_sq) { return true; } return false; diff --git a/cpp/src/entities.hpp b/cpp/src/entities.hpp index 6ad8521..41cc754 100644 --- a/cpp/src/entities.hpp +++ b/cpp/src/entities.hpp @@ -4,13 +4,13 @@ #include #include #include -#include #include +#include #include "log.hpp" #include "math.hpp" -#include "sprite.hpp" #include "pathfinder/base.hpp" +#include "sprite.hpp" class Entity { public: @@ -58,12 +58,12 @@ public: void ZeroActualVelocityInDirection(WorldPos direction); - const pathfinder::Path& GetPath() const { return m_Path; } - pathfinder::Path& GetPath() { return m_Path; } - void SetPath(pathfinder::Path& path) { m_Path = path; } + const pathfinder::Path &GetPath() const { return m_Path; } + pathfinder::Path &GetPath() { return m_Path; } + void SetPath(pathfinder::Path &path) { m_Path = path; } std::optional GetMoveTarget(); - bool CollidesWith(const Entity& other) const; + bool CollidesWith(const Entity &other) const; bool IsCollisionBoxVisible() const { return m_CollisionBoxVisible; } diff --git a/cpp/src/gameloop.cpp b/cpp/src/gameloop.cpp index 7944493..8d7b9b0 100644 --- a/cpp/src/gameloop.cpp +++ b/cpp/src/gameloop.cpp @@ -21,18 +21,17 @@ void GameLoop::Draw() { TilePos{static_cast(row), static_cast(col)})); const auto &size = camera.WorldToWindowSize(map.GetTileSize()); // LOG_DEBUG("Drawing rect (", row, ", ", col, ")"); - m_Window->DrawFilledRect(position, size, tiles[row][col]->R, tiles[row][col]->G, - tiles[row][col]->B, tiles[row][col]->A); + m_Window->DrawFilledRect(position, size, tiles[row][col]->R, + tiles[row][col]->G, tiles[row][col]->B, + tiles[row][col]->A); } } // draw the path, if it exists - for (const auto& entity : m_Game->GetEntities()) - { + for (const auto &entity : m_Game->GetEntities()) { WorldPos start_pos = entity->GetPosition(); - for (const auto &next_pos : entity->GetPath()) - { + for (const auto &next_pos : entity->GetPath()) { const auto &camera = m_Game->GetCamera(); m_Window->DrawLine(camera.WorldToWindow(start_pos), camera.WorldToWindow(next_pos)); @@ -44,29 +43,24 @@ void GameLoop::Draw() { for (auto &entity : m_Game->GetEntities()) { const auto &camera = m_Game->GetCamera(); auto entity_pos = camera.WorldToWindow(entity->GetPosition()); - m_Window->DrawSprite(entity_pos, - entity->GetSprite(), - camera.GetZoom()); - if (entity->IsCollisionBoxVisible()) - { - float collision_radius = camera.WorldToWindowSize(entity->GetCollisionRadius()); + m_Window->DrawSprite(entity_pos, entity->GetSprite(), camera.GetZoom()); + if (entity->IsCollisionBoxVisible()) { + float collision_radius = + camera.WorldToWindowSize(entity->GetCollisionRadius()); m_Window->DrawCircle(entity_pos, collision_radius, 255, 0, 0); } - if (entity->IsSelected()) - { - float collision_radius = camera.WorldToWindowSize(entity->GetCollisionRadius()); + if (entity->IsSelected()) { + float collision_radius = + camera.WorldToWindowSize(entity->GetCollisionRadius()); m_Window->DrawCircle(entity_pos, collision_radius, 0, 255, 0); } } // draw the selection box, if present - if (m_Game->IsSelectionBoxActive()) - { - const auto& [corner_pos, size] = m_Game->GetSelectionBoxPosSize(); + if (m_Game->IsSelectionBoxActive()) { + const auto &[corner_pos, size] = m_Game->GetSelectionBoxPosSize(); m_Window->DrawRect(corner_pos, size, 200, 20, 20); } - - } // TODO rethink coupling and dependencies in the game loop class diff --git a/cpp/src/gameloop.hpp b/cpp/src/gameloop.hpp index 1e381ae..daca1ff 100644 --- a/cpp/src/gameloop.hpp +++ b/cpp/src/gameloop.hpp @@ -3,24 +3,22 @@ #include #include "pathfindingdemo.hpp" -#include "window.hpp" #include "user_input.hpp" +#include "window.hpp" class GameLoop { public: GameLoop() = default; ~GameLoop() = default; - GameLoop(const GameLoop&) = delete; - GameLoop(GameLoop&&) = delete; - GameLoop& operator=(const GameLoop&) = delete; - GameLoop& operator=(GameLoop&&) = delete; - + GameLoop(const GameLoop &) = delete; + GameLoop(GameLoop &&) = delete; + GameLoop &operator=(const GameLoop &) = delete; + GameLoop &operator=(GameLoop &&) = delete; + void Run(); - void SetGame(std::unique_ptr x) { - m_Game = std::move(x); - } + void SetGame(std::unique_ptr x) { m_Game = std::move(x); } void SetWindow(std::unique_ptr x) { m_Window = std::move(x); } void SetUserInput(std::unique_ptr x) { m_UserInput = std::move(x); diff --git a/cpp/src/log.hpp b/cpp/src/log.hpp index 2f1cb6d..d81323f 100644 --- a/cpp/src/log.hpp +++ b/cpp/src/log.hpp @@ -3,11 +3,11 @@ #include #if defined(__GNUC__) || defined(__clang__) -# define PRETTY_FUNC __PRETTY_FUNCTION__ +#define PRETTY_FUNC __PRETTY_FUNCTION__ #elif defined(_MSC_VER) -# define PRETTY_FUNC __FUNCTION__ +#define PRETTY_FUNC __FUNCTION__ #else -# define PRETTY_FUNC __func__ +#define PRETTY_FUNC __func__ #endif #define LOG_CRITICAL(...) Log::critical(PRETTY_FUNC, ": ", __VA_ARGS__) diff --git a/cpp/src/map.cpp b/cpp/src/map.cpp index 902880f..5f378cf 100644 --- a/cpp/src/map.cpp +++ b/cpp/src/map.cpp @@ -11,21 +11,22 @@ Map::Map(int rows, int cols) : m_Cols(cols), m_Rows(rows) { for (size_t row = 0; row < m_Rows; row++) { m_Tiles.push_back(std::vector{}); for (size_t col = 0; col < m_Cols; col++) { - m_Tiles[row].push_back(&tile_types.at(TileType::GRASS)); + m_Tiles[row].push_back(&tile_types.at(TileType::GRASS)); } } } WorldPos Map::TileToWorld(TilePos p) const { - return WorldPos{(p.x() + 0.5f) * TILE_SIZE, (p.y() + 0.5f) * TILE_SIZE}; + return WorldPos{(p.x() + 0.5f) * TILE_SIZE, (p.y() + 0.5f) * TILE_SIZE}; } WorldPos Map::TileEdgeToWorld(TilePos p) const { - return WorldPos{p.x() * TILE_SIZE, p.y() * TILE_SIZE}; + return WorldPos{p.x() * TILE_SIZE, p.y() * TILE_SIZE}; } TilePos Map::WorldToTile(WorldPos p) const { - return TilePos{static_cast(p.x() / TILE_SIZE), static_cast(p.y() / TILE_SIZE)}; + return TilePos{static_cast(p.x() / TILE_SIZE), + static_cast(p.y() / TILE_SIZE)}; } WorldSize Map::GetTileSize() const { return WorldSize{TILE_SIZE, TILE_SIZE}; } @@ -51,40 +52,38 @@ 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 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}, + center + TilePos{1, 0}, + center + TilePos{-1, 0}, + center + TilePos{0, 1}, + center + TilePos{0, -1}, }; - for (const auto& c : candidates) { + for (const auto &c : candidates) { if (IsTilePosValid(c)) - neighbours.push_back(c); + neighbours.push_back(c); } return neighbours; } - -void Map::PaintCircle(TilePos center, unsigned radius, TileType tile_type) -{ +void Map::PaintCircle(TilePos center, unsigned radius, TileType tile_type) { // get rectangle that wraps the circle - TilePos corner1 = TilePos{center.x() - static_cast(radius), center.y() - static_cast(radius)}; - TilePos corner2 = TilePos{center.x() + static_cast(radius), center.y() + static_cast(radius)}; + TilePos corner1 = TilePos{center.x() - static_cast(radius), + center.y() - static_cast(radius)}; + TilePos corner2 = TilePos{center.x() + static_cast(radius), + center.y() + static_cast(radius)}; // iterate through all valid points, setting the type - const unsigned radius_squared = radius * radius; + const unsigned radius_squared = radius * radius; for (int x = corner1.x(); x < corner2.x(); x++) { for (int y = corner1.y(); y < corner2.y(); y++) { - TilePos current_tile = {x, y}; - unsigned distance_squared = static_cast(center.DistanceTo(current_tile) * center.DistanceTo(current_tile)); - if (IsTilePosValid(current_tile) && distance_squared < radius_squared) - { + TilePos current_tile = {x, y}; + unsigned distance_squared = static_cast( + center.DistanceTo(current_tile) * center.DistanceTo(current_tile)); + if (IsTilePosValid(current_tile) && distance_squared < radius_squared) { // y is row, x is col m_Tiles[y][x] = &tile_types.at(tile_type); } @@ -92,10 +91,12 @@ void Map::PaintCircle(TilePos center, unsigned radius, TileType tile_type) } } -void Map::PaintLine(TilePos start_tile, TilePos stop_tile, double width, TileType tile_type) -{ - const vec start{static_cast(start_tile.x()), static_cast(start_tile.y())}; - const vec stop{static_cast(stop_tile.x()), static_cast(stop_tile.y())}; +void Map::PaintLine(TilePos start_tile, TilePos stop_tile, double width, + TileType tile_type) { + const vec start{static_cast(start_tile.x()), + static_cast(start_tile.y())}; + const vec stop{static_cast(stop_tile.x()), + static_cast(stop_tile.y())}; const double line_length = start.DistanceTo(stop); const vec step = (stop - start) / line_length; const vec ortho = step.GetOrthogonal(); @@ -104,7 +105,8 @@ void Map::PaintLine(TilePos start_tile, TilePos stop_tile, double width, TileTyp for (double t = 0; t < line_length; t += 1.0) { for (double ortho_t = 0; ortho_t < width; ortho_t += 0.1) { auto tile_pos = start + step * t + ortho * ortho_t; - TilePos tile_pos_int{static_cast(tile_pos.x()), static_cast(tile_pos.y())}; + TilePos tile_pos_int{static_cast(tile_pos.x()), + static_cast(tile_pos.y())}; if (IsTilePosValid(tile_pos_int)) { size_t row = static_cast(tile_pos.x()); size_t col = static_cast(tile_pos.y()); @@ -114,15 +116,14 @@ void Map::PaintLine(TilePos start_tile, TilePos stop_tile, double width, TileTyp } } - -void Map::PaintRectangle(TilePos first_corner, TilePos second_corner, TileType tile_type) -{ +void Map::PaintRectangle(TilePos first_corner, TilePos second_corner, + TileType tile_type) { std::initializer_list xvals = {first_corner.x(), second_corner.x()}; std::initializer_list yvals = {first_corner.y(), second_corner.y()}; for (int x = std::min(xvals); x < std::max(xvals); x++) { for (int y = std::min(yvals); y < std::max(yvals); y++) { - TilePos tile_pos{x,y}; - LOG_DEBUG("tile_pos = ", tile_pos); + TilePos tile_pos{x, y}; + LOG_DEBUG("tile_pos = ", tile_pos); if (IsTilePosValid(tile_pos)) { size_t row = static_cast(tile_pos.x()); size_t col = static_cast(tile_pos.y()); diff --git a/cpp/src/map.hpp b/cpp/src/map.hpp index 6b400f6..9b40dcb 100644 --- a/cpp/src/map.hpp +++ b/cpp/src/map.hpp @@ -14,10 +14,10 @@ public: Map(int rows, int cols); Map() : Map(0, 0) {} - Map(const Map&) = delete; - Map(Map&&) = delete; - Map& operator=(const Map&) = delete; - Map& operator=(Map&&) = delete; + Map(const Map &) = delete; + Map(Map &&) = delete; + Map &operator=(const Map &) = delete; + Map &operator=(Map &&) = delete; const TileGrid &GetMapTiles() const { return m_Tiles; } @@ -25,7 +25,7 @@ public: WorldPos TileToWorld(TilePos p) const; WorldPos TileEdgeToWorld(TilePos p) const; TilePos WorldToTile(WorldPos p) const; - + WorldSize GetTileSize() const; const Tile *GetTileAt(TilePos p) const; const Tile *GetTileAt(WorldPos p) const; @@ -35,7 +35,8 @@ public: // methods for drawing on the map void PaintCircle(TilePos center, unsigned radius, TileType tile_type); void PaintLine(TilePos start, TilePos stop, double width, TileType tile_type); - void PaintRectangle(TilePos first_corner, TilePos second_corner, TileType tile_type); + void PaintRectangle(TilePos first_corner, TilePos second_corner, + TileType tile_type); std::vector GetNeighbors(TilePos center) const; float GetCost(TilePos pos) const { return GetTileAt(pos)->cost; } diff --git a/cpp/src/math.hpp b/cpp/src/math.hpp index fea1c92..d496a5d 100644 --- a/cpp/src/math.hpp +++ b/cpp/src/math.hpp @@ -4,12 +4,12 @@ #include #include #include +#include #include #include #include #include #include -#include #ifdef _WIN32 #include @@ -39,8 +39,7 @@ struct Any {}; template class vec { // Friend declaration for move constructor from different tag types - template - friend class vec; + template friend class vec; public: vec() : m_Array{} {} @@ -51,8 +50,8 @@ public: vec(std::array array) : m_Array{array} {} - template - vec(vec&& other) : m_Array{std::move(other.m_Array)} {} + template + vec(vec &&other) : m_Array{std::move(other.m_Array)} {} // // Access to elements & data @@ -77,20 +76,20 @@ public: return os; } - std::array& Data() { return m_Array; } + std::array &Data() { return m_Array; } // // binary operators // friend bool operator==(const vec &a, const vec &b) - requires (std::is_integral_v) + requires(std::is_integral_v) { return std::ranges::equal(a.m_Array, b.m_Array); } friend bool operator==(const vec &a, const vec &b) - requires (std::is_floating_point_v) + requires(std::is_floating_point_v) { for (const auto &[u, v] : std::views::zip(a.m_Array, b.m_Array)) { if (!equalEpsilon(u, v)) { @@ -109,8 +108,7 @@ public: return c; } - friend vec operator+(const vec& a, T b) - { + friend vec operator+(const vec &a, T b) { vec c; std::ranges::transform(a.m_Array, std::views::repeat(b), c.m_Array.begin(), std::plus{}); @@ -124,8 +122,7 @@ public: return c; } - friend vec operator-(const vec& a, T b) - { + friend vec operator-(const vec &a, T b) { vec c; std::ranges::transform(a.m_Array, std::views::repeat(b), c.m_Array.begin(), std::minus{}); @@ -148,10 +145,10 @@ public: return c; } - friend vec operator/(const vec &a, const vec& b) { + friend vec operator/(const vec &a, const vec &b) { vec c; - std::ranges::transform(a.m_Array, b.m_Array, - c.m_Array.begin(), std::divides{}); + std::ranges::transform(a.m_Array, b.m_Array, c.m_Array.begin(), + std::divides{}); return c; } @@ -173,12 +170,12 @@ public: return a; } - vec& operator/=(float scalar) - { - vec& a = *this; + vec &operator/=(float scalar) { + vec &a = *this; auto b = std::views::repeat(scalar); std::ranges::transform(a.m_Array, b, a.m_Array.begin(), std::divides{}); - // TODO check all of this, could be done better with views instead of ranges? + // TODO check all of this, could be done better with views instead of + // ranges? return a; } @@ -203,7 +200,6 @@ public: return (a - b).LengthSquared(); } - // // In-place vector operations // @@ -237,16 +233,14 @@ public: return tmp; } - static T DotProduct(const vec& a, const vec& b) - { - return std::inner_product( - a.m_Array.begin(), a.m_Array.end(), b.m_Array.begin(), - T{}, std::plus{}, std::multiplies{}); + static T DotProduct(const vec &a, const vec &b) { + return std::inner_product(a.m_Array.begin(), a.m_Array.end(), + b.m_Array.begin(), T{}, std::plus{}, + std::multiplies{}); } - T DotProduct(const vec& b) const - { - const auto& a = *this; + T DotProduct(const vec &b) const { + const auto &a = *this; return DotProduct(a, b); } @@ -290,28 +284,24 @@ public: return m_Array[2]; } - template - vec ChangeTag() const & - { - return vec(m_Array); + template vec ChangeTag() const & { + return vec(m_Array); } - template - vec ChangeTag() && - { - return vec(std::move(*this)); + template vec ChangeTag() && { + return vec(std::move(*this)); } // Structured binding support for N == 2 - template - const T& get() const + template + const T &get() const requires(N == 2 && I < 2) { return m_Array[I]; } - template - T& get() + template + T &get() requires(N == 2 && I < 2) { return m_Array[I]; @@ -372,114 +362,101 @@ struct TilePosHash { // // Collumn major square matrix -template -class Matrix { +template class Matrix { -using vec_type = vec; + using vec_type = vec; public: - Matrix() = default; + Matrix() = default; - // Initialization using flat array of N*N elements - template - requires (M == N*N && std::same_as) - Matrix(std::array array) : m_Array{} - { - std::size_t idx = 0; - for (auto col : array | std::views::chunk(N)) - { - std::ranges::copy(col, m_Array[idx++].Data().begin()); - } + // Initialization using flat array of N*N elements + template + requires(M == N * N && std::same_as) + Matrix(std::array array) : m_Array{} { + std::size_t idx = 0; + for (auto col : array | std::views::chunk(N)) { + std::ranges::copy(col, m_Array[idx++].Data().begin()); } + } - const vec_type& operator[](size_t index) const { return m_Array[index]; } - vec_type& operator[](size_t index) { return m_Array[index]; } + const vec_type &operator[](size_t index) const { return m_Array[index]; } + vec_type &operator[](size_t index) { return m_Array[index]; } - friend std::ostream &operator<<(std::ostream &os, const Matrix &obj) - { - os << "( "; - for (const auto &element : obj.m_Array) { - os << element << " "; - } - os << ")"; - return os; + friend std::ostream &operator<<(std::ostream &os, const Matrix &obj) { + os << "( "; + for (const auto &element : obj.m_Array) { + os << element << " "; } + os << ")"; + return os; + } + friend Matrix operator+(const Matrix &A, const Matrix &B) { + Matrix C; + std::ranges::transform(A.m_Array, B.m_Array, C.m_Array.begin(), + std::plus{}); + return C; + } - friend Matrix operator+(const Matrix& A, const Matrix& B) - { - Matrix C; - std::ranges::transform(A.m_Array, B.m_Array, C.m_Array.begin(), std::plus{}); - return C; + friend Matrix operator-(const Matrix &A, const Matrix &B) { + Matrix C; + std::ranges::transform(A.m_Array, B.m_Array, C.m_Array.begin(), + std::minus{}); + return C; + } + + friend Matrix operator*(const Matrix &A, const Matrix &B) { + Matrix C; + + for (size_t i = 0; i < N; i++) { + for (size_t j = 0; j < N; j++) { + T sum = 0; + for (size_t k = 0; k < N; ++k) + sum += A[i][k] * B[k][j]; + C[i][j] = sum; + } } + return C; + } - friend Matrix operator-(const Matrix& A, const Matrix& B) - { - Matrix C; - std::ranges::transform(A.m_Array, B.m_Array, C.m_Array.begin(), std::minus{}); - return C; + friend vec_type operator*(const Matrix &A, const vec_type &b) { + // we assume that b is row vector + vec_type c; + for (size_t i = 0; i < N; i++) { + c[i] = b.DotProduct(A[i]); } + return c; + } - friend Matrix operator*(const Matrix& A, const Matrix& B) - { - Matrix C; - - for (size_t i = 0; i < N; i++) - { - for (size_t j = 0; j < N; j++) - { - T sum = 0; - for (size_t k = 0; k < N; ++k) sum += A[i][k] * B[k][j]; - C[i][j] = sum; - } - } - return C; + static constexpr Matrix Eye() { + Matrix E; + for (size_t i = 0; i < N; i++) { + E[i][i] = T{1}; } - - friend vec_type operator*(const Matrix& A, const vec_type& b) - { - // we assume that b is row vector - vec_type c; - for (size_t i = 0; i < N; i++) - { - c[i] = b.DotProduct(A[i]); - } - return c; - } - - static constexpr Matrix Eye() - { - Matrix E; - for (size_t i = 0; i < N; i++) - { - E[i][i] = T{1}; - } - return E; - } - + return E; + } private: - std::array m_Array; + std::array m_Array; }; // Structured binding support for vec namespace std { - template - struct tuple_size> : integral_constant {}; +template +struct tuple_size> : integral_constant {}; - template - struct tuple_element> { - using type = T; - }; -} +template +struct tuple_element> { + using type = T; +}; +} // namespace std // ADL-based get for structured bindings -template -const T& get(const vec& v) { +template +const T &get(const vec &v) { return v.template get(); } -template -T& get(vec& v) { +template T &get(vec &v) { return v.template get(); } diff --git a/cpp/src/pathfinder/base.cpp b/cpp/src/pathfinder/base.cpp index cccaf5b..9df1300 100644 --- a/cpp/src/pathfinder/base.cpp +++ b/cpp/src/pathfinder/base.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include #include "pathfinder/base.hpp" @@ -9,14 +9,15 @@ namespace pathfinder { -PathFinderBase::PathFinderBase(const Map* map) : m_Map(map) {} +PathFinderBase::PathFinderBase(const Map *map) : m_Map(map) {} // LinearPathFinder also lives here, since it is too small to get it's // own implementation file -Path LinearPathFinder::CalculatePath(WorldPos, WorldPos end) // first argument (start pos) not used +Path LinearPathFinder::CalculatePath( + WorldPos, WorldPos end) // first argument (start pos) not used { auto path = Path{end}; return path; } -} // pathfinder namespace +} // namespace pathfinder diff --git a/cpp/src/pathfinder/base.hpp b/cpp/src/pathfinder/base.hpp index 97ce18f..c9cb09c 100644 --- a/cpp/src/pathfinder/base.hpp +++ b/cpp/src/pathfinder/base.hpp @@ -1,11 +1,11 @@ #pragma once -#include #include #include +#include -#include "math.hpp" #include "map.hpp" +#include "math.hpp" namespace pathfinder { @@ -21,32 +21,30 @@ enum class PathFinderType { class PathFinderBase { public: - PathFinderBase(const Map* m); + PathFinderBase(const Map *m); ~PathFinderBase() = default; - PathFinderBase(const PathFinderBase&) = delete; - PathFinderBase(PathFinderBase&&) = delete; - PathFinderBase& operator=(const PathFinderBase&) = delete; - PathFinderBase& operator=(PathFinderBase&&) = delete; + PathFinderBase(const PathFinderBase &) = delete; + PathFinderBase(PathFinderBase &&) = delete; + PathFinderBase &operator=(const PathFinderBase &) = delete; + PathFinderBase &operator=(PathFinderBase &&) = delete; - virtual const std::string_view& GetName() const = 0; + virtual const std::string_view &GetName() const = 0; virtual Path CalculatePath(WorldPos start, WorldPos end) = 0; protected: - const Map* m_Map; + const Map *m_Map; }; - class LinearPathFinder : public PathFinderBase { public: - LinearPathFinder(const Map* m): PathFinderBase(m) {} + LinearPathFinder(const Map *m) : PathFinderBase(m) {} Path CalculatePath(WorldPos start, WorldPos end) override; - const std::string_view& GetName() const override { return m_Name; } + const std::string_view &GetName() const override { return m_Name; } private: const std::string_view m_Name = "Linear Path"; }; -} // pathfinder namespace - +} // namespace pathfinder diff --git a/cpp/src/pathfinder/bfs.cpp b/cpp/src/pathfinder/bfs.cpp index 747f410..8807021 100644 --- a/cpp/src/pathfinder/bfs.cpp +++ b/cpp/src/pathfinder/bfs.cpp @@ -7,60 +7,61 @@ #include "math.hpp" namespace pathfinder { - + Path BFS::CalculatePath(WorldPos start_world, WorldPos end_world) { - if (m_Map == nullptr) return {}; + if (m_Map == nullptr) + return {}; - const TilePos start = m_Map->WorldToTile(start_world); - const TilePos end = m_Map->WorldToTile(end_world); + 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(); + 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; + 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(); + // ---------------- 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; + 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; - } - } + if (next == end) { // early exit + early_exit = true; + break; } + } } + } - // --------------- reconstruct path ----------------- - if (m_CameFrom.find(end) == m_CameFrom.end()) - return {}; // end not reached + // --------------- reconstruct path ----------------- + if (m_CameFrom.find(end) == m_CameFrom.end()) + return {}; // end not reached - Path path; - TilePos cur = end; + 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)); - while (cur != start) { - cur = m_CameFrom[cur]; - path.push_back(m_Map->TileToWorld(cur)); - } - std::reverse(path.begin(), path.end()); - return path; + } + std::reverse(path.begin(), path.end()); + return path; } -} // pathfinder namespace +} // namespace pathfinder diff --git a/cpp/src/pathfinder/bfs.hpp b/cpp/src/pathfinder/bfs.hpp index f42124c..2d19b02 100644 --- a/cpp/src/pathfinder/bfs.hpp +++ b/cpp/src/pathfinder/bfs.hpp @@ -8,13 +8,13 @@ #include "math.hpp" namespace pathfinder { - -class BFS: public PathFinderBase { + +class BFS : public PathFinderBase { public: - BFS(const Map* m): PathFinderBase(m) {} + BFS(const Map *m) : PathFinderBase(m) {} Path CalculatePath(WorldPos start, WorldPos end) override; - const std::string_view& GetName() const override { return m_Name; } + const std::string_view &GetName() const override { return m_Name; } private: const std::string_view m_Name = "Breadth First Search"; @@ -22,4 +22,4 @@ private: std::unordered_map m_CameFrom; }; -} +} // namespace pathfinder diff --git a/cpp/src/pathfinder/dijkstra.cpp b/cpp/src/pathfinder/dijkstra.cpp index 4c7048f..4d13497 100644 --- a/cpp/src/pathfinder/dijkstra.cpp +++ b/cpp/src/pathfinder/dijkstra.cpp @@ -3,71 +3,69 @@ #include "dijkstra.hpp" #include "base.hpp" -#include "utils.hpp" -#include "math.hpp" #include "map.hpp" +#include "math.hpp" +#include "utils.hpp" namespace pathfinder { -Path Dijkstra::CalculatePath(WorldPos start_world, WorldPos end_world) -{ +Path Dijkstra::CalculatePath(WorldPos start_world, WorldPos end_world) { using QueueEntry = utils::QueueEntry; - if (!m_Map) return {}; + if (!m_Map) + return {}; const TilePos start = m_Map->WorldToTile(start_world); - const TilePos end = m_Map->WorldToTile(end_world); + const TilePos end = m_Map->WorldToTile(end_world); if (!m_Map->IsTilePosValid(start) || !m_Map->IsTilePosValid(end)) - return {}; - if (start == end) return {}; + return {}; + if (start == end) + return {}; // clear previous run m_CameFrom.clear(); m_Cost.clear(); - std::priority_queue, std::greater<>> frontier; + std::priority_queue, std::greater<>> + frontier; frontier.push({0.0f, start}); - m_CameFrom[start] = start; // sentinel - m_Cost[start] = 0.0f; + m_CameFrom[start] = start; // sentinel + m_Cost[start] = 0.0f; - while (!frontier.empty()) - { - const QueueEntry current = frontier.top(); - frontier.pop(); + while (!frontier.empty()) { + const QueueEntry current = frontier.top(); + frontier.pop(); - if (current.tile == end) // early exit - break; + if (current.tile == end) // early exit + break; - for (TilePos next : m_Map->GetNeighbors(current.tile)) - { - // cost of moving to neighbour (uniform 1.0 matches original BFS) - const float newCost = m_Cost[current.tile] + m_Map->GetCost(next); + for (TilePos next : m_Map->GetNeighbors(current.tile)) { + // cost of moving to neighbour (uniform 1.0 matches original BFS) + const float newCost = m_Cost[current.tile] + m_Map->GetCost(next); - if (!m_Cost.count(next) || newCost < m_Cost[next]) - { - m_Cost[next] = newCost; - m_CameFrom[next] = current.tile; - frontier.push({newCost, next}); - } + if (!m_Cost.count(next) || newCost < m_Cost[next]) { + m_Cost[next] = newCost; + m_CameFrom[next] = current.tile; + frontier.push({newCost, next}); } + } } // reconstruct path if (!m_CameFrom.count(end)) - return {}; // goal never reached + return {}; // goal never 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)); + while (cur != start) { + cur = m_CameFrom[cur]; + path.push_back(m_Map->TileToWorld(cur)); } std::reverse(path.begin(), path.end()); return path; } -} // pathfinder namespace +} // namespace pathfinder diff --git a/cpp/src/pathfinder/dijkstra.hpp b/cpp/src/pathfinder/dijkstra.hpp index 4d867c8..beeae81 100644 --- a/cpp/src/pathfinder/dijkstra.hpp +++ b/cpp/src/pathfinder/dijkstra.hpp @@ -10,12 +10,12 @@ namespace pathfinder { -class Dijkstra: public PathFinderBase { +class Dijkstra : public PathFinderBase { public: - Dijkstra(const Map* m): PathFinderBase(m) {} + Dijkstra(const Map *m) : PathFinderBase(m) {} Path CalculatePath(WorldPos start, WorldPos end) override; - const std::string_view& GetName() const override { return m_Name; } + const std::string_view &GetName() const override { return m_Name; } private: const std::string_view m_Name = "Dijkstra's Algorithm"; @@ -23,4 +23,4 @@ private: std::unordered_map m_CameFrom; }; -} // pathfinder namespace +} // namespace pathfinder diff --git a/cpp/src/pathfinder/gbfs.cpp b/cpp/src/pathfinder/gbfs.cpp index 02684ca..12e09c1 100644 --- a/cpp/src/pathfinder/gbfs.cpp +++ b/cpp/src/pathfinder/gbfs.cpp @@ -3,69 +3,67 @@ #include "gbfs.hpp" #include "base.hpp" -#include "math.hpp" #include "map.hpp" +#include "math.hpp" #include "pathfinder/utils.hpp" namespace pathfinder { -float GBFS::Heuristic(const TilePos& a, const TilePos& b) -{ - return static_cast(std::abs(a.x() - b.x()) + std::abs(a.y() - b.y())); +float GBFS::Heuristic(const TilePos &a, const TilePos &b) { + return static_cast(std::abs(a.x() - b.x()) + std::abs(a.y() - b.y())); } -Path GBFS::CalculatePath(WorldPos start_world, WorldPos end_world) -{ +Path GBFS::CalculatePath(WorldPos start_world, WorldPos end_world) { using QueueEntry = pathfinder::utils::QueueEntry; - if (!m_Map) return {}; + if (!m_Map) + return {}; const TilePos start = m_Map->WorldToTile(start_world); - const TilePos end = m_Map->WorldToTile(end_world); + const TilePos end = m_Map->WorldToTile(end_world); if (!m_Map->IsTilePosValid(start) || !m_Map->IsTilePosValid(end)) - return {}; - if (start == end) return {}; + return {}; + if (start == end) + return {}; m_CameFrom.clear(); - std::priority_queue, std::greater<>> frontier; + std::priority_queue, std::greater<>> + frontier; frontier.push({Heuristic(start, end), start}); - m_CameFrom[start] = start; // sentinel + m_CameFrom[start] = start; // sentinel - while (!frontier.empty()) - { - const QueueEntry current = frontier.top(); - frontier.pop(); + while (!frontier.empty()) { + const QueueEntry current = frontier.top(); + frontier.pop(); - if (current.tile == end) // early exit - break; + if (current.tile == end) // early exit + break; - for (TilePos next : m_Map->GetNeighbors(current.tile)) + for (TilePos next : m_Map->GetNeighbors(current.tile)) { + if (!m_CameFrom.count(next)) // not visited { - if (!m_CameFrom.count(next)) // not visited - { - m_CameFrom[next] = current.tile; - frontier.push({Heuristic(end, next), next}); - } + m_CameFrom[next] = current.tile; + frontier.push({Heuristic(end, next), next}); } + } } // reconstruct path if (!m_CameFrom.count(end)) - return {}; // goal never reached + return {}; // goal never 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)); + while (cur != start) { + cur = m_CameFrom[cur]; + path.push_back(m_Map->TileToWorld(cur)); } std::reverse(path.begin(), path.end()); return path; } -} // pathfinder namespace +} // namespace pathfinder diff --git a/cpp/src/pathfinder/gbfs.hpp b/cpp/src/pathfinder/gbfs.hpp index dc50585..29a7266 100644 --- a/cpp/src/pathfinder/gbfs.hpp +++ b/cpp/src/pathfinder/gbfs.hpp @@ -10,17 +10,17 @@ namespace pathfinder { -class GBFS: public PathFinderBase { +class GBFS : public PathFinderBase { public: - GBFS(const Map* m): PathFinderBase(m) {} + GBFS(const Map *m) : PathFinderBase(m) {} Path CalculatePath(WorldPos start, WorldPos end) override; - const std::string_view& GetName() const override { return m_Name; } + const std::string_view &GetName() const override { return m_Name; } private: - static float Heuristic(const TilePos& a, const TilePos& b); + static float Heuristic(const TilePos &a, const TilePos &b); const std::string_view m_Name = "Greedy Best First Search"; std::unordered_map m_CameFrom; }; -} // pathfinder namespace +} // namespace pathfinder diff --git a/cpp/src/pathfinder/utils.cpp b/cpp/src/pathfinder/utils.cpp index e2bc5a4..f4a0717 100644 --- a/cpp/src/pathfinder/utils.cpp +++ b/cpp/src/pathfinder/utils.cpp @@ -3,9 +3,9 @@ #include "utils.hpp" #include "base.hpp" +#include "log.hpp" #include "map.hpp" #include "math.hpp" -#include "log.hpp" #include "pathfinder/bfs.hpp" #include "pathfinder/dijkstra.hpp" #include "pathfinder/gbfs.hpp" @@ -13,23 +13,23 @@ namespace pathfinder { namespace utils { -std::unique_ptr create(PathFinderType type, const Map* map) { +std::unique_ptr create(PathFinderType type, const Map *map) { using namespace pathfinder; switch (type) { - case PathFinderType::LINEAR: - return std::move(std::make_unique(map)); - case PathFinderType::BFS: - return std::move(std::make_unique(map)); - case PathFinderType::DIJKSTRA: - return std::move(std::make_unique(map)); - case PathFinderType::GBFS: - return std::move(std::make_unique(map)); - case PathFinderType::COUNT: - LOG_WARNING("Incorrect pathfinder type"); - return nullptr; + case PathFinderType::LINEAR: + return std::move(std::make_unique(map)); + case PathFinderType::BFS: + return std::move(std::make_unique(map)); + case PathFinderType::DIJKSTRA: + return std::move(std::make_unique(map)); + case PathFinderType::GBFS: + return std::move(std::make_unique(map)); + case PathFinderType::COUNT: + LOG_WARNING("Incorrect pathfinder type"); + return nullptr; }; return nullptr; } -} // utils namespace -} // pathfinding namespace +} // namespace utils +} // namespace pathfinder diff --git a/cpp/src/pathfinder/utils.hpp b/cpp/src/pathfinder/utils.hpp index c40e4fb..cdf7821 100644 --- a/cpp/src/pathfinder/utils.hpp +++ b/cpp/src/pathfinder/utils.hpp @@ -10,16 +10,16 @@ namespace pathfinder { namespace utils { -struct QueueEntry -{ - float cost; - TilePos tile; +struct QueueEntry { + float cost; + TilePos tile; - // min-heap -> smallest cost on top - bool operator>(const QueueEntry& o) const noexcept { return cost > o.cost; } + // min-heap -> smallest cost on top + bool operator>(const QueueEntry &o) const noexcept { return cost > o.cost; } }; -std::unique_ptr create(pathfinder::PathFinderType type, const Map* map); +std::unique_ptr +create(pathfinder::PathFinderType type, const Map *map); -} // utils namespace -} // pathfinding namespace +} // namespace utils +} // namespace pathfinder diff --git a/cpp/src/pathfindingdemo.cpp b/cpp/src/pathfindingdemo.cpp index 7d8cd50..6e06b70 100644 --- a/cpp/src/pathfindingdemo.cpp +++ b/cpp/src/pathfindingdemo.cpp @@ -8,17 +8,16 @@ #include "entities.hpp" #include "log.hpp" #include "map.hpp" -#include "user_input.hpp" #include "pathfinder/base.hpp" #include "pathfinder/utils.hpp" #include "tile.hpp" +#include "user_input.hpp" -PathFindingDemo::PathFindingDemo(int width, int height) : - m_Map(width, height) -{ +PathFindingDemo::PathFindingDemo(int width, int height) : m_Map(width, height) { LOG_DEBUG("."); // set default pathfinder method - m_PathFinder = pathfinder::utils::create(pathfinder::PathFinderType::DIJKSTRA, (const Map*)&m_Map); + m_PathFinder = pathfinder::utils::create(pathfinder::PathFinderType::DIJKSTRA, + (const Map *)&m_Map); } PathFindingDemo::~PathFindingDemo() { LOG_DEBUG("."); } @@ -33,29 +32,29 @@ void PathFindingDemo::CreateMap() { m_Map.PaintCircle(TilePos{50, 50}, 10, TileType::WATER); m_Map.PaintCircle(TilePos{75, 100}, 50, TileType::WATER); // river - m_Map.PaintLine(TilePos{0,0}, TilePos{100,100}, 3.0, TileType::WATER); + m_Map.PaintLine(TilePos{0, 0}, TilePos{100, 100}, 3.0, TileType::WATER); // road - m_Map.PaintLine(TilePos{17,6}, TilePos{100,6}, 5.0, TileType::ROAD); - m_Map.PaintLine(TilePos{10,17}, TilePos{10,100}, 5.0, TileType::ROAD); - m_Map.PaintLine(TilePos{20,10}, TilePos{10,20}, 5.0, TileType::ROAD); + m_Map.PaintLine(TilePos{17, 6}, TilePos{100, 6}, 5.0, TileType::ROAD); + m_Map.PaintLine(TilePos{10, 17}, TilePos{10, 100}, 5.0, TileType::ROAD); + m_Map.PaintLine(TilePos{20, 10}, TilePos{10, 20}, 5.0, TileType::ROAD); // bridges - m_Map.PaintLine(TilePos{50,75}, TilePos{70,75}, 5.0, TileType::WOOD); - m_Map.PaintLine(TilePos{95,26}, TilePos{95,60}, 5.0, TileType::WOOD); + m_Map.PaintLine(TilePos{50, 75}, TilePos{70, 75}, 5.0, TileType::WOOD); + m_Map.PaintLine(TilePos{95, 26}, TilePos{95, 60}, 5.0, TileType::WOOD); // island - m_Map.PaintRectangle(TilePos{70, 60}, TilePos{100,100}, TileType::GRASS); + m_Map.PaintRectangle(TilePos{70, 60}, TilePos{100, 100}, TileType::GRASS); // walls - m_Map.PaintLine(TilePos{71,60}, TilePos{90,60}, 1.0, TileType::WALL); - m_Map.PaintLine(TilePos{77,67}, TilePos{100,67}, 1.0, TileType::WALL); - m_Map.PaintLine(TilePos{71,60}, TilePos{71,75}, 1.0, TileType::WALL); - m_Map.PaintLine(TilePos{72,73}, TilePos{95,73}, 1.0, TileType::WALL); - m_Map.PaintLine(TilePos{95,73}, TilePos{95,90}, 1.0, TileType::WALL); - m_Map.PaintLine(TilePos{71,81}, TilePos{71,100}, 1.0, TileType::WALL); - m_Map.PaintLine(TilePos{72,81}, TilePos{90,81}, 1.0, TileType::WALL); - m_Map.PaintLine(TilePos{89,87}, TilePos{89,100}, 1.0, TileType::WALL); - m_Map.PaintLine(TilePos{84,81}, TilePos{84,96}, 1.0, TileType::WALL); - m_Map.PaintLine(TilePos{78,87}, TilePos{78,100}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{71, 60}, TilePos{90, 60}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{77, 67}, TilePos{100, 67}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{71, 60}, TilePos{71, 75}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{72, 73}, TilePos{95, 73}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{95, 73}, TilePos{95, 90}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{71, 81}, TilePos{71, 100}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{72, 81}, TilePos{90, 81}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{89, 87}, TilePos{89, 100}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{84, 81}, TilePos{84, 96}, 1.0, TileType::WALL); + m_Map.PaintLine(TilePos{78, 87}, TilePos{78, 100}, 1.0, TileType::WALL); - // add some controllable entities + // add some controllable entities m_Entities.clear(); auto player = std::make_shared(); player->SetPosition(m_Map.TileToWorld(TilePos{25, 20})); @@ -65,19 +64,16 @@ void PathFindingDemo::CreateMap() { player2->SetPosition(m_Map.TileToWorld(TilePos{50, 20})); AddEntity(player2); - for (int i = 0; i < 1; i++) - { - for (int j = 0; j < 10; j++) - { + for (int i = 0; i < 1; i++) { + for (int j = 0; j < 10; j++) { auto p = std::make_shared(); - p->SetPosition(m_Map.TileToWorld(TilePos{10+5*i, 40+5*j})); + p->SetPosition(m_Map.TileToWorld(TilePos{10 + 5 * i, 40 + 5 * j})); AddEntity(p); } } // select everything - TODO this is just temporary for testing - for (const auto& entity : m_Entities) - { + for (const auto &entity : m_Entities) { m_SelectedEntities.push_back(entity); } } @@ -86,23 +82,17 @@ WorldPos PathFindingDemo::GetRandomPosition() const { return WorldPos{0.0f, 0.0f}; // totally random! } - - -const std::vector& PathFindingDemo::GetEntityCollisions() -{ +const std::vector &PathFindingDemo::GetEntityCollisions() { static std::vector m_Collisions; m_Collisions.clear(); - for (const auto &entity_A : m_Entities) - { - for (const auto &entity_B : m_Entities) - { + for (const auto &entity_A : m_Entities) { + for (const auto &entity_B : m_Entities) { if (entity_A == entity_B) continue; if (!entity_A->IsCollidable() || !entity_B->IsCollidable()) continue; - if (entity_A->CollidesWith(*entity_B)) - { + if (entity_A->CollidesWith(*entity_B)) { // handle collision logic m_Collisions.emplace_back(Collision(entity_A, entity_B)); } @@ -111,37 +101,33 @@ const std::vector& PathFindingDemo::GetEntityCollisions() return m_Collisions; } - // Update entity positions, handle collisions void PathFindingDemo::UpdateWorld() { - + float time_delta = 1.0f; - - for (auto& entity : m_Entities) - { + + for (auto &entity : m_Entities) { // calculate the velocity auto current_pos = entity->GetPosition(); double tile_velocity_coeff = m_Map.GetTileVelocityCoeff(current_pos); auto next_pos = entity->GetMoveTarget(); WorldPos velocity = WorldPos{}; - if (next_pos) - { + if (next_pos) { velocity = next_pos.value() - current_pos; velocity.Normalize(); - //LOG_DEBUG("I want to move to: ", next_pos.value(), - // ", velocity: ", velocity); + // LOG_DEBUG("I want to move to: ", next_pos.value(), + // ", velocity: ", velocity); } entity->SetActualVelocity(velocity * tile_velocity_coeff); - - for (const auto& collision : GetEntityCollisions()) - { - // TODO this loop is quite "hot", is it good idea to use weak_ptr and promote it? + + for (const auto &collision : GetEntityCollisions()) { + // TODO this loop is quite "hot", is it good idea to use weak_ptr and + // promote it? auto weak_A = std::get<0>(collision); auto weak_B = std::get<1>(collision); auto A = weak_A.lock(); auto B = weak_B.lock(); - if (A == nullptr || B == nullptr) - { + if (A == nullptr || B == nullptr) { continue; } if (!A->IsMovable()) @@ -152,65 +138,50 @@ void PathFindingDemo::UpdateWorld() { A->ZeroActualVelocityInDirection(AB); // handle logic // TODO - } - + } + // update the position entity->Update(time_delta); } } -void PathFindingDemo::HandleActions(const std::vector &actions) -{ - for (const auto &action : actions) - { - if (action.type == UserAction::Type::EXIT) - { +void PathFindingDemo::HandleActions(const std::vector &actions) { + for (const auto &action : actions) { + if (action.type == UserAction::Type::EXIT) { LOG_INFO("Exit requested"); m_ExitRequested = true; - } - else if (action.type == UserAction::Type::SET_MOVE_TARGET) - { + } else if (action.type == UserAction::Type::SET_MOVE_TARGET) { WorldPos target_pos = m_Camera.WindowToWorld(action.Argument.position); - for (auto& selected_entity : m_SelectedEntities) - { + for (auto &selected_entity : m_SelectedEntities) { LOG_INFO("Calculating path to target: ", target_pos); - if (auto sp = selected_entity.lock()) - { - auto path = m_PathFinder->CalculatePath(sp->GetPosition(), target_pos); + if (auto sp = selected_entity.lock()) { + auto path = + m_PathFinder->CalculatePath(sp->GetPosition(), target_pos); sp->SetPath(path); LOG_INFO("Done, path node count: ", path.size()); } else { - LOG_INFO("Cannot calculate path for destroyed entity (weak_ptr.lock() failed)"); + LOG_INFO("Cannot calculate path for destroyed entity " + "(weak_ptr.lock() failed)"); } } - } - else if (action.type == UserAction::Type::SELECT_PATHFINDER) - { + } else if (action.type == UserAction::Type::SELECT_PATHFINDER) { using namespace pathfinder; PathFinderType type = static_cast(action.Argument.number); - m_PathFinder = pathfinder::utils::create(type, (const Map*)&m_Map); + 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; + } 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); - } - else if (action.type == UserAction::Type::CAMERA_ZOOM) - { + } else if (action.type == UserAction::Type::CAMERA_ZOOM) { m_Camera.Zoom(action.Argument.float_number); LOG_INFO("Camera zoom: ", action.Argument.float_number); - } - else if (action.type == UserAction::Type::SELECTION_START) - { + } else if (action.type == UserAction::Type::SELECTION_START) { m_SelectionBox.active = true; m_SelectionBox.start = action.Argument.position; m_SelectionBox.end = action.Argument.position; - } - else if (action.type == UserAction::Type::SELECTION_END) - { + } else if (action.type == UserAction::Type::SELECTION_END) { m_SelectionBox.end = action.Argument.position; m_SelectionBox.active = false; auto diff = m_SelectionBox.end - m_SelectionBox.start; @@ -219,9 +190,7 @@ void PathFindingDemo::HandleActions(const std::vector &actions) WorldPos start = m_Camera.WindowToWorld(m_SelectionBox.start); WorldPos end = m_Camera.WindowToWorld(m_SelectionBox.end); SelectEntitiesInRectangle(start, end); - } - else if (action.type == UserAction::Type::SELECTION_CHANGE) - { + } else if (action.type == UserAction::Type::SELECTION_CHANGE) { m_SelectionBox.end = action.Argument.position; auto diff = m_SelectionBox.end - m_SelectionBox.start; m_SelectionBox.size = diff.ChangeTag(); @@ -229,30 +198,25 @@ void PathFindingDemo::HandleActions(const std::vector &actions) }; } -void PathFindingDemo::DeselectEntities() -{ - std::for_each(m_SelectedEntities.begin(), m_SelectedEntities.end(), [](auto& x) - { - if (auto entity = x.lock()) - entity->Deselect(); - } - ); +void PathFindingDemo::DeselectEntities() { + std::for_each(m_SelectedEntities.begin(), m_SelectedEntities.end(), + [](auto &x) { + if (auto entity = x.lock()) + entity->Deselect(); + }); m_SelectedEntities.clear(); } -void PathFindingDemo::SelectEntitiesInRectangle(WorldPos A, WorldPos B) -{ +void PathFindingDemo::SelectEntitiesInRectangle(WorldPos A, WorldPos B) { DeselectEntities(); // TODO use colliders for this auto [x_min, x_max] = std::minmax(A.x(), B.x()); auto [y_min, y_max] = std::minmax(A.y(), B.y()); - for (const auto& entity : m_Entities) - { - const auto& pos = entity->GetPosition(); + for (const auto &entity : m_Entities) { + const auto &pos = entity->GetPosition(); bool x_in_range = x_min < pos.x() && pos.x() < x_max; bool y_in_range = y_min < pos.y() && pos.y() < y_max; - if (x_in_range && y_in_range) - { + if (x_in_range && y_in_range) { m_SelectedEntities.push_back(std::weak_ptr(entity)); entity->Select(); } @@ -260,12 +224,9 @@ void PathFindingDemo::SelectEntitiesInRectangle(WorldPos A, WorldPos B) LOG_INFO("Selected ", m_SelectedEntities.size(), " entities"); } -std::pair PathFindingDemo::GetSelectionBoxPosSize() -{ - const auto& pos = m_SelectionBox.start; +std::pair PathFindingDemo::GetSelectionBoxPosSize() { + const auto &pos = m_SelectionBox.start; WindowPos size_pos = m_SelectionBox.end - m_SelectionBox.start; WindowSize size = size_pos.ChangeTag(); return std::pair(pos, size); - } - diff --git a/cpp/src/pathfindingdemo.hpp b/cpp/src/pathfindingdemo.hpp index 2f3eb4c..9a96020 100644 --- a/cpp/src/pathfindingdemo.hpp +++ b/cpp/src/pathfindingdemo.hpp @@ -5,17 +5,16 @@ #include #include +#include "camera.hpp" #include "entities.hpp" #include "log.hpp" #include "map.hpp" -#include "user_input.hpp" #include "pathfinder/base.hpp" -#include "camera.hpp" +#include "user_input.hpp" using Collision = std::pair, std::weak_ptr>; -struct SelectionBox -{ +struct SelectionBox { WindowPos start, end; WindowSize size; bool active; @@ -31,9 +30,9 @@ public: PathFindingDemo &operator=(const PathFindingDemo &) = delete; PathFindingDemo &operator=(PathFindingDemo &&) = delete; - std::vector>& GetEntities() { return m_Entities; } - const Map& GetMap() const { return m_Map; } - const Camera& GetCamera() const { return m_Camera; } + std::vector> &GetEntities() { return m_Entities; } + const Map &GetMap() const { return m_Map; } + const Camera &GetCamera() const { return m_Camera; } bool IsExitRequested() const { return m_ExitRequested; } void AddEntity(std::shared_ptr e); @@ -46,10 +45,12 @@ public: void DeselectEntities(); bool IsSelectionBoxActive() const { return m_SelectionBox.active; } std::pair GetSelectionBoxPosSize(); - std::vector> GetSelectedEntities() { return m_SelectedEntities; } + std::vector> GetSelectedEntities() { + return m_SelectedEntities; + } private: - const std::vector& GetEntityCollisions(); + const std::vector &GetEntityCollisions(); bool m_ExitRequested = false; Map m_Map; diff --git a/cpp/src/tile.cpp b/cpp/src/tile.cpp index e8d5aa0..836c0fc 100644 --- a/cpp/src/tile.cpp +++ b/cpp/src/tile.cpp @@ -6,9 +6,9 @@ // we could use array here, but this is more explicit, // and we don't access tile_types that often, so it should be ok const std::unordered_map tile_types = { - { TileType::GRASS, Tile{1.0, 0, 200, 0, 255}}, - { TileType::WOOD, Tile{1.0, 132, 68, 0, 255}}, - { TileType::ROAD, Tile{0.5, 20, 20, 20, 255}}, - { TileType::WATER, Tile{10.0, 0, 50, 200, 255}}, - { TileType::WALL, Tile{1000.0, 144, 33, 0, 255}}, + {TileType::GRASS, Tile{1.0, 0, 200, 0, 255}}, + {TileType::WOOD, Tile{1.0, 132, 68, 0, 255}}, + {TileType::ROAD, Tile{0.5, 20, 20, 20, 255}}, + {TileType::WATER, Tile{10.0, 0, 50, 200, 255}}, + {TileType::WALL, Tile{1000.0, 144, 33, 0, 255}}, }; diff --git a/cpp/src/tile.hpp b/cpp/src/tile.hpp index c80f931..be97665 100644 --- a/cpp/src/tile.hpp +++ b/cpp/src/tile.hpp @@ -1,9 +1,9 @@ #pragma once +#include #include #include #include -#include #include struct Tile { diff --git a/cpp/src/user_input.cpp b/cpp/src/user_input.cpp index ae9895f..a46139b 100644 --- a/cpp/src/user_input.cpp +++ b/cpp/src/user_input.cpp @@ -2,8 +2,8 @@ #include #include #include -#include #include +#include #include "user_input.hpp" @@ -20,130 +20,103 @@ UserInput::~UserInput() { LOG_DEBUG("."); }; std::expected UserInput::Init() { return {}; } -void UserInput::GetActions_mouse(const SDL_Event& event) -{ +void UserInput::GetActions_mouse(const SDL_Event &event) { static bool mouse_pan = false; SDL_MouseButtonEvent mouse_event = event.button; MouseButton button = static_cast(mouse_event.button); - if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) - { - if (button == MouseButton::LEFT) - { + if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { + if (button == MouseButton::LEFT) { LOG_DEBUG("Selection start at ", mouse_event.x, ", ", mouse_event.y); m_SelectionActive = true; m_Actions.emplace_back(UserAction::Type::SELECTION_START, WindowPos{mouse_event.x, mouse_event.y}); - } - else if (button == MouseButton::RIGHT) - { + } else if (button == MouseButton::RIGHT) { LOG_DEBUG("Set move target to: ", mouse_event.x, ", ", mouse_event.y); m_Actions.emplace_back(UserAction::Type::SET_MOVE_TARGET, WindowPos{mouse_event.x, mouse_event.y}); - } - else if (button == MouseButton::MIDDLE) - { + } else if (button == MouseButton::MIDDLE) { mouse_pan = true; } - } - else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) - { - if (button == MouseButton::LEFT) - { + } else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP) { + if (button == MouseButton::LEFT) { LOG_DEBUG("Selection end at ", mouse_event.x, ", ", mouse_event.y); m_SelectionActive = false; m_Actions.emplace_back(UserAction::Type::SELECTION_END, WindowPos{mouse_event.x, mouse_event.y}); } - if (button == MouseButton::MIDDLE) - { + if (button == MouseButton::MIDDLE) { mouse_pan = false; } - } - else if (event.type == SDL_EVENT_MOUSE_MOTION) - { + } else if (event.type == SDL_EVENT_MOUSE_MOTION) { SDL_MouseMotionEvent motion_event = event.motion; - if (mouse_pan) - { + if (mouse_pan) { m_Actions.emplace_back(UserAction::Type::CAMERA_PAN, - WindowPos{motion_event.xrel, motion_event.yrel}); + WindowPos{motion_event.xrel, motion_event.yrel}); } - if (m_SelectionActive) - { + if (m_SelectionActive) { m_Actions.emplace_back(UserAction::Type::SELECTION_CHANGE, WindowPos{mouse_event.x, mouse_event.y}); } - } - else if(event.type == SDL_EVENT_MOUSE_WHEEL) - { + } else if (event.type == SDL_EVENT_MOUSE_WHEEL) { SDL_MouseWheelEvent mouse_wheel = event.wheel; m_Actions.emplace_back(UserAction::Type::CAMERA_ZOOM, mouse_wheel.y); } } -void UserInput::GetActions_keyboard(const SDL_Event& event) -{ - bool key_down = event.type == SDL_EVENT_KEY_DOWN ? true : false; - SDL_KeyboardEvent kbd_event = event.key; - if (kbd_event.repeat) { - // SDL repeats KEY_DOWN if key is held down, we ignore that - return; - } - LOG_DEBUG("Key '", static_cast(kbd_event.key), - key_down ? "' down" : "' up"); +void UserInput::GetActions_keyboard(const SDL_Event &event) { + bool key_down = event.type == SDL_EVENT_KEY_DOWN ? true : false; + SDL_KeyboardEvent kbd_event = event.key; + if (kbd_event.repeat) { + // SDL repeats KEY_DOWN if key is held down, we ignore that + return; + } + LOG_DEBUG("Key '", static_cast(kbd_event.key), + key_down ? "' down" : "' up"); - switch (kbd_event.key) { - case 'q': - m_Actions.emplace_back(UserAction::Type::EXIT); - return; - case '1': - case '2': - case '3': - case '4': - if (key_down) { - int selection = kbd_event.key - '0'; - m_Actions.emplace_back(UserAction::Type::SELECT_PATHFINDER, selection); - LOG_INFO("Pathfinder selected: ", selection); - } - break; - default: - LOG_INFO("Key '", static_cast(kbd_event.key), "' not mapped"); - break; - } + switch (kbd_event.key) { + case 'q': + m_Actions.emplace_back(UserAction::Type::EXIT); + return; + case '1': + case '2': + case '3': + case '4': + if (key_down) { + int selection = kbd_event.key - '0'; + m_Actions.emplace_back(UserAction::Type::SELECT_PATHFINDER, selection); + LOG_INFO("Pathfinder selected: ", selection); + } + break; + default: + LOG_INFO("Key '", static_cast(kbd_event.key), "' not mapped"); + break; + } } -const std::vector& UserInput::GetActions() { +const std::vector &UserInput::GetActions() { static std::unordered_set mouse_events = { - SDL_EVENT_MOUSE_MOTION, - SDL_EVENT_MOUSE_BUTTON_DOWN, - SDL_EVENT_MOUSE_BUTTON_UP, - SDL_EVENT_MOUSE_WHEEL, - SDL_EVENT_MOUSE_ADDED, - SDL_EVENT_MOUSE_REMOVED, + SDL_EVENT_MOUSE_MOTION, SDL_EVENT_MOUSE_BUTTON_DOWN, + SDL_EVENT_MOUSE_BUTTON_UP, SDL_EVENT_MOUSE_WHEEL, + SDL_EVENT_MOUSE_ADDED, SDL_EVENT_MOUSE_REMOVED, }; - + static std::unordered_set keyboard_events = { - SDL_EVENT_KEY_DOWN, - SDL_EVENT_KEY_UP, + SDL_EVENT_KEY_DOWN, + SDL_EVENT_KEY_UP, }; - + SDL_Event event; m_Actions.clear(); - while (SDL_PollEvent(&event)) - { - if (keyboard_events.contains(event.type)) - { + while (SDL_PollEvent(&event)) { + if (keyboard_events.contains(event.type)) { GetActions_keyboard(event); - } - else if (mouse_events.contains(event.type)) - { - GetActions_mouse(event); - } - else - { + } else if (mouse_events.contains(event.type)) { + GetActions_mouse(event); + } 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 dee9231..630d470 100644 --- a/cpp/src/user_input.hpp +++ b/cpp/src/user_input.hpp @@ -7,14 +7,12 @@ #include "log.hpp" #include "math.hpp" -// Seems like SDL doesn't have named constants for mouse button +// Seems like SDL doesn't have named constants for mouse button enum class MouseButton { LEFT = 1, MIDDLE, RIGHT }; class UserAction { public: - - enum class Type - { + enum class Type { NONE, EXIT, SET_MOVE_TARGET, @@ -44,7 +42,7 @@ public: } Argument; // TODO use std::variant - //std::variant Argument; + // std::variant Argument; }; class UserInput { @@ -65,6 +63,6 @@ private: std::vector m_Actions; bool m_SelectionActive = false; - void GetActions_keyboard(const SDL_Event&); - void GetActions_mouse(const SDL_Event&); + void GetActions_keyboard(const SDL_Event &); + void GetActions_mouse(const SDL_Event &); }; diff --git a/cpp/src/window.cpp b/cpp/src/window.cpp index f92bfcc..49ad415 100644 --- a/cpp/src/window.cpp +++ b/cpp/src/window.cpp @@ -79,19 +79,20 @@ Window::~Window() { void Window::DrawSprite(const WindowPos &position, Sprite &s, float scale) { WorldSize size = s.GetSize() * scale; WorldPos img_center = s.GetCenter() * scale; - SDL_FRect rect = {position.x() - img_center.x(), position.y() - img_center.y(), - size.x(), size.y()}; + SDL_FRect rect = {position.x() - img_center.x(), + position.y() - img_center.y(), size.x(), size.y()}; SDL_RenderTexture(m_Renderer.get(), s.GetTexture(), nullptr, &rect); } -void Window::DrawFilledRect(const WindowPos &position, const WindowSize size, uint8_t R, - uint8_t G, uint8_t B, uint8_t A) { +void Window::DrawFilledRect(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); SDL_RenderFillRect(m_Renderer.get(), &rect); } -void Window::DrawRect(const WindowPos &position, const WindowSize size, uint8_t R, uint8_t G, uint8_t B) { +void Window::DrawRect(const WindowPos &position, const WindowSize size, + uint8_t R, uint8_t G, uint8_t B) { SDL_FRect rect = {position.x(), position.y(), size.x(), size.y()}; SDL_SetRenderDrawColor(m_Renderer.get(), R, G, B, 255); SDL_RenderRect(m_Renderer.get(), &rect); @@ -105,7 +106,8 @@ void Window::ClearWindow() { void Window::Flush() { SDL_RenderPresent(m_Renderer.get()); } // TODO use some struct for color -void Window::DrawCircle(const WindowPos &position, float radius, uint8_t R, uint8_t G, uint8_t B) { +void Window::DrawCircle(const WindowPos &position, float radius, uint8_t R, + uint8_t G, uint8_t B) { int cx = static_cast(position.x()); int cy = static_cast(position.y()); SDL_SetRenderDrawColor(m_Renderer.get(), R, G, B, 255); @@ -117,9 +119,7 @@ void Window::DrawCircle(const WindowPos &position, float radius, uint8_t R, uint } } -void Window::DrawLine(const WindowPos &A, const WindowPos &B) -{ +void Window::DrawLine(const WindowPos &A, const WindowPos &B) { SDL_SetRenderDrawColor(m_Renderer.get(), 255, 0, 0, 255); SDL_RenderLine(m_Renderer.get(), A.x(), A.y(), B.x(), B.y()); } - diff --git a/cpp/src/window.hpp b/cpp/src/window.hpp index 0729bd4..dec8d15 100644 --- a/cpp/src/window.hpp +++ b/cpp/src/window.hpp @@ -23,12 +23,14 @@ public: std::expected Init(); void DrawSprite(const WindowPos &position, Sprite &s, float scale = 1.0f); - void DrawFilledRect(const WindowPos &position, const WindowSize size, uint8_t R, - uint8_t G, uint8_t B, uint8_t A); - void DrawRect(const WindowPos &position, const WindowSize size, uint8_t R, uint8_t G, uint8_t B); + void DrawFilledRect(const WindowPos &position, const WindowSize size, + uint8_t R, uint8_t G, uint8_t B, uint8_t A); + void DrawRect(const WindowPos &position, const WindowSize size, uint8_t R, + uint8_t G, uint8_t B); void ClearWindow(); void Flush(); - void DrawCircle(const WindowPos &position, float radius, uint8_t R, uint8_t G, uint8_t B); + void DrawCircle(const WindowPos &position, float radius, uint8_t R, uint8_t G, + uint8_t B); void DrawLine(const WindowPos &A, const WindowPos &B); private: @@ -37,5 +39,4 @@ private: std::shared_ptr m_Renderer = nullptr; SDL_Window *m_Window; SDL_GLContext m_Context; - }; From 434f80809564482835fc8ac816c536667c1b4547 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Thu, 30 Oct 2025 15:18:41 +0100 Subject: [PATCH 18/23] Added sample file for git hook --- scripts/pre-commit.sample | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 scripts/pre-commit.sample diff --git a/scripts/pre-commit.sample b/scripts/pre-commit.sample new file mode 100644 index 0000000..535c259 --- /dev/null +++ b/scripts/pre-commit.sample @@ -0,0 +1,9 @@ +#!/bin/sh + +# Copy this file to .git/hooks + +# Run clang-format before commiting +files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|hpp)$') +[ -z "$files" ] && exit 0 +clang-format -i $files +git add $files From 69d08e5310619cacd0d7855e2adcd6bfe22384e4 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 31 Oct 2025 06:06:46 +0100 Subject: [PATCH 19/23] Added Clang-tidy to Linux build --- CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c26e04..997d225 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,9 @@ else() set(SDL3_TARGET ${SDL3_LIBRARIES}) set(SDL3_IMAGE_TARGET ${SDL3_image_LIBRARIES}) set(GLEW_TARGET GLEW::GLEW) + + # Enable clang-tidy + set(CMAKE_CXX_CLANG_TIDY "clang-tidy") endif() # Include directories @@ -188,13 +191,13 @@ else() # GCC/Clang flags with extended debugging symbols set(CMAKE_CXX_FLAGS_DEBUG "-ggdb3 -O0") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") + # Formatting target (clang only) + add_custom_target(format + COMMAND clang-format -i ${MAIN_SOURCES} ${HEADERS} +) endif() list(TRANSFORM MAIN_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/") list(TRANSFORM HEADERS PREPEND "${CMAKE_SOURCE_DIR}/") -# Formatting target (clang only) -add_custom_target(format - COMMAND clang-format -i ${MAIN_SOURCES} ${HEADERS} -) \ No newline at end of file From 30eecc366eba1b0a68764f564ca19a771f82e902 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 31 Oct 2025 07:22:26 +0100 Subject: [PATCH 20/23] Fixed clang-tidy warnings --- cpp/src/map.cpp | 2 ++ cpp/src/pathfinder/base.hpp | 4 ++-- cpp/src/pathfinder/bfs.hpp | 2 +- cpp/src/pathfinder/dijkstra.hpp | 2 +- cpp/src/pathfinder/gbfs.hpp | 2 +- cpp/src/pathfinder/utils.cpp | 8 ++++---- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cpp/src/map.cpp b/cpp/src/map.cpp index 5f378cf..94b375a 100644 --- a/cpp/src/map.cpp +++ b/cpp/src/map.cpp @@ -102,7 +102,9 @@ void Map::PaintLine(TilePos start_tile, TilePos stop_tile, double width, const vec ortho = step.GetOrthogonal(); LOG_DEBUG("step = ", step, " ortho = ", ortho); + // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter) for (double t = 0; t < line_length; t += 1.0) { + // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter) for (double ortho_t = 0; ortho_t < width; ortho_t += 0.1) { auto tile_pos = start + step * t + ortho * ortho_t; TilePos tile_pos_int{static_cast(tile_pos.x()), diff --git a/cpp/src/pathfinder/base.hpp b/cpp/src/pathfinder/base.hpp index c9cb09c..da7d994 100644 --- a/cpp/src/pathfinder/base.hpp +++ b/cpp/src/pathfinder/base.hpp @@ -22,7 +22,7 @@ enum class PathFinderType { class PathFinderBase { public: PathFinderBase(const Map *m); - ~PathFinderBase() = default; + virtual ~PathFinderBase() = default; PathFinderBase(const PathFinderBase &) = delete; PathFinderBase(PathFinderBase &&) = delete; @@ -36,7 +36,7 @@ protected: const Map *m_Map; }; -class LinearPathFinder : public PathFinderBase { +class LinearPathFinder final : public PathFinderBase { public: LinearPathFinder(const Map *m) : PathFinderBase(m) {} diff --git a/cpp/src/pathfinder/bfs.hpp b/cpp/src/pathfinder/bfs.hpp index 2d19b02..b8ad6b4 100644 --- a/cpp/src/pathfinder/bfs.hpp +++ b/cpp/src/pathfinder/bfs.hpp @@ -9,7 +9,7 @@ namespace pathfinder { -class BFS : public PathFinderBase { +class BFS final : public PathFinderBase { public: BFS(const Map *m) : PathFinderBase(m) {} diff --git a/cpp/src/pathfinder/dijkstra.hpp b/cpp/src/pathfinder/dijkstra.hpp index beeae81..9152f2e 100644 --- a/cpp/src/pathfinder/dijkstra.hpp +++ b/cpp/src/pathfinder/dijkstra.hpp @@ -10,7 +10,7 @@ namespace pathfinder { -class Dijkstra : public PathFinderBase { +class Dijkstra final : public PathFinderBase { public: Dijkstra(const Map *m) : PathFinderBase(m) {} diff --git a/cpp/src/pathfinder/gbfs.hpp b/cpp/src/pathfinder/gbfs.hpp index 29a7266..e9ca849 100644 --- a/cpp/src/pathfinder/gbfs.hpp +++ b/cpp/src/pathfinder/gbfs.hpp @@ -10,7 +10,7 @@ namespace pathfinder { -class GBFS : public PathFinderBase { +class GBFS final : public PathFinderBase { public: GBFS(const Map *m) : PathFinderBase(m) {} diff --git a/cpp/src/pathfinder/utils.cpp b/cpp/src/pathfinder/utils.cpp index f4a0717..3de5272 100644 --- a/cpp/src/pathfinder/utils.cpp +++ b/cpp/src/pathfinder/utils.cpp @@ -17,13 +17,13 @@ std::unique_ptr create(PathFinderType type, const Map *map) { using namespace pathfinder; switch (type) { case PathFinderType::LINEAR: - return std::move(std::make_unique(map)); + return std::make_unique(map); case PathFinderType::BFS: - return std::move(std::make_unique(map)); + return std::make_unique(map); case PathFinderType::DIJKSTRA: - return std::move(std::make_unique(map)); + return std::make_unique(map); case PathFinderType::GBFS: - return std::move(std::make_unique(map)); + return std::make_unique(map); case PathFinderType::COUNT: LOG_WARNING("Incorrect pathfinder type"); return nullptr; From 2f1c285a9342ed88c84b930d711caaaa86474c28 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 31 Oct 2025 17:24:43 +0100 Subject: [PATCH 21/23] Added clang-uml config file --- .clang-uml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .clang-uml diff --git a/.clang-uml b/.clang-uml new file mode 100644 index 0000000..6d130e2 --- /dev/null +++ b/.clang-uml @@ -0,0 +1,26 @@ +# When configuring with cmake, use: +# cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug +compilation_database_dir: build + +# Path to system header needs to be specified, +# you will probably need to modify this +add_compile_flags: + - '-I/usr/lib/clang/21/include' + +output_directory: docs/diagrams +diagrams: + class_diagram: + type: class + glob: + - cpp/src/**/*.cpp + - cpp/src/**/*.hpp + include_diagram: + type: include + glob: + - cpp/src/**/*.cpp + # Include also external system headers + #generate_system_headers: true + include: + # Include only files belonging to these paths + paths: + - cpp/src From a42d7c578d24acf6743f4261388618dab97d2450 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 31 Oct 2025 17:29:58 +0100 Subject: [PATCH 22/23] Added instructions for diagram generation --- README.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7bf1b17..10dc568 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ ![C++ pathfinding demo](./docs/img/screenshot_1.png) -**Work in progress** - This is a demo of pathfinding on a 2D grid. It consists of 2 main parts: * python notes and implementation @@ -52,13 +50,21 @@ TODO * SDL3 * SDL3-image * GLEW +* gtest e.g. on Archlinux: ``` -pacman -S glew sdl3 sdl3_image +pacman -S glew sdl3 sdl3_image gtest ``` +Optional dependencies for generating class and include diagrams: + +* plantuml +* [clang-uml](https://github.com/bkryza/clang-uml) + +clang-uml needs to be either installed from [AUR](https://aur.archlinux.org/packages/clang-uml) or built manually + #### Build ```bash @@ -68,12 +74,22 @@ cmake --build build -j 16 Optionally you can also use options: -* `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to enable compile database export +* `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to enable compile database export (needed for class diagram generation) * `-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++` to use clang ``` cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=O -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -`````` +``` Run the `pathfinding` binary in the `build` folder. +#### Generate architecture diagrams + +Build with `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON`. Then in the root folder run: + +``` +clang-uml +plantuml -tsvg docs/diagrams/*.puml +``` + +The resulting svg files are located in [docs/diagrams/](./docs/diagrams/). From 9b4b852872d4728f19b20460a8dd41e91c7e5edc Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Fri, 31 Oct 2025 17:30:38 +0100 Subject: [PATCH 23/23] Added diagrams --- docs/diagrams/class_diagram.svg | 1 + docs/diagrams/include_diagram.svg | 1 + 2 files changed, 2 insertions(+) create mode 100644 docs/diagrams/class_diagram.svg create mode 100644 docs/diagrams/include_diagram.svg diff --git a/docs/diagrams/class_diagram.svg b/docs/diagrams/class_diagram.svg new file mode 100644 index 0000000..4a5221d --- /dev/null +++ b/docs/diagrams/class_diagram.svg @@ -0,0 +1 @@ +std::arrayT,Nstd::arrayTarr,Mstd::arrayvec<T,N,Tag>,Nstd::integral_constantstd::size_t,2std::tuple_sizestd::tuple_sizevec<T,2,Tag>std::tuple_elementstd::tuple_elementI,vec<T,2,Tag>std::vectorTile const*std::vectorstd::vector<Tile const*>std::vectorvec<int32_t,2,TilePosTag>std::basic_string_viewcharstd::vectorvec<float,2,WorldPosTag>std::unordered_mapvec<int32_t,2,TilePosTag>,double,TilePosHashstd::unordered_mapvec<int32_t,2,TilePosTag>,vec<int32_t,2,TilePosTag>,TilePosHashstd::stringstd::shared_ptrSDL_Rendererstd::unique_ptrSDL_Texture,void(SDL_Texture *)std::optionalvec<float,2,WorldPosTag>std::expectedvoid,std::stringstd::vectorUserActionstd::shared_ptrEntitystd::vectorstd::shared_ptr<Entity>std::pairvec<float,2,WindowPosTag>,vec<float,2,WindowSizeTag>std::weak_ptrEntitystd::vectorstd::weak_ptr<Entity>std::pairstd::weak_ptr<Entity>,std::weak_ptr<Entity>std::vectorstd::pair<std::weak_ptr<Entity>,std::weak_ptr<Entity>>std::unique_ptrpathfinder::PathFinderBasestd::unique_ptrPathFindingDemostd::unique_ptrWindowstd::unique_ptrUserInputAnyvecT,N,OtherTagvecT,size_t N,Tag=Anyvec() : voidvec(std::array<T,N> array) : voidvec<ArgsT...>(ArgsT... args) : voidvec<OtherTag>(vec<T,N,OtherTag> && other) : voidoperator+=(const vec<T,N,Tag> & b) : vec<T,N,Tag> &operator-=(const vec<T,N,Tag> & b) : vec<T,N,Tag> &operator/=(float scalar) : vec<T,N,Tag> &operator[](size_t index) const : const T &operator[](size_t index) : T &ChangeTag<TargetTag>() : vec<T, N, TargetTag>ChangeTag<TargetTag>() const : vec<T, N, TargetTag>Data() : std::array<T,N> &DistanceSquared(const vec<T,N,Tag> & b) const : TDistanceTo(const vec<T,N,Tag> & b) const : TDotProduct(const vec<T,N,Tag> & a, const vec<T,N,Tag> & b) : TDotProduct(const vec<T,N,Tag> & b) const : TGetNormalized() const : vec<T,N,Tag>GetOrthogonal() const : vec<T,N,Tag>Length() const : TLengthSquared() const : TNormalize() : voidget<size_t I>() : T &get<size_t I>() const : const T &x() : T &x() const : const T &y() : T &y() const : const T &z() const : const T &z() : T &m_Array : std::array<T,N>WorldPosTagWorldSizeTagWindowPosTagWindowSizeTagTilePosTagTileSizeTagvecint32_t,2,TilePosTagTilePosHashoperator()(const TilePos & p) const noexcept : std::size_tvecT,N,TagMatrixT,size_t N,Tag=AnyMatrix() = default : voidMatrix<Tarr,size_t M>(std::array<Tarr,M> array) : voidoperator[](size_t index) const : const vec_type &operator[](size_t index) : vec_type &Eye() constexpr : Matrix<T,N,Tag>m_Array : std::array<vec_type,N>vecT,2,TagTileA : uint8_tB : uint8_tG : uint8_tR : uint8_tcost : floatTileTypeGRASSWOODROADWATERWALLvecfloat,2,WorldPosTagvecfloat,2,WorldSizeTagMapMap(int rows, int cols) : voidMap() : voidMap(const Map &) = deleted : voidMap(Map &&) = deleted : voidoperator=(const Map &) = deleted : Map &operator=(Map &&) = deleted : Map &GetCost(TilePos pos) const : floatGetMapTiles() const : const TileGrid &GetNeighbors(TilePos center) const : std::vector<TilePos>GetTileAt(TilePos p) const : const Tile *GetTileAt(WorldPos p) const : const Tile *GetTileSize() const : WorldSizeGetTileVelocityCoeff<T>(T p) const : doubleIsTilePosValid(TilePos p) const : boolPaintCircle(TilePos center, unsigned int radius, TileType tile_type) : voidPaintLine(TilePos start, TilePos stop, double width, TileType tile_type) : voidPaintRectangle(TilePos first_corner, TilePos second_corner, TileType tile_type) : voidTileEdgeToWorld(TilePos p) const : WorldPosTileToWorld(TilePos p) const : WorldPosWorldToTile(WorldPos p) const : TilePosTILE_SIZE : const floatm_Cols : size_tm_Rows : size_tm_Tiles : TileGridpathfinder::PathFinderTypeLINEARBFSDIJKSTRAGBFSCOUNTpathfinder::PathFinderBasePathFinderBase(const Map * m) : voidPathFinderBase(const PathFinderBase &) = deleted : voidPathFinderBase(PathFinderBase &&) = deleted : void~PathFinderBase() constexpr = default : voidoperator=(const PathFinderBase &) = deleted : PathFinderBase &operator=(PathFinderBase &&) = deleted : PathFinderBase &CalculatePath(WorldPos start, WorldPos end) = 0 : PathGetName() const = 0 : const std::string_view &m_Map : const Map *pathfinder::LinearPathFinderLinearPathFinder(const Map * m) : voidCalculatePath(WorldPos start, WorldPos end) : PathGetName() const : const std::string_view &m_Name : const std::string_viewpathfinder::BFSBFS(const Map * m) : voidCalculatePath(WorldPos start, WorldPos end) : PathGetName() const : const std::string_view &m_CameFrom : std::unordered_map<TilePos,TilePos,TilePosHash>m_Distance : std::unordered_map<TilePos,double,TilePosHash>m_Name : const std::string_viewpathfinder::DijkstraDijkstra(const Map * m) : voidCalculatePath(WorldPos start, WorldPos end) : PathGetName() const : const std::string_view &m_CameFrom : std::unordered_map<TilePos,TilePos,TilePosHash>m_Cost : std::unordered_map<TilePos,double,TilePosHash>m_Name : const std::string_viewpathfinder::utils::QueueEntryoperator>(const QueueEntry & o) const noexcept : boolcost : floattile : TilePospathfinder::GBFSGBFS(const Map * m) : voidCalculatePath(WorldPos start, WorldPos end) : PathGetName() const : const std::string_view &Heuristic(const TilePos & a, const TilePos & b) : floatm_CameFrom : std::unordered_map<TilePos,TilePos,TilePosHash>m_Name : const std::string_viewLog::LevelTypesCRITICALERRORWARNINGINFODEBUGPROFILING_DEBUGvecU,size_t M,OtherTagvecfloat,2,WindowPosTagvecfloat,2,WindowSizeTagCameraGetPan() const : WorldPosGetZoom() const : floatPan(const WorldPos & delta) : voidWindowToWorld(WindowPos) const : WorldPosWindowToWorldSize(WindowSize) const : WorldSizeWindowToWorldSize<T>(T window_size) const : TWorldToWindow(WorldPos) const : WindowPosWorldToWindowSize(WorldSize) const : WindowSizeWorldToWindowSize<T>(T world_size) const : TZoom(float delta) : voidm_Pan : WorldPosm_Zoom : floatSpriteSprite() : voidSprite(std::string path, WorldPos center = WorldPos{}) : voidSprite(const Sprite &) = deleted : voidSprite(Sprite &&) = deleted : void~Sprite() : voidoperator=(const Sprite &) = deleted : Sprite &operator=(Sprite &&) = deleted : Sprite &GetCenter() const : WorldPosGetSize() const : WorldSizeGetTexture() : SDL_Texture *LoadImage(std::string path, WorldPos image_center = WorldPos{}) : voidSetRenderer(std::shared_ptr<SDL_Renderer> renderer) : voidm_ImageCenter : WorldPosm_Renderer : std::shared_ptr<SDL_Renderer>m_Size : WorldSizem_Texture : std::unique_ptr<SDL_Texture,decltype(&SDL_DestroyTexture)>m_TextureHeight : floatm_TextureWidth : floatEntityEntity(WorldPos position = = {0.0f, 0.0f}) : voidEntity(const Entity &) = deleted : voidEntity(Entity &&) = deleted : voidoperator=(const Entity &) = deleted : Entity &operator=(Entity &&) = deleted : Entity &CollidesWith(const Entity & other) const : boolDeselect() : voidGetActualVelocity() const : const WorldPos &GetCollisionRadius() constexpr const = 0 : floatGetCollisionRadiusSquared() constexpr const : floatGetMoveTarget() : std::optional<WorldPos>GetPath() : pathfinder::Path &GetPath() const : const pathfinder::Path &GetPosition() const : const WorldPos &GetRequestedVelocity() const : const WorldPos &GetSprite() = 0 : Sprite &GetType() constexpr const = 0 : TypeIsCollidable() const = 0 : boolIsCollisionBoxVisible() const : boolIsFlaggedExpired() const : boolIsMovable() const = 0 : boolIsSelected() const : boolSelect() : voidSetActualVelocity(const WorldPos & new_velocity) : voidSetFlagExpired() : voidSetPath(pathfinder::Path & path) : voidSetPosition(WorldPos new_pos) : voidSetRequestedVelocity(const WorldPos & new_velocity) : voidUpdate(float time_delta) : voidZeroActualVelocityInDirection(WorldPos direction) : voidm_ActualVelocity : WorldPosm_CollisionBoxVisible : boolm_FlagExpired : boolm_Path : pathfinder::Pathm_Position : WorldPosm_RequestedVelocity : WorldPosm_Selected : boolEntity::TypeNONEPLAYERTILECOUNTPlayerPlayer() : voidGetCollisionRadius() constexpr const : floatGetSprite() : Sprite &GetType() constexpr const : Entity::TypeIsCollidable() const : boolIsMovable() const : boolLoadResources() : voidm_Sprite : std::unique_ptr<Sprite>MouseButtonLEFTMIDDLERIGHTUserActionUserAction() : voidUserAction(Type t) : voidUserAction(Type t, char key) : voidUserAction(Type t, WindowPos v) : voidUserAction(Type t, int32_t arg) : voidUserAction(Type t, float arg) : void~UserAction() constexpr = default : voidArgument : UserAction::(Argument)type : TypeUserAction::TypeNONEEXITSET_MOVE_TARGETSELECT_PATHFINDERCAMERA_PANCAMERA_ZOOMSELECTION_STARTSELECTION_CHANGESELECTION_ENDUserAction::(Argument)float_number : floatkey : charnumber : int32_tposition : WindowPosUserInputUserInput() : voidUserInput(const UserInput & x) = deleted : voidUserInput(UserInput && x) = deleted : void~UserInput() : voidoperator=(const UserInput &) = deleted : UserInput &operator=(UserInput &&) = deleted : UserInput &GetActions() : const std::vector<UserAction> &GetActions_keyboard(const SDL_Event &) : voidGetActions_mouse(const SDL_Event &) : voidInit() : std::expected<void,std::string>m_Actions : std::vector<UserAction>m_SelectionActive : boolSelectionBoxactive : boolend : WindowPossize : WindowSizestart : WindowPosPathFindingDemoPathFindingDemo(int width, int height) : voidPathFindingDemo(const PathFindingDemo & m) = deleted : voidPathFindingDemo(PathFindingDemo && m) = deleted : void~PathFindingDemo() : voidoperator=(const PathFindingDemo &) = deleted : PathFindingDemo &operator=(PathFindingDemo &&) = deleted : PathFindingDemo &AddEntity(std::shared_ptr<Entity> e) : voidCreateMap() : voidDeselectEntities() : voidGetCamera() const : const Camera &GetEntities() : std::vector<std::shared_ptr<Entity>> &GetEntityCollisions() : const std::vector<Collision> &GetMap() const : const Map &GetRandomPosition() const : WorldPosGetSelectedEntities() : std::vector<std::weak_ptr<Entity>>GetSelectionBoxPosSize() : std::pair<WindowPos,WindowSize>HandleActions(const std::vector<UserAction> & actions) : voidIsExitRequested() const : boolIsSelectionBoxActive() const : boolSelectEntitiesInRectangle(WorldPos A, WorldPos B) : voidUpdateWorld() : voidm_Camera : Cameram_Entities : std::vector<std::shared_ptr<Entity>>m_ExitRequested : boolm_Map : Mapm_PathFinder : std::unique_ptr<pathfinder::PathFinderBase>m_SelectedEntities : std::vector<std::weak_ptr<Entity>>m_SelectionBox : SelectionBoxWindowWindow(int width, int height) : voidWindow(const Window & x) = deleted : voidWindow(Window && x) = deleted : void~Window() : voidoperator=(const Window &) = deleted : Window &operator=(Window &&) = deleted : Window &ClearWindow() : voidDrawCircle(const WindowPos & position, float radius, uint8_t R, uint8_t G, uint8_t B) : voidDrawFilledRect(const WindowPos & position, const WindowSize size, uint8_t R, uint8_t G, uint8_t B, uint8_t A) : voidDrawLine(const WindowPos & A, const WindowPos & B) : voidDrawRect(const WindowPos & position, const WindowSize size, uint8_t R, uint8_t G, uint8_t B) : voidDrawSprite(const WindowPos & position, Sprite & s, float scale = 1.0f) : voidFlush() : voidInit() : std::expected<void,std::string>m_Context : SDL_GLContextm_Height : uint32_tm_Renderer : std::shared_ptr<SDL_Renderer>m_Width : uint32_tm_Window : SDL_Window *GameLoopGameLoop() = default : voidGameLoop(const GameLoop &) = deleted : voidGameLoop(GameLoop &&) = deleted : void~GameLoop() constexpr = default : voidoperator=(const GameLoop &) = deleted : GameLoop &operator=(GameLoop &&) = deleted : GameLoop &Draw() : voidRun() : voidSetGame(std::unique_ptr<PathFindingDemo> x) : voidSetUserInput(std::unique_ptr<UserInput> x) : voidSetWindow(std::unique_ptr<Window> x) : voidm_Game : std::unique_ptr<PathFindingDemo>m_UserInput : std::unique_ptr<UserInput>m_Window : std::unique_ptr<Window>UserAction::(anonymous_8224030)float_number : floatkey : charnumber : int32_tposition : WindowPosUserAction::(anonymous_7630584)float_number : floatkey : charnumber : int32_tposition : WindowPosUserAction::(anonymous_8808080)float_number : floatkey : charnumber : int32_tposition : WindowPosm_Arraym_Arraym_Tilesm_Mapm_Namem_Namem_Distancem_CameFromm_Namem_Costm_CameFromtilem_Namem_CameFromm_Panm_Texturem_Sizem_ImageCenterm_Positionm_ActualVelocitym_RequestedVelocitym_Pathm_SpritetypeArgumentpositionm_Actionsstartendsizem_Mapm_Cameram_Entitiesm_PathFinderm_SelectedEntitiesm_SelectionBoxm_Rendererm_Gamem_Windowm_UserInputpositionpositionposition \ No newline at end of file diff --git a/docs/diagrams/include_diagram.svg b/docs/diagrams/include_diagram.svg new file mode 100644 index 0000000..cf78dec --- /dev/null +++ b/docs/diagrams/include_diagram.svg @@ -0,0 +1 @@ +cppsrcpathfindermap.hppmath.hpptile.hpplog.hppmain.cppgameloop.hpppathfindingdemo.hppcamera.hppentities.hppsprite.hppuser_input.hppwindow.hppsprite.cppcamera.cppentities.cpppathfindingdemo.cpptile.cppuser_input.cppwindow.cppgameloop.cppmap.cppbase.cppbase.hppbfs.cppbfs.hppdijkstra.cppdijkstra.hpputils.hppgbfs.cppgbfs.hpputils.cpp \ No newline at end of file