diff --git a/include/omath/pathfinding/navigation_mesh.hpp b/include/omath/pathfinding/navigation_mesh.hpp index f9f5e27..882062e 100644 --- a/include/omath/pathfinding/navigation_mesh.hpp +++ b/include/omath/pathfinding/navigation_mesh.hpp @@ -6,6 +6,7 @@ #include "omath/linear_algebra/vector3.hpp" #include +#include #include #include #include @@ -29,10 +30,20 @@ namespace omath::pathfinding [[nodiscard]] bool empty() const; + // Events -- per-vertex optional tag (e.g. "jump", "teleport") + void set_event(const Vector3& vertex, std::string event_id); + void clear_event(const Vector3& vertex); + + [[nodiscard]] + std::optional get_event(const Vector3& vertex) const noexcept; + [[nodiscard]] std::string serialize() const noexcept; void deserialize(const std::string& raw); std::unordered_map, std::vector>> m_vertex_map; + + private: + std::unordered_map, std::string> m_vertex_events; }; } // namespace omath::pathfinding diff --git a/source/pathfinding/navigation_mesh.cpp b/source/pathfinding/navigation_mesh.cpp index 8ee17a2..d468590 100644 --- a/source/pathfinding/navigation_mesh.cpp +++ b/source/pathfinding/navigation_mesh.cpp @@ -5,6 +5,7 @@ #include #include #include + namespace omath::pathfinding { std::expected, std::string> @@ -29,13 +30,40 @@ namespace omath::pathfinding return m_vertex_map.empty(); } + void NavigationMesh::set_event(const Vector3& vertex, std::string event_id) + { + m_vertex_events[vertex] = std::move(event_id); + } + + void NavigationMesh::clear_event(const Vector3& vertex) + { + m_vertex_events.erase(vertex); + } + + std::optional NavigationMesh::get_event(const Vector3& vertex) const noexcept + { + const auto it = m_vertex_events.find(vertex); + if (it == m_vertex_events.end()) + return std::nullopt; + return it->second; + } + + // Serialization format per vertex line: + // x y z neighbor_count event_id + // where event_id is "-" when no event is set. + // Neighbor lines follow: nx ny nz + std::string NavigationMesh::serialize() const noexcept { std::ostringstream oss; for (const auto& [vertex, neighbors] : m_vertex_map) { + const auto event_it = m_vertex_events.find(vertex); + const std::string& event = (event_it != m_vertex_events.end()) ? event_it->second : "-"; + oss << vertex.x << ' ' << vertex.y << ' ' << vertex.z - << ' ' << neighbors.size() << '\n'; + << ' ' << neighbors.size() << ' ' << event << '\n'; + for (const auto& n : neighbors) oss << n.x << ' ' << n.y << ' ' << n.z << '\n'; } @@ -45,11 +73,13 @@ namespace omath::pathfinding void NavigationMesh::deserialize(const std::string& raw) { m_vertex_map.clear(); + m_vertex_events.clear(); std::istringstream iss(raw); Vector3 vertex; std::size_t neighbors_count; - while (iss >> vertex.x >> vertex.y >> vertex.z >> neighbors_count) + std::string event; + while (iss >> vertex.x >> vertex.y >> vertex.z >> neighbors_count >> event) { std::vector> neighbors; neighbors.reserve(neighbors_count); @@ -61,6 +91,9 @@ namespace omath::pathfinding neighbors.push_back(n); } m_vertex_map.emplace(vertex, std::move(neighbors)); + + if (event != "-") + m_vertex_events.emplace(vertex, std::move(event)); } } } // namespace omath::pathfinding diff --git a/tests/general/unit_test_navigation_mesh.cpp b/tests/general/unit_test_navigation_mesh.cpp index c3c9314..266bf62 100644 --- a/tests/general/unit_test_navigation_mesh.cpp +++ b/tests/general/unit_test_navigation_mesh.cpp @@ -140,3 +140,102 @@ TEST(NavigationMeshTests, VertexWithNoNeighborsRoundTrip) ASSERT_EQ(nav2.m_vertex_map.count(v), 1u); EXPECT_TRUE(nav2.get_neighbors(v).empty()); } + +// --------------------------------------------------------------------------- +// Vertex events +// --------------------------------------------------------------------------- + +TEST(NavigationMeshTests, EventNotSetByDefault) +{ + NavigationMesh nav; + const Vector3 v{0.f, 0.f, 0.f}; + nav.m_vertex_map.emplace(v, std::vector>{}); + + EXPECT_FALSE(nav.get_event(v).has_value()); +} + +TEST(NavigationMeshTests, SetAndGetEvent) +{ + NavigationMesh nav; + const Vector3 v{1.f, 0.f, 0.f}; + nav.m_vertex_map.emplace(v, std::vector>{}); + nav.set_event(v, "jump"); + + const auto event = nav.get_event(v); + ASSERT_TRUE(event.has_value()); + EXPECT_EQ(event.value(), "jump"); +} + +TEST(NavigationMeshTests, OverwriteEvent) +{ + NavigationMesh nav; + const Vector3 v{1.f, 0.f, 0.f}; + nav.m_vertex_map.emplace(v, std::vector>{}); + nav.set_event(v, "jump"); + nav.set_event(v, "teleport"); + + EXPECT_EQ(nav.get_event(v).value(), "teleport"); +} + +TEST(NavigationMeshTests, ClearEvent) +{ + NavigationMesh nav; + const Vector3 v{1.f, 0.f, 0.f}; + nav.m_vertex_map.emplace(v, std::vector>{}); + nav.set_event(v, "jump"); + nav.clear_event(v); + + EXPECT_FALSE(nav.get_event(v).has_value()); +} + +TEST(NavigationMeshTests, EventRoundTripSerialization) +{ + NavigationMesh nav; + const Vector3 a{0.f, 0.f, 0.f}; + const Vector3 b{1.f, 0.f, 0.f}; + nav.m_vertex_map.emplace(a, std::vector>{b}); + nav.m_vertex_map.emplace(b, std::vector>{}); + nav.set_event(b, "jump"); + + NavigationMesh nav2; + nav2.deserialize(nav.serialize()); + + ASSERT_FALSE(nav2.get_event(a).has_value()); + ASSERT_TRUE(nav2.get_event(b).has_value()); + EXPECT_EQ(nav2.get_event(b).value(), "jump"); +} + +TEST(NavigationMeshTests, MultipleEventsRoundTrip) +{ + NavigationMesh nav; + const Vector3 a{0.f, 0.f, 0.f}; + const Vector3 b{1.f, 0.f, 0.f}; + const Vector3 c{2.f, 0.f, 0.f}; + nav.m_vertex_map.emplace(a, std::vector>{}); + nav.m_vertex_map.emplace(b, std::vector>{}); + nav.m_vertex_map.emplace(c, std::vector>{}); + nav.set_event(a, "spawn"); + nav.set_event(c, "teleport"); + + NavigationMesh nav2; + nav2.deserialize(nav.serialize()); + + EXPECT_EQ(nav2.get_event(a).value(), "spawn"); + EXPECT_FALSE(nav2.get_event(b).has_value()); + EXPECT_EQ(nav2.get_event(c).value(), "teleport"); +} + +TEST(NavigationMeshTests, DeserializeClearsOldEvents) +{ + NavigationMesh nav; + const Vector3 v{0.f, 0.f, 0.f}; + nav.m_vertex_map.emplace(v, std::vector>{}); + nav.set_event(v, "jump"); + + // Deserialize a mesh that has no events + NavigationMesh empty_events; + empty_events.m_vertex_map.emplace(v, std::vector>{}); + + nav.deserialize(empty_events.serialize()); + EXPECT_FALSE(nav.get_event(v).has_value()); +}