From 5dc48d98f9ce2fd5ee9cc52eed12ac0c1cc4fd14 Mon Sep 17 00:00:00 2001 From: mlsdpk Date: Sun, 28 Nov 2021 22:18:32 +0630 Subject: [PATCH 1/4] Improve gui style --- src/Game.cpp | 62 +++++++++------- .../Algorithms/GraphBased/GraphBased.cpp | 71 +++++++++---------- .../Algorithms/SamplingBased/RRT/RRT.cpp | 1 - .../SamplingBased/RRT_STAR/RRT_STAR.cpp | 2 - .../SamplingBased/SamplingBased.cpp | 52 ++++++-------- 5 files changed, 92 insertions(+), 96 deletions(-) diff --git a/src/Game.cpp b/src/Game.cpp index 5a0985c..7995181 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -72,7 +72,6 @@ void Game::render() { window_->clear(sf::Color::White); if (!states_.empty()) { - // guiTheme(); ImGui::SetNextWindowPos(ImVec2(0.f, 0.f), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize( ImVec2(332.f, static_cast(window_->getSize().y)), @@ -83,6 +82,39 @@ void Game::render() { ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); renderGui(); states_.top()->render(); + + if (ImGui::CollapsingHeader("Help")) { + ImGui::Text("ABOUT THIS VISUALIZER:"); + ImGui::Text( + "This project involves minimal implementations of\nthe popular " + "planning algorithms, including\nboth graph-based and " + "sampling-based planners."); + + ImGui::Separator(); + + ImGui::Text("AVAILABLE PLANNERS:"); + + ImGui::BulletText("Graph-based Planners:"); + ImGui::Indent(); + ImGui::BulletText("Breadth-first search (BFS)"); + ImGui::BulletText("Depth-first search (DFS)"); + ImGui::BulletText("Dijkstra"); + ImGui::BulletText("A*"); + + ImGui::Unindent(); + ImGui::BulletText("Sampling-based Planners:"); + ImGui::Indent(); + ImGui::BulletText("Rapidly-exploring random trees (RRT)"); + ImGui::BulletText("RRT*"); + ImGui::Unindent(); + + ImGui::Separator(); + + ImGui::Text("USAGE GUIDE:"); + ImGui::BulletText("Left-click to place/remove obstacle cells"); + ImGui::BulletText("Left-SHIFT+left-click to change starting cell"); + ImGui::BulletText("Left-CTRL+left-click to change end cell"); + } ImGui::End(); } @@ -100,6 +132,7 @@ void Game::initGuiTheme() { ImGuiStyle* style = &ImGui::GetStyle(); style->FramePadding = ImVec2(8.f, 8.f); + style->ItemSpacing = ImVec2(4.0f, 4.0f); // dark theme colors auto& colors = ImGui::GetStyle().Colors; @@ -164,8 +197,9 @@ void Game::setSamplingBasedPlanner(const int id) { } void Game::renderGui() { - if (ImGui::Button("Choose Planner..")) ImGui::OpenPopup("planners_popup"); - ImGui::SameLine(); + if (ImGui::Button("Choose Planner", ImVec2(210.0f, 0.0f))) + ImGui::OpenPopup("planners_popup"); + ImGui::SameLine(0.0f, 8.0f); ImGui::TextUnformatted(curr_planner_.c_str()); if (ImGui::BeginPopup("planners_popup")) { if (ImGui::BeginMenu("Graph-based Planners")) { @@ -199,28 +233,6 @@ void Game::renderGui() { ImGui::EndPopup(); } - - // if (ImGui::BeginCombo("Select Planner", curr_planner_.c_str())) { - // for (int n = 0; n < PLANNER_NAMES.size(); n++) { - // bool is_selected = (curr_planner_ == PLANNER_NAMES[n]); - // if (ImGui::Selectable(PLANNER_NAMES[n].c_str(), is_selected)) { - // if (PLANNER_NAMES[n] != curr_planner_) { - // // change the planner - // setPlanner(n); - // } - // curr_planner_ = PLANNER_NAMES[n]; - // } - - // if (is_selected) - // ImGui::SetItemDefaultFocus(); // Set the initial focus when opening - // the - // // combo (scrolling + for keyboard - // // navigation support in the upcoming - // // navigation branch) - // } - // ImGui::EndCombo(); - // } - ImGui::Spacing(); } } // namespace path_finding_visualizer \ No newline at end of file diff --git a/src/States/Algorithms/GraphBased/GraphBased.cpp b/src/States/Algorithms/GraphBased/GraphBased.cpp index 9923284..eefbc53 100644 --- a/src/States/Algorithms/GraphBased/GraphBased.cpp +++ b/src/States/Algorithms/GraphBased/GraphBased.cpp @@ -201,7 +201,7 @@ void GraphBased::renderGui() { // RESET button { if (!disable_run_ || is_running_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RESET", ImVec2(100.f, 40.f)); + bool clicked = ImGui::Button("RESET", ImVec2(103.0f, 0.0f)); if (!disable_run_ || is_running_) ImGui::EndDisabled(); if (clicked && !is_running_) { is_reset_ = true; @@ -216,7 +216,7 @@ void GraphBased::renderGui() { // always disabled (not implemented yet) { if (true) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("PAUSE", ImVec2(100.f, 40.f)); + bool clicked = ImGui::Button("PAUSE", ImVec2(103.0f, 0.0f)); if (true) ImGui::EndDisabled(); } @@ -225,7 +225,7 @@ void GraphBased::renderGui() { // RUN button { if (disable_run_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RUN", ImVec2(100.f, 40.f)); + bool clicked = ImGui::Button("RUN", ImVec2(103.0f, 0.0f)); if (disable_run_) ImGui::EndDisabled(); if (clicked && !is_solved_) { is_running_ = true; @@ -235,48 +235,41 @@ void GraphBased::renderGui() { } } - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - if (disable_gui_parameters_) ImGui::BeginDisabled(); + if (ImGui::CollapsingHeader("Configuration", + ImGuiTreeNodeFlags_DefaultOpen)) { + if (disable_gui_parameters_) ImGui::BeginDisabled(); + // grid size slider + if (ImGui::SliderInt("Grid Size", &slider_grid_size_, 10, 100)) { + gridSize_ = slider_grid_size_; + initNodes(true, false); + } - // grid size slider - if (ImGui::SliderInt("Grid Size", &slider_grid_size_, 10, 100)) { - gridSize_ = slider_grid_size_; - initNodes(true, false); - } + // radio buttons for choosing 4 or 8 connected grids + { + bool a, b; + a = ImGui::RadioButton("4-connected", &grid_connectivity_, 0); + ImGui::SameLine(); + b = ImGui::RadioButton("8-connected", &grid_connectivity_, 1); + if (a || b) { + initNodes(false, true); + } + } - ImGui::Spacing(); + // virtual function renderParametersGui() + // need to be implemented by derived class + renderParametersGui(); - // radio buttons for choosing 4 or 8 connected grids - { - bool a, b; - a = ImGui::RadioButton("4-connected", &grid_connectivity_, 0); - ImGui::SameLine(); - b = ImGui::RadioButton("8-connected", &grid_connectivity_, 1); - if (a || b) { - initNodes(false, true); + { + if (ImGui::Button("CLEAR OBSTACLES", ImVec2(156.5f, 0.f))) { + clearObstacles(); + } + ImGui::SameLine(); + if (ImGui::Button("RESET PARAMETERS", ImVec2(156.5f, 0.f))) { + } } - } - ImGui::Spacing(); - // virtual function renderParametersGui() - // need to be implemented by derived class - renderParametersGui(); - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - { - if (ImGui::Button("CLEAR OBSTACLES", ImVec2(154.f, 40.f))) { - clearObstacles(); - } - ImGui::SameLine(); - if (ImGui::Button("RESET PARAMETERS", ImVec2(154.f, 40.f))) { - } + if (disable_gui_parameters_) ImGui::EndDisabled(); } - - if (disable_gui_parameters_) ImGui::EndDisabled(); } void GraphBased::render() { diff --git a/src/States/Algorithms/SamplingBased/RRT/RRT.cpp b/src/States/Algorithms/SamplingBased/RRT/RRT.cpp index 1eefe7c..19cf126 100644 --- a/src/States/Algorithms/SamplingBased/RRT/RRT.cpp +++ b/src/States/Algorithms/SamplingBased/RRT/RRT.cpp @@ -102,7 +102,6 @@ void RRT::renderParametersGui() { if (ImGui::InputDouble("range", &range_, 0.01, 1.0, "%.3f")) { if (range_ < 0) range_ = 0.01; } - ImGui::Spacing(); if (ImGui::InputDouble("goal_radius", &goal_radius_, 0.01, 1.0, "%.3f")) { if (goal_radius_ < 0.01) goal_radius_ = 0.01; } diff --git a/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp b/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp index 5f198e6..881d83a 100644 --- a/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp +++ b/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp @@ -58,14 +58,12 @@ void RRT_STAR::renderParametersGui() { if (ImGui::InputDouble("range", &range_, 0.01, 1.0, "%.3f")) { if (range_ < 0.01) range_ = 0.01; } - ImGui::Spacing(); if (ImGui::InputDouble("rewire_factor", &rewire_factor_, 0.01, 0.1, "%.2f")) { if (rewire_factor_ < 1.0) rewire_factor_ = 1.0; else if (rewire_factor_ > 2.0) rewire_factor_ = 2.0; } - ImGui::Spacing(); if (ImGui::InputDouble("goal_radius", &goal_radius_, 0.01, 1.0, "%.3f")) { if (goal_radius_ < 0.01) goal_radius_ = 0.01; } diff --git a/src/States/Algorithms/SamplingBased/SamplingBased.cpp b/src/States/Algorithms/SamplingBased/SamplingBased.cpp index e0d974b..8bde525 100644 --- a/src/States/Algorithms/SamplingBased/SamplingBased.cpp +++ b/src/States/Algorithms/SamplingBased/SamplingBased.cpp @@ -211,7 +211,7 @@ void SamplingBased::renderGui() { // RESET button { if (!disable_run_ || is_running_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RESET", ImVec2(100.f, 40.f)); + bool clicked = ImGui::Button("RESET", ImVec2(103.f, 0.f)); if (!disable_run_ || is_running_) ImGui::EndDisabled(); if (clicked && !is_running_) { is_reset_ = true; @@ -226,7 +226,7 @@ void SamplingBased::renderGui() { // always disabled (not implemented yet) { if (true) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("PAUSE", ImVec2(100.f, 40.f)); + bool clicked = ImGui::Button("PAUSE", ImVec2(103.f, 0.f)); if (true) ImGui::EndDisabled(); } @@ -235,7 +235,7 @@ void SamplingBased::renderGui() { // RUN button { if (disable_run_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RUN", ImVec2(100.f, 40.f)); + bool clicked = ImGui::Button("RUN", ImVec2(103.f, 0.f)); if (disable_run_) ImGui::EndDisabled(); if (clicked && !is_solved_) { is_running_ = true; @@ -245,8 +245,6 @@ void SamplingBased::renderGui() { } } - ImGui::Spacing(); - { std::unique_lock iter_no_lck(iter_no_mutex_); const float progress = static_cast( @@ -254,36 +252,32 @@ void SamplingBased::renderGui() { static_cast(max_iterations_), 0.0, 1.0)); const std::string buf = std::to_string(curr_iter_no_) + "/" + std::to_string(max_iterations_); - ImGui::ProgressBar(progress, ImVec2(-1.0f, 0.0f), buf.c_str()); + ImGui::ProgressBar(progress, ImVec2(317.0f, 0.0f), buf.c_str()); } - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - if (disable_gui_parameters_) ImGui::BeginDisabled(); + if (ImGui::CollapsingHeader("Configuration", + ImGuiTreeNodeFlags_DefaultOpen)) { + if (disable_gui_parameters_) ImGui::BeginDisabled(); - if (ImGui::InputInt("max_iterations", &max_iterations_, 1, 1000)) { - if (max_iterations_ < 1) max_iterations_ = 1; - } - ImGui::Spacing(); - // virtual function renderParametersGui() - // need to be implemented by derived class - renderParametersGui(); - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - { - if (ImGui::Button("CLEAR OBSTACLES", ImVec2(154.f, 40.f))) { - clearObstacles(); + if (ImGui::InputInt("max_iterations", &max_iterations_, 1, 1000)) { + if (max_iterations_ < 1) max_iterations_ = 1; } - ImGui::SameLine(); - if (ImGui::Button("RESET PARAMETERS", ImVec2(154.f, 40.f))) { - initParameters(); + // virtual function renderParametersGui() + // need to be implemented by derived class + renderParametersGui(); + + { + if (ImGui::Button("CLEAR OBSTACLES", ImVec2(156.5f, 0.f))) { + clearObstacles(); + } + ImGui::SameLine(); + if (ImGui::Button("RESET PARAMETERS", ImVec2(156.5f, 0.f))) { + initParameters(); + } } - } - if (disable_gui_parameters_) ImGui::EndDisabled(); + if (disable_gui_parameters_) ImGui::EndDisabled(); + } } void SamplingBased::render() { From ed5bab5df5a35d5f222faa2145c8dcb7191f7b36 Mon Sep 17 00:00:00 2001 From: mlsdpk Date: Mon, 29 Nov 2021 19:10:56 +0630 Subject: [PATCH 2/4] Create menubar and more improvements --- include/Game.h | 8 +- include/LoggerPanel.h | 76 ++++++ include/State.h | 13 +- .../Algorithms/GraphBased/ASTAR/ASTAR.h | 3 +- .../States/Algorithms/GraphBased/BFS/BFS.h | 3 +- .../States/Algorithms/GraphBased/DFS/DFS.h | 3 +- .../Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h | 4 +- .../States/Algorithms/GraphBased/GraphBased.h | 6 +- .../States/Algorithms/SamplingBased/RRT/RRT.h | 3 +- .../SamplingBased/RRT_STAR/RRT_STAR.h | 4 +- .../Algorithms/SamplingBased/SamplingBased.h | 6 +- src/Game.cpp | 244 ++++++++++++------ src/State.cpp | 8 +- .../Algorithms/GraphBased/ASTAR/ASTAR.cpp | 5 +- src/States/Algorithms/GraphBased/BFS/BFS.cpp | 5 +- src/States/Algorithms/GraphBased/DFS/DFS.cpp | 5 +- .../GraphBased/DIJKSTRA/DIJKSTRA.cpp | 5 +- .../Algorithms/GraphBased/GraphBased.cpp | 46 +--- .../Algorithms/SamplingBased/RRT/RRT.cpp | 5 +- .../SamplingBased/RRT_STAR/RRT_STAR.cpp | 6 +- .../SamplingBased/SamplingBased.cpp | 56 ++-- 21 files changed, 317 insertions(+), 197 deletions(-) create mode 100644 include/LoggerPanel.h diff --git a/include/Game.h b/include/Game.h index a99efe1..42de7e5 100644 --- a/include/Game.h +++ b/include/Game.h @@ -36,9 +36,11 @@ class Game { void update(); void render(); void initGuiTheme(); - void renderGui(); + void renderMenuBar(ImGuiIO& io); void setGraphBasedPlanner(const int id); void setSamplingBasedPlanner(const int id); + void showHowToUseWindow(); + void showAboutWindow(); private: sf::RenderWindow* window_; @@ -47,6 +49,10 @@ class Game { float dt_; std::stack> states_; std::string curr_planner_; + std::shared_ptr logger_panel_; + bool disable_run_; + bool show_how_to_use_window_{false}; + bool show_about_window_{false}; }; } // namespace path_finding_visualizer \ No newline at end of file diff --git a/include/LoggerPanel.h b/include/LoggerPanel.h new file mode 100644 index 0000000..407ca69 --- /dev/null +++ b/include/LoggerPanel.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +namespace path_finding_visualizer { + +class LoggerPanel { + public: + LoggerPanel() { + AutoScroll = true; + clear(); + } + + void clear() { + Buf.clear(); + LineOffsets.clear(); + LineOffsets.push_back(0); + } + + void render(const char* title) { + if (!ImGui::Begin(title)) { + ImGui::End(); + return; + } + + ImGui::BeginChild("scrolling", ImVec2(0, 0), false, + ImGuiWindowFlags_HorizontalScrollbar); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + const char* buf = Buf.begin(); + const char* buf_end = Buf.end(); + { + ImGuiListClipper clipper; + clipper.Begin(LineOffsets.Size); + while (clipper.Step()) { + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; + line_no++) { + const char* line_start = buf + LineOffsets[line_no]; + const char* line_end = (line_no + 1 < LineOffsets.Size) + ? (buf + LineOffsets[line_no + 1] - 1) + : buf_end; + ImGui::TextUnformatted(line_start, line_end); + } + } + clipper.End(); + } + ImGui::PopStyleVar(); + + if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + ImGui::SetScrollHereY(1.0f); + + ImGui::EndChild(); + ImGui::End(); + } + + void info(const std::string& msg) { AddLog("[INFO] %s\n", msg.c_str()); } + + private: + ImGuiTextBuffer Buf; + ImVector LineOffsets; // Index to lines offset. We maintain this with + // AddLog() calls. + bool AutoScroll; // Keep scrolling if already at the bottom. + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) { + int old_size = Buf.size(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') LineOffsets.push_back(old_size + 1); + } +}; + +} // namespace path_finding_visualizer \ No newline at end of file diff --git a/include/State.h b/include/State.h index c55d305..f9033fe 100644 --- a/include/State.h +++ b/include/State.h @@ -6,10 +6,13 @@ #include #include #include +#include #include #include #include +#include "LoggerPanel.h" + /* State Base Class */ @@ -20,14 +23,17 @@ class State { private: protected: std::stack> &states_; - sf::RenderWindow *window_; + std::shared_ptr logger_panel_; sf::Vector2i mousePositionWindow_; bool quit_; + bool is_reset_; + bool is_running_; public: // Constructor - State(sf::RenderWindow *window, std::stack> &states); + State(sf::RenderWindow *window, std::stack> &states, + std::shared_ptr logger_panel); // Destructor virtual ~State(); @@ -35,6 +41,9 @@ class State { // Accessors const bool getQuit() const; + void setReset(bool is_reset) { is_reset_ = is_reset; } + void setRunning(bool is_running) { is_running_ = is_running; } + // Functions virtual void checkForQuit(); virtual void updateMousePosition(); diff --git a/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h b/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h index 5264e67..8c4ccd8 100644 --- a/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h +++ b/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h @@ -21,7 +21,8 @@ struct MinimumDistanceASTAR { class ASTAR : public BFS { public: // Constructor - ASTAR(sf::RenderWindow *window, std::stack> &states); + ASTAR(sf::RenderWindow *window, std::stack> &states, + std::shared_ptr logger_panel); // Destructor virtual ~ASTAR(); diff --git a/include/States/Algorithms/GraphBased/BFS/BFS.h b/include/States/Algorithms/GraphBased/BFS/BFS.h index abc7ffe..76b7557 100644 --- a/include/States/Algorithms/GraphBased/BFS/BFS.h +++ b/include/States/Algorithms/GraphBased/BFS/BFS.h @@ -11,7 +11,8 @@ namespace graph_based { class BFS : public GraphBased { public: // Constructor - BFS(sf::RenderWindow *window, std::stack> &states); + BFS(sf::RenderWindow *window, std::stack> &states, + std::shared_ptr logger_panel); // Destructor virtual ~BFS(); diff --git a/include/States/Algorithms/GraphBased/DFS/DFS.h b/include/States/Algorithms/GraphBased/DFS/DFS.h index 11d0d66..702136f 100644 --- a/include/States/Algorithms/GraphBased/DFS/DFS.h +++ b/include/States/Algorithms/GraphBased/DFS/DFS.h @@ -10,7 +10,8 @@ namespace graph_based { class DFS : public BFS { public: // Constructor - DFS(sf::RenderWindow *window, std::stack> &states); + DFS(sf::RenderWindow *window, std::stack> &states, + std::shared_ptr logger_panel); // Destructor virtual ~DFS(); diff --git a/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h b/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h index 78909e0..8363d16 100644 --- a/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h +++ b/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h @@ -21,8 +21,8 @@ struct MinimumDistanceDIJKSTRA { class DIJKSTRA : public BFS { public: // Constructor - DIJKSTRA(sf::RenderWindow *window, - std::stack> &states); + DIJKSTRA(sf::RenderWindow *window, std::stack> &states, + std::shared_ptr logger_panel); // Destructor virtual ~DIJKSTRA(); diff --git a/include/States/Algorithms/GraphBased/GraphBased.h b/include/States/Algorithms/GraphBased/GraphBased.h index 60d8188..08803ce 100644 --- a/include/States/Algorithms/GraphBased/GraphBased.h +++ b/include/States/Algorithms/GraphBased/GraphBased.h @@ -10,6 +10,7 @@ #include #include +#include "LoggerPanel.h" #include "MessageQueue.h" #include "State.h" #include "States/Algorithms/GraphBased/Node.h" @@ -22,7 +23,8 @@ class GraphBased : public State { public: // Constructor GraphBased(sf::RenderWindow* window, - std::stack>& states); + std::stack>& states, + std::shared_ptr logger_panel); // Destructor virtual ~GraphBased(); @@ -72,9 +74,7 @@ class GraphBased : public State { std::shared_ptr> message_queue_; // logic flags - bool is_running_; bool is_initialized_; - bool is_reset_; bool is_solved_; bool disable_run_; bool disable_gui_parameters_; diff --git a/include/States/Algorithms/SamplingBased/RRT/RRT.h b/include/States/Algorithms/SamplingBased/RRT/RRT.h index 15a871c..0a340be 100644 --- a/include/States/Algorithms/SamplingBased/RRT/RRT.h +++ b/include/States/Algorithms/SamplingBased/RRT/RRT.h @@ -11,7 +11,8 @@ namespace sampling_based { class RRT : public SamplingBased { public: // Constructor - RRT(sf::RenderWindow *window, std::stack> &states); + RRT(sf::RenderWindow *window, std::stack> &states, + std::shared_ptr logger_panel, const std::string &name); // Destructor virtual ~RRT(); diff --git a/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h b/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h index 5e93bf5..a82ba6a 100644 --- a/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h +++ b/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h @@ -10,8 +10,8 @@ namespace sampling_based { class RRT_STAR : public RRT { public: // Constructor - RRT_STAR(sf::RenderWindow* window, - std::stack>& states); + RRT_STAR(sf::RenderWindow* window, std::stack>& states, + std::shared_ptr logger_panel, const std::string& name); // Destructor virtual ~RRT_STAR(); diff --git a/include/States/Algorithms/SamplingBased/SamplingBased.h b/include/States/Algorithms/SamplingBased/SamplingBased.h index 20629e9..d553dad 100644 --- a/include/States/Algorithms/SamplingBased/SamplingBased.h +++ b/include/States/Algorithms/SamplingBased/SamplingBased.h @@ -32,7 +32,9 @@ class SamplingBased : public State { public: // Constructor SamplingBased(sf::RenderWindow *window, - std::stack> &states); + std::stack> &states, + std::shared_ptr logger_panel, + const std::string &name); // Destructor virtual ~SamplingBased(); @@ -116,9 +118,7 @@ class SamplingBased : public State { std::shared_ptr> message_queue_; // logic flags - bool is_running_; bool is_initialized_; - bool is_reset_; bool is_solved_; bool is_stopped_; bool disable_run_; diff --git a/src/Game.cpp b/src/Game.cpp index 7995181..20aa285 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -18,9 +18,12 @@ namespace path_finding_visualizer { // Constructor Game::Game(sf::RenderWindow* window) : window_{window} { + disable_run_ = false; + logger_panel_ = std::make_shared(); curr_planner_ = GRAPH_BASED_PLANNERS[0]; // manually add BFS for now - states_.push(std::make_unique(window_, states_)); + states_.push( + std::make_unique(window_, states_, logger_panel_)); initGuiTheme(); } @@ -68,54 +71,160 @@ void Game::update() { ImGui::SFML::Update(*window_, dtClock_.restart()); } +void Game::renderMenuBar(ImGuiIO& io) { + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("Planners")) { + if (ImGui::BeginMenu("Graph-based Planners")) { + for (int n = 0; n < GRAPH_BASED_PLANNERS.size(); n++) { + bool selected = (GRAPH_BASED_PLANNERS[n] == curr_planner_); + if (ImGui::MenuItem(GRAPH_BASED_PLANNERS[n].c_str(), nullptr, + selected, !selected)) { + if (!selected) { + // change the planner + logger_panel_->clear(); + disable_run_ = false; + setGraphBasedPlanner(n); + } + curr_planner_ = GRAPH_BASED_PLANNERS[n]; + } + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Sampling-based Planners")) { + for (int n = 0; n < SAMPLING_BASED_PLANNERS.size(); n++) { + bool selected = (SAMPLING_BASED_PLANNERS[n] == curr_planner_); + if (ImGui::MenuItem(SAMPLING_BASED_PLANNERS[n].c_str(), nullptr, + selected, !selected)) { + if (!selected) { + // change the planner + logger_panel_->clear(); + disable_run_ = false; + setSamplingBasedPlanner(n); + } + curr_planner_ = SAMPLING_BASED_PLANNERS[n]; + } + } + ImGui::EndMenu(); + } + + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Run")) { + { + if (disable_run_) ImGui::BeginDisabled(); + bool clicked = ImGui::MenuItem("Start Planning"); + if (disable_run_) ImGui::EndDisabled(); + if (clicked) { + logger_panel_->info("RUN button pressed. Planning started."); + disable_run_ = true; + if (!states_.empty()) { + states_.top()->setRunning(true); + } + } + } + { + if (!disable_run_) ImGui::BeginDisabled(); + bool clicked = ImGui::MenuItem("Reset Planner Data"); + if (!disable_run_) ImGui::EndDisabled(); + if (clicked) { + logger_panel_->info("RESET button pressed. Planning resetted."); + disable_run_ = false; + if (!states_.empty()) { + states_.top()->setReset(true); + } + } + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Help")) { + ImGui::MenuItem("How To Use", nullptr, &show_how_to_use_window_); + ImGui::MenuItem("About", nullptr, &show_about_window_); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } +} + void Game::render() { window_->clear(sf::Color::White); if (!states_.empty()) { - ImGui::SetNextWindowPos(ImVec2(0.f, 0.f), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize( - ImVec2(332.f, static_cast(window_->getSize().y)), - ImGuiCond_None); + ImGui::SetNextWindowPos(ImVec2(0.f, 0.f), ImGuiCond_Once); + ImGui::SetNextWindowSize(ImVec2(332.f, 536.f), ImGuiCond_None); ImGui::Begin("path_finding_visualizer", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); - renderGui(); + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_MenuBar); + + ImGuiIO& io = ImGui::GetIO(); + renderMenuBar(io); + + ImGui::Text("Current Planner: %s", curr_planner_.c_str()); + ImGui::Spacing(); + ImGui::Spacing(); + states_.top()->render(); - if (ImGui::CollapsingHeader("Help")) { - ImGui::Text("ABOUT THIS VISUALIZER:"); - ImGui::Text( - "This project involves minimal implementations of\nthe popular " - "planning algorithms, including\nboth graph-based and " - "sampling-based planners."); - - ImGui::Separator(); - - ImGui::Text("AVAILABLE PLANNERS:"); - - ImGui::BulletText("Graph-based Planners:"); - ImGui::Indent(); - ImGui::BulletText("Breadth-first search (BFS)"); - ImGui::BulletText("Depth-first search (DFS)"); - ImGui::BulletText("Dijkstra"); - ImGui::BulletText("A*"); - - ImGui::Unindent(); - ImGui::BulletText("Sampling-based Planners:"); - ImGui::Indent(); - ImGui::BulletText("Rapidly-exploring random trees (RRT)"); - ImGui::BulletText("RRT*"); - ImGui::Unindent(); - - ImGui::Separator(); - - ImGui::Text("USAGE GUIDE:"); - ImGui::BulletText("Left-click to place/remove obstacle cells"); - ImGui::BulletText("Left-SHIFT+left-click to change starting cell"); - ImGui::BulletText("Left-CTRL+left-click to change end cell"); + if (show_how_to_use_window_) { + if (ImGui::CollapsingHeader("How To Use")) { + ImGui::Text("USAGE GUIDE:"); + ImGui::BulletText("Left-click to place/remove obstacle cells"); + ImGui::BulletText("Left-SHIFT+left-click to change starting cell"); + ImGui::BulletText("Left-CTRL+left-click to change end cell"); + } } + + if (show_about_window_) { + if (ImGui::CollapsingHeader("About")) { + ImGui::Text("Path-finding Visualizer (v1.0.0)"); + ImGui::Text("Developed and maintained by Phone Thiha Kyaw."); + ImGui::Text("Email: mlsdphonethk @ gmail dot com"); + ImGui::Separator(); + ImGui::Text("ABOUT THIS VISUALIZER:"); + ImGui::Text( + "This project involves minimal implementations of\nthe popular " + "planning algorithms, including\nboth graph-based and " + "sampling-based planners."); + + ImGui::Separator(); + + ImGui::Text("AVAILABLE PLANNERS:"); + + ImGui::BulletText("Graph-based Planners:"); + ImGui::Indent(); + ImGui::BulletText("Breadth-first search (BFS)"); + ImGui::BulletText("Depth-first search (DFS)"); + ImGui::BulletText("Dijkstra"); + ImGui::BulletText("A*"); + + ImGui::Unindent(); + ImGui::BulletText("Sampling-based Planners:"); + ImGui::Indent(); + ImGui::BulletText("Rapidly-exploring random trees (RRT)"); + ImGui::BulletText("RRT*"); + ImGui::Unindent(); + } + } + ImGui::End(); + + ImGui::SetNextWindowPos(ImVec2(0.f, 536.f), ImGuiCond_Once); + ImGui::SetNextWindowSize( + ImVec2(332.f, static_cast(window_->getSize().y) - 536.f), + ImGuiCond_None); + + ImGui::Begin("Console", nullptr, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse); + ImGui::End(); + + // Actually call in the regular Log helper (which will Begin() into the same + // window as we just did) + logger_panel_->render("Console"); } ImGui::SFML::Render(*window_); @@ -132,7 +241,7 @@ void Game::initGuiTheme() { ImGuiStyle* style = &ImGui::GetStyle(); style->FramePadding = ImVec2(8.f, 8.f); - style->ItemSpacing = ImVec2(4.0f, 4.0f); + style->ItemSpacing = ImVec2(8.0f, 4.0f); // dark theme colors auto& colors = ImGui::GetStyle().Colors; @@ -162,19 +271,23 @@ void Game::setGraphBasedPlanner(const int id) { switch (id) { case GRAPH_BASED_PLANNERS_IDS::BFS: // BFS - states_.push(std::make_unique(window_, states_)); + states_.push( + std::make_unique(window_, states_, logger_panel_)); break; case GRAPH_BASED_PLANNERS_IDS::DFS: // DFS - states_.push(std::make_unique(window_, states_)); + states_.push( + std::make_unique(window_, states_, logger_panel_)); break; case GRAPH_BASED_PLANNERS_IDS::DIJKSTRA: // Dijkstra - states_.push(std::make_unique(window_, states_)); + states_.push(std::make_unique(window_, states_, + logger_panel_)); break; case GRAPH_BASED_PLANNERS_IDS::AStar: // A-Star - states_.push(std::make_unique(window_, states_)); + states_.push( + std::make_unique(window_, states_, logger_panel_)); break; default: break; @@ -185,54 +298,17 @@ void Game::setSamplingBasedPlanner(const int id) { switch (id) { case SAMPLING_BASED_PLANNERS_IDS::RRT: // RRT - states_.push(std::make_unique(window_, states_)); + states_.push(std::make_unique( + window_, states_, logger_panel_, SAMPLING_BASED_PLANNERS[id])); break; case SAMPLING_BASED_PLANNERS_IDS::RRT_STAR: // RRTStar - states_.push(std::make_unique(window_, states_)); + states_.push(std::make_unique( + window_, states_, logger_panel_, SAMPLING_BASED_PLANNERS[id])); break; default: break; } } -void Game::renderGui() { - if (ImGui::Button("Choose Planner", ImVec2(210.0f, 0.0f))) - ImGui::OpenPopup("planners_popup"); - ImGui::SameLine(0.0f, 8.0f); - ImGui::TextUnformatted(curr_planner_.c_str()); - if (ImGui::BeginPopup("planners_popup")) { - if (ImGui::BeginMenu("Graph-based Planners")) { - for (int n = 0; n < GRAPH_BASED_PLANNERS.size(); n++) { - bool selected = (GRAPH_BASED_PLANNERS[n] == curr_planner_); - if (ImGui::MenuItem(GRAPH_BASED_PLANNERS[n].c_str(), nullptr, selected, - !selected)) { - if (!selected) { - // change the planner - setGraphBasedPlanner(n); - } - curr_planner_ = GRAPH_BASED_PLANNERS[n]; - } - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Sampling-based Planners")) { - for (int n = 0; n < SAMPLING_BASED_PLANNERS.size(); n++) { - bool selected = (SAMPLING_BASED_PLANNERS[n] == curr_planner_); - if (ImGui::MenuItem(SAMPLING_BASED_PLANNERS[n].c_str(), nullptr, - selected, !selected)) { - if (!selected) { - // change the planner - setSamplingBasedPlanner(n); - } - curr_planner_ = SAMPLING_BASED_PLANNERS[n]; - } - } - ImGui::EndMenu(); - } - - ImGui::EndPopup(); - } -} - } // namespace path_finding_visualizer \ No newline at end of file diff --git a/src/State.cpp b/src/State.cpp index 6233d8d..c56acf7 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -3,8 +3,12 @@ namespace path_finding_visualizer { State::State(sf::RenderWindow *window, - std::stack> &states) - : window_{window}, states_{states}, quit_{false} {} + std::stack> &states, + std::shared_ptr logger_panel) + : window_{window}, + states_{states}, + logger_panel_{logger_panel}, + quit_{false} {} State::~State() {} diff --git a/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp b/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp index 7fd8224..1f7d6b6 100644 --- a/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp +++ b/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp @@ -5,8 +5,9 @@ namespace graph_based { // Constructor ASTAR::ASTAR(sf::RenderWindow *window, - std::stack> &states) - : BFS(window, states) {} + std::stack> &states, + std::shared_ptr logger_panel) + : BFS(window, states, logger_panel) {} // Destructor ASTAR::~ASTAR() {} diff --git a/src/States/Algorithms/GraphBased/BFS/BFS.cpp b/src/States/Algorithms/GraphBased/BFS/BFS.cpp index 14f5c7c..5e9718b 100644 --- a/src/States/Algorithms/GraphBased/BFS/BFS.cpp +++ b/src/States/Algorithms/GraphBased/BFS/BFS.cpp @@ -4,8 +4,9 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -BFS::BFS(sf::RenderWindow* window, std::stack>& states) - : GraphBased(window, states) {} +BFS::BFS(sf::RenderWindow* window, std::stack>& states, + std::shared_ptr logger_panel) + : GraphBased(window, states, logger_panel) {} // Destructor BFS::~BFS() {} diff --git a/src/States/Algorithms/GraphBased/DFS/DFS.cpp b/src/States/Algorithms/GraphBased/DFS/DFS.cpp index 1b7d8a1..6b24e2d 100644 --- a/src/States/Algorithms/GraphBased/DFS/DFS.cpp +++ b/src/States/Algorithms/GraphBased/DFS/DFS.cpp @@ -4,8 +4,9 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -DFS::DFS(sf::RenderWindow* window, std::stack>& states) - : BFS(window, states) {} +DFS::DFS(sf::RenderWindow* window, std::stack>& states, + std::shared_ptr logger_panel) + : BFS(window, states, logger_panel) {} // Destructor DFS::~DFS() {} diff --git a/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp b/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp index 16e9426..fcf12b3 100644 --- a/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp +++ b/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp @@ -5,8 +5,9 @@ namespace graph_based { // Constructor DIJKSTRA::DIJKSTRA(sf::RenderWindow *window, - std::stack> &states) - : BFS(window, states) {} + std::stack> &states, + std::shared_ptr logger_panel) + : BFS(window, states, logger_panel) {} // Destructor DIJKSTRA::~DIJKSTRA() {} diff --git a/src/States/Algorithms/GraphBased/GraphBased.cpp b/src/States/Algorithms/GraphBased/GraphBased.cpp index eefbc53..565174d 100644 --- a/src/States/Algorithms/GraphBased/GraphBased.cpp +++ b/src/States/Algorithms/GraphBased/GraphBased.cpp @@ -5,8 +5,9 @@ namespace graph_based { // Constructor GraphBased::GraphBased(sf::RenderWindow* window, - std::stack>& states) - : State(window, states), keyTimeMax_{1.f}, keyTime_{0.f} { + std::stack>& states, + std::shared_ptr logger_panel) + : State(window, states, logger_panel), keyTimeMax_{1.f}, keyTime_{0.f} { initVariables(); initNodes(); initColors(); @@ -148,6 +149,7 @@ void GraphBased::update(const float& dt) { is_initialized_ = false; is_reset_ = false; is_solved_ = false; + disable_gui_parameters_ = false; message_queue_ = std::make_shared>(); } @@ -166,6 +168,7 @@ void GraphBased::update(const float& dt) { thread_joined_ = false; is_initialized_ = true; + disable_gui_parameters_ = true; } // check the algorithm is solved or not @@ -196,45 +199,6 @@ void GraphBased::clearObstacles() { } void GraphBased::renderGui() { - // buttons - { - // RESET button - { - if (!disable_run_ || is_running_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RESET", ImVec2(103.0f, 0.0f)); - if (!disable_run_ || is_running_) ImGui::EndDisabled(); - if (clicked && !is_running_) { - is_reset_ = true; - disable_gui_parameters_ = false; - disable_run_ = false; - } - } - - ImGui::SameLine(); - - // TODO: PAUSE button - // always disabled (not implemented yet) - { - if (true) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("PAUSE", ImVec2(103.0f, 0.0f)); - if (true) ImGui::EndDisabled(); - } - - ImGui::SameLine(); - - // RUN button - { - if (disable_run_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RUN", ImVec2(103.0f, 0.0f)); - if (disable_run_) ImGui::EndDisabled(); - if (clicked && !is_solved_) { - is_running_ = true; - disable_gui_parameters_ = true; - disable_run_ = true; - } - } - } - if (ImGui::CollapsingHeader("Configuration", ImGuiTreeNodeFlags_DefaultOpen)) { if (disable_gui_parameters_) ImGui::BeginDisabled(); diff --git a/src/States/Algorithms/SamplingBased/RRT/RRT.cpp b/src/States/Algorithms/SamplingBased/RRT/RRT.cpp index 19cf126..eddaf09 100644 --- a/src/States/Algorithms/SamplingBased/RRT/RRT.cpp +++ b/src/States/Algorithms/SamplingBased/RRT/RRT.cpp @@ -4,8 +4,9 @@ namespace path_finding_visualizer { namespace sampling_based { // Constructor -RRT::RRT(sf::RenderWindow *window, std::stack> &states) - : SamplingBased(window, states) { +RRT::RRT(sf::RenderWindow *window, std::stack> &states, + std::shared_ptr logger_panel, const std::string &name) + : SamplingBased(window, states, logger_panel, name) { initParameters(); initialize(); } diff --git a/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp b/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp index 881d83a..f4c4916 100644 --- a/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp +++ b/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp @@ -5,8 +5,10 @@ namespace sampling_based { // Constructor RRT_STAR::RRT_STAR(sf::RenderWindow *window, - std::stack> &states) - : RRT(window, states) { + std::stack> &states, + std::shared_ptr logger_panel, + const std::string &name) + : RRT(window, states, logger_panel, name) { initParameters(); initialize(); } diff --git a/src/States/Algorithms/SamplingBased/SamplingBased.cpp b/src/States/Algorithms/SamplingBased/SamplingBased.cpp index 8bde525..e7e607a 100644 --- a/src/States/Algorithms/SamplingBased/SamplingBased.cpp +++ b/src/States/Algorithms/SamplingBased/SamplingBased.cpp @@ -5,8 +5,11 @@ namespace sampling_based { // Constructor SamplingBased::SamplingBased(sf::RenderWindow* window, - std::stack>& states) - : State(window, states), key_time_max_{1.f}, key_time_{0.f} { + std::stack>& states, + std::shared_ptr logger_panel, + const std::string& name) + : State(window, states, logger_panel), key_time_max_{1.f}, key_time_{0.f} { + logger_panel_->info("Initialize " + name + " planner"); initVariables(); start_vertex_ = std::make_shared(); @@ -86,6 +89,7 @@ void SamplingBased::update(const float& dt) { is_initialized_ = false; is_reset_ = false; is_solved_ = false; + disable_gui_parameters_ = false; std::unique_lock lck(mutex_); is_stopped_ = true; @@ -115,6 +119,9 @@ void SamplingBased::update(const float& dt) { is_stopped_ = false; lck.unlock(); + logger_panel_->info("Planning started with " + + std::to_string(max_iterations_) + " iterations."); + // create thread // solve the algorithm concurrently t_ = std::thread(&SamplingBased::solveConcurrently, this, start_vertex_, @@ -122,6 +129,7 @@ void SamplingBased::update(const float& dt) { thread_joined_ = false; is_initialized_ = true; + disable_gui_parameters_ = true; } // check the algorithm is solved or not @@ -132,6 +140,8 @@ void SamplingBased::update(const float& dt) { thread_joined_ = true; is_running_ = false; is_solved_ = true; + logger_panel_->info( + "Iterations number reach max limit. Planning stopped."); } } else { // only allow mouse and key inputs @@ -206,45 +216,6 @@ void SamplingBased::renderObstacles() { void SamplingBased::clearObstacles() { obstacles_.clear(); } void SamplingBased::renderGui() { - // buttons - { - // RESET button - { - if (!disable_run_ || is_running_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RESET", ImVec2(103.f, 0.f)); - if (!disable_run_ || is_running_) ImGui::EndDisabled(); - if (clicked && !is_running_) { - is_reset_ = true; - disable_gui_parameters_ = false; - disable_run_ = false; - } - } - - ImGui::SameLine(); - - // TODO: PAUSE button - // always disabled (not implemented yet) - { - if (true) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("PAUSE", ImVec2(103.f, 0.f)); - if (true) ImGui::EndDisabled(); - } - - ImGui::SameLine(); - - // RUN button - { - if (disable_run_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RUN", ImVec2(103.f, 0.f)); - if (disable_run_) ImGui::EndDisabled(); - if (clicked && !is_solved_) { - is_running_ = true; - disable_gui_parameters_ = true; - disable_run_ = true; - } - } - } - { std::unique_lock iter_no_lck(iter_no_mutex_); const float progress = static_cast( @@ -269,10 +240,13 @@ void SamplingBased::renderGui() { { if (ImGui::Button("CLEAR OBSTACLES", ImVec2(156.5f, 0.f))) { clearObstacles(); + logger_panel_->info("Successfully removed all the obstacles."); } ImGui::SameLine(); if (ImGui::Button("RESET PARAMETERS", ImVec2(156.5f, 0.f))) { initParameters(); + logger_panel_->info( + "Planner related parameters resetted to default ones."); } } From c9a723ffccd7232679317b4f5714fc4dbb88e8a0 Mon Sep 17 00:00:00 2001 From: mlsdpk Date: Wed, 1 Dec 2021 15:00:26 +0630 Subject: [PATCH 3/4] Major updates to the visualizer - Integrate ImGui docking - Improve UI - Window and panels can now be resizable and fullscreen - Refactor graph and sampling-based classes --- dependencies/CMakeLists.txt | 3 +- include/Game.h | 5 +- include/State.h | 20 +- .../Algorithms/GraphBased/ASTAR/ASTAR.h | 10 +- .../States/Algorithms/GraphBased/BFS/BFS.h | 12 +- .../States/Algorithms/GraphBased/DFS/DFS.h | 10 +- .../Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h | 10 +- .../States/Algorithms/GraphBased/GraphBased.h | 40 ++-- include/States/Algorithms/GraphBased/Node.h | 32 +-- include/States/Algorithms/GraphBased/Utils.h | 18 +- .../States/Algorithms/SamplingBased/RRT/RRT.h | 5 +- .../SamplingBased/RRT_STAR/RRT_STAR.h | 3 +- .../Algorithms/SamplingBased/SamplingBased.h | 15 +- src/Game.cpp | 196 +++++++++++++----- src/State.cpp | 23 +- .../Algorithms/GraphBased/ASTAR/ASTAR.cpp | 105 +++------- src/States/Algorithms/GraphBased/BFS/BFS.cpp | 120 +++++------ src/States/Algorithms/GraphBased/DFS/DFS.cpp | 77 ++----- .../GraphBased/DIJKSTRA/DIJKSTRA.cpp | 91 +++----- .../Algorithms/GraphBased/GraphBased.cpp | 72 +++++-- src/States/Algorithms/GraphBased/Node.cpp | 12 +- .../Algorithms/SamplingBased/RRT/RRT.cpp | 78 +++---- .../SamplingBased/RRT_STAR/RRT_STAR.cpp | 6 +- .../SamplingBased/SamplingBased.cpp | 76 ++++--- src/main.cpp | 14 +- 25 files changed, 528 insertions(+), 525 deletions(-) diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index ecd79ea..c65b7c1 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -11,7 +11,8 @@ add_subdirectory(sfml) FetchContent_Declare( imgui GIT_REPOSITORY https://github.com/ocornut/imgui - GIT_TAG 55d35d8387c15bf0cfd71861df67af8cfbda7456 + # GIT_TAG 55d35d8387c15bf0cfd71861df67af8cfbda7456 + GIT_TAG 719d9313041b85227a3e6deb289a313819aaeab3 # latest commit of docking-branch ) FetchContent_Declare( diff --git a/include/Game.h b/include/Game.h index 42de7e5..d13d369 100644 --- a/include/Game.h +++ b/include/Game.h @@ -22,7 +22,7 @@ enum SAMPLING_BASED_PLANNERS_IDS { RRT, RRT_STAR }; class Game { public: // Constructors - Game(sf::RenderWindow* window); + Game(sf::RenderWindow* window, sf::RenderTexture* render_texture); // Destructors virtual ~Game(); @@ -44,6 +44,9 @@ class Game { private: sf::RenderWindow* window_; + sf::RenderTexture* render_texture_; + sf::Vector2f view_move_xy_; + ImVec2 mouse_pos_in_canvas_; sf::Event ev_; sf::Clock dtClock_; float dt_; diff --git a/include/State.h b/include/State.h index f9033fe..324869d 100644 --- a/include/State.h +++ b/include/State.h @@ -22,37 +22,29 @@ namespace path_finding_visualizer { class State { private: protected: - std::stack> &states_; - sf::RenderWindow *window_; std::shared_ptr logger_panel_; - sf::Vector2i mousePositionWindow_; - bool quit_; + sf::Vector2f mousePositionWindow_; bool is_reset_; bool is_running_; public: // Constructor - State(sf::RenderWindow *window, std::stack> &states, - std::shared_ptr logger_panel); + State(std::shared_ptr logger_panel); // Destructor virtual ~State(); - // Accessors - const bool getQuit() const; - void setReset(bool is_reset) { is_reset_ = is_reset; } void setRunning(bool is_running) { is_running_ = is_running; } // Functions - virtual void checkForQuit(); - virtual void updateMousePosition(); + void updateMousePosition(const ImVec2 &mousePos); // virtual functions virtual void endState() = 0; - virtual void updateKeybinds() = 0; - virtual void update(const float &dt) = 0; - virtual void render() = 0; + virtual void update(const float &dt, const ImVec2 &mousePos) = 0; + virtual void renderConfig() = 0; + virtual void renderScene(sf::RenderTexture &render_texture) = 0; }; } // namespace path_finding_visualizer \ No newline at end of file diff --git a/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h b/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h index 8c4ccd8..bbab3e7 100644 --- a/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h +++ b/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h @@ -21,17 +21,17 @@ struct MinimumDistanceASTAR { class ASTAR : public BFS { public: // Constructor - ASTAR(sf::RenderWindow *window, std::stack> &states, - std::shared_ptr logger_panel); + ASTAR(std::shared_ptr logger_panel); // Destructor virtual ~ASTAR(); // Overriden functions virtual void initAlgorithm() override; - virtual void solveConcurrently( - std::shared_ptr nodeStart, std::shared_ptr nodeEnd, - std::shared_ptr> message_queue) override; + + // override main update function + virtual void updatePlanner(bool &solved, Node &start_node, + Node &end_node) override; protected: // ASTAR related diff --git a/include/States/Algorithms/GraphBased/BFS/BFS.h b/include/States/Algorithms/GraphBased/BFS/BFS.h index 76b7557..ebc3894 100644 --- a/include/States/Algorithms/GraphBased/BFS/BFS.h +++ b/include/States/Algorithms/GraphBased/BFS/BFS.h @@ -11,8 +11,7 @@ namespace graph_based { class BFS : public GraphBased { public: // Constructor - BFS(sf::RenderWindow *window, std::stack> &states, - std::shared_ptr logger_panel); + BFS(std::shared_ptr logger_panel); // Destructor virtual ~BFS(); @@ -24,13 +23,12 @@ class BFS : public GraphBased { virtual void updateNodes() override; // override render functions - virtual void renderNodes() override; + virtual void renderNodes(sf::RenderTexture &render_texture) override; virtual void renderParametersGui() override; - // BFS algorithm function - virtual void solveConcurrently( - std::shared_ptr nodeStart, std::shared_ptr nodeEnd, - std::shared_ptr> message_queue) override; + // override main update function + virtual void updatePlanner(bool &solved, Node &start_node, + Node &end_node) override; private: // BFS related diff --git a/include/States/Algorithms/GraphBased/DFS/DFS.h b/include/States/Algorithms/GraphBased/DFS/DFS.h index 702136f..88708e1 100644 --- a/include/States/Algorithms/GraphBased/DFS/DFS.h +++ b/include/States/Algorithms/GraphBased/DFS/DFS.h @@ -10,8 +10,7 @@ namespace graph_based { class DFS : public BFS { public: // Constructor - DFS(sf::RenderWindow *window, std::stack> &states, - std::shared_ptr logger_panel); + DFS(std::shared_ptr logger_panel); // Destructor virtual ~DFS(); @@ -19,10 +18,9 @@ class DFS : public BFS { // override initialization Functions void initAlgorithm() override; - // DFS algorithm function - void solveConcurrently( - std::shared_ptr nodeStart, std::shared_ptr nodeEnd, - std::shared_ptr> message_queue) override; + // override main update function + virtual void updatePlanner(bool &solved, Node &start_node, + Node &end_node) override; private: // DFS related diff --git a/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h b/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h index 8363d16..07f0bb0 100644 --- a/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h +++ b/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h @@ -21,17 +21,17 @@ struct MinimumDistanceDIJKSTRA { class DIJKSTRA : public BFS { public: // Constructor - DIJKSTRA(sf::RenderWindow *window, std::stack> &states, - std::shared_ptr logger_panel); + DIJKSTRA(std::shared_ptr logger_panel); // Destructor virtual ~DIJKSTRA(); // Overriden functions virtual void initAlgorithm() override; - void solveConcurrently( - std::shared_ptr nodeStart, std::shared_ptr nodeEnd, - std::shared_ptr> message_queue) override; + + // override main update function + virtual void updatePlanner(bool &solved, Node &start_node, + Node &end_node) override; protected: // DIJKSTRA related diff --git a/include/States/Algorithms/GraphBased/GraphBased.h b/include/States/Algorithms/GraphBased/GraphBased.h index 08803ce..471d580 100644 --- a/include/States/Algorithms/GraphBased/GraphBased.h +++ b/include/States/Algorithms/GraphBased/GraphBased.h @@ -22,32 +22,41 @@ namespace graph_based { class GraphBased : public State { public: // Constructor - GraphBased(sf::RenderWindow* window, - std::stack>& states, - std::shared_ptr logger_panel); + GraphBased(std::shared_ptr logger_panel); // Destructor virtual ~GraphBased(); // Override Functions void endState() override; - void updateKeybinds() override; - void update(const float& dt) override; - void render() override; + void update(const float& dt, const ImVec2& mousePos) override; + void renderConfig() override; + void renderScene(sf::RenderTexture& render_texture) override; // virtual functions virtual void clearObstacles(); virtual void renderGui(); // render planner specific parameters virtual void renderParametersGui() = 0; - virtual void renderNodes() = 0; + virtual void renderNodes(sf::RenderTexture& render_texture) = 0; virtual void updateNodes() = 0; virtual void initAlgorithm() = 0; - virtual void solveConcurrently( - std::shared_ptr nodeStart, std::shared_ptr nodeEnd, - std::shared_ptr> message_queue) = 0; + // pure virtual function need to be implemented by graph-based planners + virtual void updatePlanner(bool& solved, Node& node_start, + Node& node_end) = 0; + + void solveConcurrently(std::shared_ptr nodeStart, + std::shared_ptr nodeEnd, + std::shared_ptr> message_queue); + void updateKeyTime(const float& dt); + const bool getKeyTime(); protected: + // initialization Functions + void initColors(); + void initVariables(); + void initNodes(bool reset = true, bool reset_neighbours_only = false); + // colors sf::Color BGN_COL, FONT_COL, IDLE_COL, HOVER_COL, ACTIVE_COL, START_COL, END_COL, VISITED_COL, FRONTIER_COL, OBST_COL, PATH_COL; @@ -57,8 +66,11 @@ class GraphBased : public State { float keyTimeMax_; // Map Variables + int no_of_grid_rows_; + int no_of_grid_cols_; int gridSize_; int slider_grid_size_; + sf::Vector2f init_grid_xy_; // 0 = 4 connected grid, 1 = 8 connected grid int grid_connectivity_; unsigned int mapWidth_; @@ -82,14 +94,6 @@ class GraphBased : public State { // threads std::thread t_; bool thread_joined_; - - // initialization Functions - void initColors(); - void initVariables(); - void initNodes(bool reset = true, bool reset_neighbours_only = false); - - void updateKeyTime(const float& dt); - const bool getKeyTime(); }; } // namespace graph_based diff --git a/include/States/Algorithms/GraphBased/Node.h b/include/States/Algorithms/GraphBased/Node.h index dbc5767..60e30f8 100644 --- a/include/States/Algorithms/GraphBased/Node.h +++ b/include/States/Algorithms/GraphBased/Node.h @@ -10,18 +10,6 @@ namespace path_finding_visualizer { namespace graph_based { class Node { - private: - // Variables - bool isObstacle_; - bool isVisited_; - bool isFrontier_; - bool isPath_; - sf::Vector2i pos_; - std::vector> vecNeighbours_; - std::shared_ptr parent_; - double gDist_; - double fDist_; - public: // Constructor Node(); @@ -34,9 +22,11 @@ class Node { const bool isVisited() const; const bool isFrontier() const; const bool isPath() const; + const bool isStart() const; + const bool isGoal() const; // Accessors - sf::Vector2i getPos(); + sf::Vector2i getPos() const; std::shared_ptr getParentNode(); const std::vector>* getNeighbours() const; const double getGDistance() const; @@ -47,12 +37,28 @@ class Node { void setVisited(bool b); void setFrontier(bool b); void setPath(bool b); + void setStart(bool b); + void setGoal(bool b); void setPosition(sf::Vector2i pos); void setNeighbours(std::shared_ptr node); void clearNeighbours(); void setParentNode(std::shared_ptr node); void setGDistance(double dist); void setFDistance(double dist); + + protected: + // Variables + bool isObstacle_; + bool isVisited_; + bool isFrontier_; + bool isPath_; + bool isStart_; + bool isGoal_; + sf::Vector2i pos_; + std::vector> vecNeighbours_; + std::shared_ptr parent_; + double gDist_; + double fDist_; }; } // namespace graph_based diff --git a/include/States/Algorithms/GraphBased/Utils.h b/include/States/Algorithms/GraphBased/Utils.h index 1f0d659..1df9062 100644 --- a/include/States/Algorithms/GraphBased/Utils.h +++ b/include/States/Algorithms/GraphBased/Utils.h @@ -8,23 +8,21 @@ namespace path_finding_visualizer { namespace graph_based { namespace utils { -inline double distanceCost(const std::shared_ptr &n1, - const std::shared_ptr &n2) { +inline double distanceCost(const Node &n1, const Node &n2) { return std::sqrt( - (n1->getPos().x - n2->getPos().x) * (n1->getPos().x - n2->getPos().x) + - (n1->getPos().y - n2->getPos().y) * (n1->getPos().y - n2->getPos().y)); + (n1.getPos().x - n2.getPos().x) * (n1.getPos().x - n2.getPos().x) + + (n1.getPos().y - n2.getPos().y) * (n1.getPos().y - n2.getPos().y)); } -inline double costToGoHeuristics(const std::shared_ptr &n1, - const std::shared_ptr &n2, +inline double costToGoHeuristics(const Node &n1, const Node &n2, bool use_manhattan = false) { if (use_manhattan) - return fabs(n1->getPos().x - n2->getPos().x) + - fabs(n1->getPos().y - n2->getPos().y); + return fabs(n1.getPos().x - n2.getPos().x) + + fabs(n1.getPos().y - n2.getPos().y); return std::sqrt( - (n1->getPos().x - n2->getPos().x) * (n1->getPos().x - n2->getPos().x) + - (n1->getPos().y - n2->getPos().y) * (n1->getPos().y - n2->getPos().y)); + (n1.getPos().x - n2.getPos().x) * (n1.getPos().x - n2.getPos().x) + + (n1.getPos().y - n2.getPos().y) * (n1.getPos().y - n2.getPos().y)); } inline void addNeighbours(std::vector> &nodes, diff --git a/include/States/Algorithms/SamplingBased/RRT/RRT.h b/include/States/Algorithms/SamplingBased/RRT/RRT.h index 0a340be..8efcc2f 100644 --- a/include/States/Algorithms/SamplingBased/RRT/RRT.h +++ b/include/States/Algorithms/SamplingBased/RRT/RRT.h @@ -11,8 +11,7 @@ namespace sampling_based { class RRT : public SamplingBased { public: // Constructor - RRT(sf::RenderWindow *window, std::stack> &states, - std::shared_ptr logger_panel, const std::string &name); + RRT(std::shared_ptr logger_panel, const std::string &name); // Destructor virtual ~RRT(); @@ -23,7 +22,7 @@ class RRT : public SamplingBased { virtual void initParameters() override; // override render functions - virtual void renderPlannerData() override; + virtual void renderPlannerData(sf::RenderTexture &render_texture) override; virtual void renderParametersGui() override; // override main update function diff --git a/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h b/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h index a82ba6a..90f3990 100644 --- a/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h +++ b/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h @@ -10,8 +10,7 @@ namespace sampling_based { class RRT_STAR : public RRT { public: // Constructor - RRT_STAR(sf::RenderWindow* window, std::stack>& states, - std::shared_ptr logger_panel, const std::string& name); + RRT_STAR(std::shared_ptr logger_panel, const std::string& name); // Destructor virtual ~RRT_STAR(); diff --git a/include/States/Algorithms/SamplingBased/SamplingBased.h b/include/States/Algorithms/SamplingBased/SamplingBased.h index d553dad..83aa7c9 100644 --- a/include/States/Algorithms/SamplingBased/SamplingBased.h +++ b/include/States/Algorithms/SamplingBased/SamplingBased.h @@ -31,9 +31,7 @@ struct Vertex { class SamplingBased : public State { public: // Constructor - SamplingBased(sf::RenderWindow *window, - std::stack> &states, - std::shared_ptr logger_panel, + SamplingBased(std::shared_ptr logger_panel, const std::string &name); // Destructor @@ -41,12 +39,12 @@ class SamplingBased : public State { // Override Functions void endState() override; - void updateKeybinds() override; - void update(const float &dt) override; - void render() override; + void update(const float &dt, const ImVec2 &mousePos) override; + void renderConfig() override; + void renderScene(sf::RenderTexture &render_texture) override; void updateUserInput(); - void renderObstacles(); + void renderObstacles(sf::RenderTexture &render_texture); void clearObstacles(); void initVariables(); void updateKeyTime(const float &dt); @@ -58,7 +56,7 @@ class SamplingBased : public State { // all the sampling-based planners need to override this functions // rendering function for algorithm specific - virtual void renderPlannerData() = 0; + virtual void renderPlannerData(sf::RenderTexture &render_texture) = 0; // render planner specific parameters virtual void renderParametersGui() = 0; @@ -88,6 +86,7 @@ class SamplingBased : public State { float key_time_max_; // Map related + sf::Vector2f init_grid_xy_; unsigned int obst_size_; unsigned int map_width_; unsigned int map_height_; diff --git a/src/Game.cpp b/src/Game.cpp index 20aa285..38b751a 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -17,14 +17,13 @@ using rrtstar_state_type = path_finding_visualizer::sampling_based::RRT_STAR; namespace path_finding_visualizer { // Constructor -Game::Game(sf::RenderWindow* window) : window_{window} { - disable_run_ = false; +Game::Game(sf::RenderWindow* window, sf::RenderTexture* render_texture) + : window_{window}, render_texture_{render_texture}, disable_run_{false} { logger_panel_ = std::make_shared(); curr_planner_ = GRAPH_BASED_PLANNERS[0]; // manually add BFS for now - states_.push( - std::make_unique(window_, states_, logger_panel_)); - + states_.push(std::make_unique(logger_panel_)); + view_move_xy_.x = view_move_xy_.y = 0.f; initGuiTheme(); } @@ -38,6 +37,7 @@ const bool Game::running() const { return window_->isOpen(); } void Game::pollEvents() { // Event polling while (window_->pollEvent(ev_)) { + // ImGui::SFML::ProcessEvent(ev_); ImGui::SFML::ProcessEvent(ev_); switch (ev_.type) { case sf::Event::Closed: @@ -57,12 +57,7 @@ void Game::update() { updateDt(); if (!states_.empty()) { - states_.top()->update(dt_); - - if (states_.top()->getQuit()) { - states_.top()->endState(); - states_.pop(); - } + states_.top()->update(dt_, mouse_pos_in_canvas_); } else { // End the Application window_->close(); @@ -149,25 +144,77 @@ void Game::renderMenuBar(ImGuiIO& io) { } void Game::render() { - window_->clear(sf::Color::White); + window_->clear(); + render_texture_->clear(sf::Color::White); if (!states_.empty()) { - ImGui::SetNextWindowPos(ImVec2(0.f, 0.f), ImGuiCond_Once); - ImGui::SetNextWindowSize(ImVec2(332.f, 536.f), ImGuiCond_None); + // DOCKING STUFFS + static bool opt_dockspace = true; + static bool opt_padding = false; + static bool opt_fullscreen = true; + static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; + + // We are using the ImGuiWindowFlags_NoDocking flag to make the parent + // window not dockable into, because it would be confusing to have two + // docking targets within each others. + ImGuiWindowFlags window_flags = + ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; + if (opt_fullscreen) { + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + window_flags |= ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove; + window_flags |= + ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + } else { + dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode; + } + + if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) + window_flags |= ImGuiWindowFlags_NoBackground; - ImGui::Begin("path_finding_visualizer", nullptr, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_MenuBar); + if (!opt_padding) + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("DockSpace Demo", &opt_dockspace, window_flags); + if (!opt_padding) ImGui::PopStyleVar(); + if (opt_fullscreen) ImGui::PopStyleVar(2); + // Submit the DockSpace ImGuiIO& io = ImGui::GetIO(); + ImGuiStyle& style = ImGui::GetStyle(); + float min_window_size_x = style.WindowMinSize.x; + style.WindowMinSize.x = 332.f; + if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) { + ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); + } + style.WindowMinSize.x = min_window_size_x; + + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Close", NULL, false)) window_->close(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + //////////////////////////////// + // Configurations + //////////////////////////////// + ImGui::Begin("Configurations", nullptr, ImGuiWindowFlags_MenuBar); renderMenuBar(io); ImGui::Text("Current Planner: %s", curr_planner_.c_str()); ImGui::Spacing(); ImGui::Spacing(); - states_.top()->render(); + // render planner specific configurations + states_.top()->renderConfig(); if (show_how_to_use_window_) { if (ImGui::CollapsingHeader("How To Use")) { @@ -210,25 +257,66 @@ void Game::render() { } } + ImGui::End(); // end Configurations + //////////////////////////////// + + ////////////////////////// + // Console Panel + ////////////////////////// + ImGui::Begin("Console"); ImGui::End(); + ////////////////////////// + + ////////////////////////////////////////////////////////////////////// + // Planning Scene Panel + ////////////////////////////////////////////////////////////////////// + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.f, 0.f}); + ImGui::Begin("Planning Scene"); + + const ImVec2 planning_scene_panel_size = ImGui::GetContentRegionAvail(); + render_texture_->create(static_cast(planning_scene_panel_size.x), + static_cast(planning_scene_panel_size.y)); + render_texture_->clear(sf::Color::White); + + sf::View view; + view.setSize( + sf::Vector2f(planning_scene_panel_size.x, planning_scene_panel_size.y)); + view.setCenter( + sf::Vector2f((planning_scene_panel_size.x / 2.f) + view_move_xy_.x, + (planning_scene_panel_size.y / 2.f) + view_move_xy_.y)); + render_texture_->setView(view); + states_.top()->renderScene(*render_texture_); + + ImGui::ImageButton(*render_texture_, 0); + + const bool is_hovered = ImGui::IsItemHovered(); // Hovered + const bool is_active = ImGui::IsItemActive(); // Held + + // move the planning scene around by dragging mouse Right-click + if (is_hovered && ImGui::IsMouseDragging(ImGuiMouseButton_Right)) { + view_move_xy_.x -= io.MouseDelta.x; + view_move_xy_.y -= io.MouseDelta.y; + } - ImGui::SetNextWindowPos(ImVec2(0.f, 536.f), ImGuiCond_Once); - ImGui::SetNextWindowSize( - ImVec2(332.f, static_cast(window_->getSize().y) - 536.f), - ImGuiCond_None); + // Update the current mouse position in planning scene panel + ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); + const ImVec2 origin(canvas_p0.x - view_move_xy_.x, + canvas_p0.y - view_move_xy_.y); + mouse_pos_in_canvas_.x = io.MousePos.x - origin.x; + mouse_pos_in_canvas_.y = + io.MousePos.y - origin.y + planning_scene_panel_size.y; - ImGui::Begin("Console", nullptr, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse); ImGui::End(); + ImGui::PopStyleVar(); + ////////////////////////////////////////////////////////////////////// - // Actually call in the regular Log helper (which will Begin() into the same - // window as we just did) logger_panel_->render("Console"); - } + ImGui::End(); // dockspace end - ImGui::SFML::Render(*window_); - window_->display(); + ImGui::SFML::Render(*window_); + window_->display(); + render_texture_->display(); + } } void Game::initGuiTheme() { @@ -237,57 +325,67 @@ void Game::initGuiTheme() { io.Fonts->AddFontFromFileTTF("../fonts/OpenSans/OpenSans-Regular.ttf", 18.0f); ImGui::SFML::UpdateFontTexture(); - ImGui::StyleColorsDark(); + // enable docking + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGuiStyle* style = &ImGui::GetStyle(); - style->FramePadding = ImVec2(8.f, 8.f); - style->ItemSpacing = ImVec2(8.0f, 4.0f); // dark theme colors auto& colors = ImGui::GetStyle().Colors; - colors[ImGuiCol_WindowBg] = ImVec4(0.1f, 0.105f, 0.11f, 1.0f); + // headers + colors[ImGuiCol_Header] = ImVec4(0.2f, 0.205f, 0.21f, 1.0f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.3f, 0.305f, 0.31f, 1.0f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.15f, 0.1505f, 0.151f, 1.0f); + + // buttons colors[ImGuiCol_Button] = ImVec4(0.2f, 0.205f, 0.21f, 1.0f); colors[ImGuiCol_ButtonHovered] = ImVec4(0.3f, 0.305f, 0.31f, 1.0f); colors[ImGuiCol_ButtonActive] = ImVec4(0.15f, 0.1505f, 0.151f, 1.0f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.3f, 0.305f, 0.31f, 1.0f); - colors[ImGuiCol_Header] = ImVec4(0.15f, 0.1505f, 0.151f, 1.0f); + // tabs + colors[ImGuiCol_Tab] = ImVec4(0.15f, 0.1505f, 0.151f, 1.0f); + colors[ImGuiCol_TabHovered] = ImVec4(0.38, 0.3805f, 0.381f, 1.0f); + colors[ImGuiCol_TabActive] = ImVec4(0.28, 0.2805f, 0.281f, 1.0f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.15f, 0.1505f, 0.151f, 1.0f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.2f, 0.205f, 0.21f, 1.0f); + // frame background colors[ImGuiCol_FrameBg] = ImVec4(0.2f, 0.205f, 0.21f, 1.0f); colors[ImGuiCol_FrameBgHovered] = ImVec4(0.3f, 0.305f, 0.31f, 1.0f); colors[ImGuiCol_FrameBgActive] = ImVec4(0.15f, 0.1505f, 0.151f, 1.0f); - colors[ImGuiCol_PopupBg] = ImVec4(0.2f, 0.205f, 0.21f, 1.0f); - + // sider colors[ImGuiCol_SliderGrab] = colors[ImGuiCol_Text]; colors[ImGuiCol_SliderGrabActive] = colors[ImGuiCol_Text]; + // progress bar colors[ImGuiCol_PlotHistogram] = ImVec4(0.3f, 0.305f, 0.31f, 1.0f); + + // title + colors[ImGuiCol_TitleBg] = ImVec4(0.15f, 0.1505f, 0.151f, 1.0f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.15f, 0.1505f, 0.151f, 1.0f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.15f, 0.1505f, 0.151f, 1.0f); } void Game::setGraphBasedPlanner(const int id) { switch (id) { case GRAPH_BASED_PLANNERS_IDS::BFS: // BFS - states_.push( - std::make_unique(window_, states_, logger_panel_)); + states_.push(std::make_unique(logger_panel_)); break; case GRAPH_BASED_PLANNERS_IDS::DFS: // DFS - states_.push( - std::make_unique(window_, states_, logger_panel_)); + states_.push(std::make_unique(logger_panel_)); break; case GRAPH_BASED_PLANNERS_IDS::DIJKSTRA: // Dijkstra - states_.push(std::make_unique(window_, states_, - logger_panel_)); + states_.push(std::make_unique(logger_panel_)); break; case GRAPH_BASED_PLANNERS_IDS::AStar: // A-Star - states_.push( - std::make_unique(window_, states_, logger_panel_)); + states_.push(std::make_unique(logger_panel_)); break; default: break; @@ -299,12 +397,12 @@ void Game::setSamplingBasedPlanner(const int id) { case SAMPLING_BASED_PLANNERS_IDS::RRT: // RRT states_.push(std::make_unique( - window_, states_, logger_panel_, SAMPLING_BASED_PLANNERS[id])); + logger_panel_, SAMPLING_BASED_PLANNERS[id])); break; case SAMPLING_BASED_PLANNERS_IDS::RRT_STAR: // RRTStar states_.push(std::make_unique( - window_, states_, logger_panel_, SAMPLING_BASED_PLANNERS[id])); + logger_panel_, SAMPLING_BASED_PLANNERS[id])); break; default: break; diff --git a/src/State.cpp b/src/State.cpp index c56acf7..9ab5f89 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -2,27 +2,14 @@ namespace path_finding_visualizer { -State::State(sf::RenderWindow *window, - std::stack> &states, - std::shared_ptr logger_panel) - : window_{window}, - states_{states}, - logger_panel_{logger_panel}, - quit_{false} {} +State::State(std::shared_ptr logger_panel) + : logger_panel_{logger_panel} {} State::~State() {} -const bool State::getQuit() const { return quit_; } - -// TODO: Check escape not working properly -void State::checkForQuit() { - if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)) { - quit_ = true; - } -} - -void State::updateMousePosition() { - mousePositionWindow_ = sf::Mouse::getPosition(*window_); +void State::updateMousePosition(const ImVec2& mousePos) { + mousePositionWindow_.x = mousePos.x; + mousePositionWindow_.y = mousePos.y; } } // namespace path_finding_visualizer \ No newline at end of file diff --git a/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp b/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp index 1f7d6b6..cbbb0de 100644 --- a/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp +++ b/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp @@ -4,10 +4,7 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -ASTAR::ASTAR(sf::RenderWindow *window, - std::stack> &states, - std::shared_ptr logger_panel) - : BFS(window, states, logger_panel) {} +ASTAR::ASTAR(std::shared_ptr logger_panel) : BFS(logger_panel) {} // Destructor ASTAR::~ASTAR() {} @@ -23,89 +20,45 @@ void ASTAR::initAlgorithm() { nodeStart_->setGDistance(0.0); nodeStart_->setFDistance(utils::costToGoHeuristics( - nodeStart_, nodeEnd_, use_manhattan_heuristics_)); + *nodeStart_, *nodeEnd_, use_manhattan_heuristics_)); frontier_.push(nodeStart_); } -void ASTAR::solveConcurrently( - std::shared_ptr nodeStart, std::shared_ptr nodeEnd, - std::shared_ptr> message_queue) { - // copy assignment - // thread-safe due to shared_ptrs - std::shared_ptr s_nodeStart = nodeStart; - std::shared_ptr s_nodeEnd = nodeEnd; - std::shared_ptr> s_message_queue = message_queue; - - bool solved = false; - - double cycleDuration = 1; // duration of a single simulation cycle in ms - // init stop watch - auto lastUpdate = std::chrono::system_clock::now(); - - while (true) { - // compute time difference to stop watch - long timeSinceLastUpdate = - std::chrono::duration_cast( - std::chrono::system_clock::now() - lastUpdate) - .count(); +void ASTAR::updatePlanner(bool &solved, Node &start_node, Node &end_node) { + if (!frontier_.empty()) { + std::shared_ptr node_current = frontier_.top(); + node_current->setFrontier(false); + node_current->setVisited(true); + frontier_.pop(); - if (timeSinceLastUpdate >= cycleDuration) { - //////////////////////////// - // run the main algorithm // - //////////////////////////// + if (node_current->isGoal()) { + solved = true; + } - if (!frontier_.empty()) { - std::shared_ptr nodeCurrent = frontier_.top(); - nodeCurrent->setFrontier(false); - nodeCurrent->setVisited(true); - frontier_.pop(); + for (auto node_neighbour : *node_current->getNeighbours()) { + if (node_neighbour->isVisited() || node_neighbour->isObstacle()) { + continue; + } - if (nodeCurrent == s_nodeEnd) { - solved = true; - } + double dist = node_current->getGDistance() + + utils::costToGoHeuristics(*node_current, *node_neighbour, + use_manhattan_heuristics_); - for (auto nodeNeighbour : *nodeCurrent->getNeighbours()) { - if (nodeNeighbour->isVisited() || nodeNeighbour->isObstacle()) { - continue; - } + if (dist < node_neighbour->getGDistance()) { + node_neighbour->setParentNode(node_current); + node_neighbour->setGDistance(dist); - double dist = nodeCurrent->getGDistance() + - utils::costToGoHeuristics(nodeCurrent, nodeNeighbour, + // f = g + h + double f_dist = node_current->getGDistance() + + utils::costToGoHeuristics(*node_neighbour, end_node, use_manhattan_heuristics_); - - if (dist < nodeNeighbour->getGDistance()) { - nodeNeighbour->setParentNode(nodeCurrent); - nodeNeighbour->setGDistance(dist); - - // f = g + h - double f_dist = - nodeCurrent->getGDistance() + - utils::costToGoHeuristics(nodeNeighbour, s_nodeEnd, - use_manhattan_heuristics_); - nodeNeighbour->setFDistance(f_dist); - nodeNeighbour->setFrontier(true); - frontier_.push(nodeNeighbour); - } - } - } else { - solved = true; + node_neighbour->setFDistance(f_dist); + node_neighbour->setFrontier(true); + frontier_.push(node_neighbour); } - - //////////////////////////// - - // reset stop watch for next cycle - lastUpdate = std::chrono::system_clock::now(); } - - // sends an update method to the message queue using move semantics - auto ftr = std::async(std::launch::async, &MessageQueue::send, - s_message_queue, std::move(solved)); - ftr.wait(); - - if (solved) return; - - // sleep at every iteration to reduce CPU usage - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + solved = true; } } diff --git a/src/States/Algorithms/GraphBased/BFS/BFS.cpp b/src/States/Algorithms/GraphBased/BFS/BFS.cpp index 5e9718b..295e391 100644 --- a/src/States/Algorithms/GraphBased/BFS/BFS.cpp +++ b/src/States/Algorithms/GraphBased/BFS/BFS.cpp @@ -4,9 +4,8 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -BFS::BFS(sf::RenderWindow* window, std::stack>& states, - std::shared_ptr logger_panel) - : GraphBased(window, states, logger_panel) {} +BFS::BFS(std::shared_ptr logger_panel) + : GraphBased(logger_panel) {} // Destructor BFS::~BFS() {} @@ -24,8 +23,8 @@ void BFS::initAlgorithm() { // override updateNodes() function void BFS::updateNodes() { if (sf::Mouse::isButtonPressed(sf::Mouse::Left) && getKeyTime()) { - int localY = ((mousePositionWindow_.x - 350) / gridSize_); - int localX = ((mousePositionWindow_.y - 18) / gridSize_); + int localY = ((mousePositionWindow_.x - init_grid_xy_.x) / gridSize_); + int localX = ((mousePositionWindow_.y - init_grid_xy_.y) / gridSize_); if (localX >= 0 && localX < mapHeight_ / gridSize_) { if (localY >= 0 && localY < mapWidth_ / gridSize_) { @@ -42,11 +41,19 @@ void BFS::updateNodes() { if (!is_solved_) { if (sf::Keyboard::isKeyPressed(sf::Keyboard::LShift)) { if (!isObstacle) { - if (selectedNode != nodeEnd_) nodeStart_ = selectedNode; + if (selectedNode != nodeEnd_) { + nodeStart_->setStart(false); + nodeStart_ = selectedNode; + nodeStart_->setStart(true); + } } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl)) { if (!isObstacle) { - if (selectedNode != nodeStart_) nodeEnd_ = selectedNode; + if (selectedNode != nodeStart_) { + nodeEnd_->setGoal(false); + nodeEnd_ = selectedNode; + nodeEnd_->setGoal(true); + } } } else { selectedNode->setObstacle(!isObstacle); @@ -54,7 +61,11 @@ void BFS::updateNodes() { } else { if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl)) { if (!isObstacle) { - if (selectedNode != nodeStart_) nodeEnd_ = selectedNode; + if (selectedNode != nodeStart_) { + nodeEnd_->setGoal(false); + nodeEnd_ = selectedNode; + nodeEnd_->setGoal(true); + } } } } @@ -64,14 +75,20 @@ void BFS::updateNodes() { } // override renderNodes() function -void BFS::renderNodes() { +void BFS::renderNodes(sf::RenderTexture &render_texture) { + const auto texture_size = render_texture.getSize(); + + init_grid_xy_.x = (texture_size.x / 2.) - (mapWidth_ / 2.); + init_grid_xy_.y = (texture_size.y / 2.) - (mapHeight_ / 2.); + for (int x = 0; x < mapHeight_ / gridSize_; x++) { for (int y = 0; y < mapWidth_ / gridSize_; y++) { float size = static_cast(gridSize_); sf::RectangleShape rectangle(sf::Vector2f(size, size)); rectangle.setOutlineThickness(2.f); rectangle.setOutlineColor(BGN_COL); - rectangle.setPosition(350 + y * size, 18 + x * size); + rectangle.setPosition(init_grid_xy_.x + y * size, + init_grid_xy_.y + x * size); int nodeIndex = (mapWidth_ / gridSize_) * x + y; @@ -88,19 +105,19 @@ void BFS::renderNodes() { rectangle.setFillColor(IDLE_COL); } - if (nodes_[nodeIndex] == nodeStart_) { + if (nodes_[nodeIndex]->isStart()) { rectangle.setFillColor(START_COL); - } else if (nodes_[nodeIndex] == nodeEnd_) { + } else if (nodes_[nodeIndex]->isGoal()) { rectangle.setFillColor(END_COL); } - window_->draw(rectangle); + render_texture.draw(rectangle); } } // visualizing path if (nodeEnd_ != nullptr) { std::shared_ptr current = nodeEnd_; - while (current->getParentNode() != nullptr && current != nodeStart_) { + while (current->getParentNode() != nullptr && !current->isStart()) { current->setPath(true); current = current->getParentNode(); } @@ -109,67 +126,26 @@ void BFS::renderNodes() { void BFS::renderParametersGui() {} -void BFS::solveConcurrently(std::shared_ptr nodeStart, - std::shared_ptr nodeEnd, - std::shared_ptr> message_queue) { - // copy assignment - // thread-safe due to shared_ptrs - std::shared_ptr s_nodeStart = nodeStart; - std::shared_ptr s_nodeEnd = nodeEnd; - std::shared_ptr> s_message_queue = message_queue; - - bool solved = false; - - double cycleDuration = 1.0; // duration of a single simulation cycle in ms - // init stop watch - auto lastUpdate = std::chrono::system_clock::now(); - - while (true) { - // compute time difference to stop watch - long timeSinceLastUpdate = - std::chrono::duration_cast( - std::chrono::system_clock::now() - lastUpdate) - .count(); - - if (timeSinceLastUpdate >= cycleDuration) { - //////////////////////////// - // run the main algorithm // - //////////////////////////// - if (!frontier_.empty()) { - std::shared_ptr nodeCurrent = frontier_.front(); - nodeCurrent->setFrontier(false); - frontier_.pop(); - - if (nodeCurrent == s_nodeEnd) { - solved = true; - } - - for (auto nodeNeighbour : *nodeCurrent->getNeighbours()) { - if (!nodeNeighbour->isVisited() && nodeNeighbour->isObstacle() == 0) { - nodeNeighbour->setParentNode(nodeCurrent); - nodeNeighbour->setVisited(true); - nodeNeighbour->setFrontier(true); - frontier_.push(nodeNeighbour); - } - } - } else { - solved = true; - } - //////////////////////////// +void BFS::updatePlanner(bool &solved, Node &start_node, Node &end_node) { + if (!frontier_.empty()) { + std::shared_ptr node_current = frontier_.front(); + node_current->setFrontier(false); + frontier_.pop(); - // reset stop watch for next cycle - lastUpdate = std::chrono::system_clock::now(); + if (node_current->isGoal()) { + solved = true; } - // sends an update method to the message queue using move semantics - auto ftr = std::async(std::launch::async, &MessageQueue::send, - s_message_queue, std::move(solved)); - ftr.wait(); - - if (solved) return; - - // sleep at every iteration to reduce CPU usage - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + for (auto node_neighbour : *node_current->getNeighbours()) { + if (!node_neighbour->isVisited() && node_neighbour->isObstacle() == 0) { + node_neighbour->setParentNode(node_current); + node_neighbour->setVisited(true); + node_neighbour->setFrontier(true); + frontier_.push(node_neighbour); + } + } + } else { + solved = true; } } diff --git a/src/States/Algorithms/GraphBased/DFS/DFS.cpp b/src/States/Algorithms/GraphBased/DFS/DFS.cpp index 6b24e2d..7b64147 100644 --- a/src/States/Algorithms/GraphBased/DFS/DFS.cpp +++ b/src/States/Algorithms/GraphBased/DFS/DFS.cpp @@ -4,9 +4,7 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -DFS::DFS(sf::RenderWindow* window, std::stack>& states, - std::shared_ptr logger_panel) - : BFS(window, states, logger_panel) {} +DFS::DFS(std::shared_ptr logger_panel) : BFS(logger_panel) {} // Destructor DFS::~DFS() {} @@ -21,67 +19,26 @@ void DFS::initAlgorithm() { frontier_.push(nodeStart_); } -void DFS::solveConcurrently(std::shared_ptr nodeStart, - std::shared_ptr nodeEnd, - std::shared_ptr> message_queue) { - // copy assignment - // thread-safe due to shared_ptrs - std::shared_ptr s_nodeStart = nodeStart; - std::shared_ptr s_nodeEnd = nodeEnd; - std::shared_ptr> s_message_queue = message_queue; - - bool solved = false; - - double cycleDuration = 1; // duration of a single simulation cycle in ms - // init stop watch - auto lastUpdate = std::chrono::system_clock::now(); - - while (true) { - // compute time difference to stop watch - long timeSinceLastUpdate = - std::chrono::duration_cast( - std::chrono::system_clock::now() - lastUpdate) - .count(); - - if (timeSinceLastUpdate >= cycleDuration) { - //////////////////////////// - // run the main algorithm // - //////////////////////////// - if (!frontier_.empty()) { - std::shared_ptr nodeCurrent = frontier_.top(); - nodeCurrent->setFrontier(false); - frontier_.pop(); +void DFS::updatePlanner(bool &solved, Node &start_node, Node &end_node) { + if (!frontier_.empty()) { + std::shared_ptr node_current = frontier_.top(); + node_current->setFrontier(false); + frontier_.pop(); - if (nodeCurrent == s_nodeEnd) { - solved = true; - } + if (node_current->isGoal()) { + solved = true; + } - for (auto nodeNeighbour : *nodeCurrent->getNeighbours()) { - if (!nodeNeighbour->isVisited() && nodeNeighbour->isObstacle() == 0) { - nodeNeighbour->setParentNode(nodeCurrent); - nodeNeighbour->setVisited(true); - nodeNeighbour->setFrontier(true); - frontier_.push(nodeNeighbour); - } - } - } else { - solved = true; + for (auto node_neighbour : *node_current->getNeighbours()) { + if (!node_neighbour->isVisited() && node_neighbour->isObstacle() == 0) { + node_neighbour->setParentNode(node_current); + node_neighbour->setVisited(true); + node_neighbour->setFrontier(true); + frontier_.push(node_neighbour); } - //////////////////////////// - - // reset stop watch for next cycle - lastUpdate = std::chrono::system_clock::now(); } - - // sends an update method to the message queue using move semantics - auto ftr = std::async(std::launch::async, &MessageQueue::send, - s_message_queue, std::move(solved)); - ftr.wait(); - - if (solved) return; - - // sleep at every iteration to reduce CPU usage - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + solved = true; } } diff --git a/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp b/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp index fcf12b3..25a382c 100644 --- a/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp +++ b/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp @@ -4,10 +4,8 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -DIJKSTRA::DIJKSTRA(sf::RenderWindow *window, - std::stack> &states, - std::shared_ptr logger_panel) - : BFS(window, states, logger_panel) {} +DIJKSTRA::DIJKSTRA(std::shared_ptr logger_panel) + : BFS(logger_panel) {} // Destructor DIJKSTRA::~DIJKSTRA() {} @@ -22,76 +20,35 @@ void DIJKSTRA::initAlgorithm() { frontier_.push(nodeStart_); } -void DIJKSTRA::solveConcurrently( - std::shared_ptr nodeStart, std::shared_ptr nodeEnd, - std::shared_ptr> message_queue) { - // copy assignment - // thread-safe due to shared_ptrs - std::shared_ptr s_nodeStart = nodeStart; - std::shared_ptr s_nodeEnd = nodeEnd; - std::shared_ptr> s_message_queue = message_queue; - - bool solved = false; - - double cycleDuration = 1; // duration of a single simulation cycle in ms - // init stop watch - auto lastUpdate = std::chrono::system_clock::now(); - - while (true) { - // compute time difference to stop watch - long timeSinceLastUpdate = - std::chrono::duration_cast( - std::chrono::system_clock::now() - lastUpdate) - .count(); - - if (timeSinceLastUpdate >= cycleDuration) { - //////////////////////////// - // run the main algorithm // - //////////////////////////// - if (!frontier_.empty()) { - std::shared_ptr nodeCurrent = frontier_.top(); - nodeCurrent->setFrontier(false); - nodeCurrent->setVisited(true); - frontier_.pop(); +void DIJKSTRA::updatePlanner(bool &solved, Node &start_node, Node &end_node) { + if (!frontier_.empty()) { + std::shared_ptr node_current = frontier_.top(); + node_current->setFrontier(false); + node_current->setVisited(true); + frontier_.pop(); - if (nodeCurrent == s_nodeEnd) { - solved = true; - } + if (node_current->isGoal()) { + solved = true; + } - for (auto nodeNeighbour : *nodeCurrent->getNeighbours()) { - if (nodeNeighbour->isVisited() || nodeNeighbour->isObstacle()) { - continue; - } + for (auto node_neighbour : *node_current->getNeighbours()) { + if (node_neighbour->isVisited() || node_neighbour->isObstacle()) { + continue; + } - double dist = nodeCurrent->getGDistance() + - utils::distanceCost(nodeCurrent, nodeNeighbour); + double dist = node_current->getGDistance() + + utils::distanceCost(*node_current, *node_neighbour); - if (dist < nodeNeighbour->getGDistance()) { - nodeNeighbour->setParentNode(nodeCurrent); - nodeNeighbour->setGDistance(dist); + if (dist < node_neighbour->getGDistance()) { + node_neighbour->setParentNode(node_current); + node_neighbour->setGDistance(dist); - nodeNeighbour->setFrontier(true); - frontier_.push(nodeNeighbour); - } - } - } else { - solved = true; + node_neighbour->setFrontier(true); + frontier_.push(node_neighbour); } - //////////////////////////// - - // reset stop watch for next cycle - lastUpdate = std::chrono::system_clock::now(); } - - // sends an update method to the message queue using move semantics - auto ftr = std::async(std::launch::async, &MessageQueue::send, - s_message_queue, std::move(solved)); - ftr.wait(); - - if (solved) return; - - // sleep at every iteration to reduce CPU usage - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + solved = true; } } diff --git a/src/States/Algorithms/GraphBased/GraphBased.cpp b/src/States/Algorithms/GraphBased/GraphBased.cpp index 565174d..bec382b 100644 --- a/src/States/Algorithms/GraphBased/GraphBased.cpp +++ b/src/States/Algorithms/GraphBased/GraphBased.cpp @@ -4,10 +4,8 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -GraphBased::GraphBased(sf::RenderWindow* window, - std::stack>& states, - std::shared_ptr logger_panel) - : State(window, states, logger_panel), keyTimeMax_{1.f}, keyTime_{0.f} { +GraphBased::GraphBased(std::shared_ptr logger_panel) + : State(logger_panel), keyTimeMax_{1.f}, keyTime_{0.f} { initVariables(); initNodes(); initColors(); @@ -26,8 +24,10 @@ void GraphBased::initVariables() { slider_grid_size_ = 20; gridSize_ = slider_grid_size_; grid_connectivity_ = 0; - mapWidth_ = 700; - mapHeight_ = 700; + + no_of_grid_rows_ = no_of_grid_cols_ = 10; + mapWidth_ = no_of_grid_cols_ * gridSize_; + mapHeight_ = no_of_grid_rows_ * gridSize_; message_queue_ = std::make_shared>(); @@ -71,6 +71,8 @@ void GraphBased::initNodes(bool reset, bool reset_neighbours_only) { if (reset) { nodes_[nodeIndex]->setPosition(sf::Vector2i(x, y)); nodes_[nodeIndex]->setObstacle(false); + nodes_[nodeIndex]->setStart(false); + nodes_[nodeIndex]->setGoal(false); } nodes_[nodeIndex]->setVisited(false); nodes_[nodeIndex]->setFrontier(false); @@ -101,15 +103,15 @@ void GraphBased::initNodes(bool reset, bool reset_neighbours_only) { // initialize Start and End nodes ptrs (upper left and lower right corners) nodeStart_ = nodes_[(mapWidth_ / gridSize_) * 0 + 0]; nodeStart_->setParentNode(nullptr); + nodeStart_->setStart(true); nodeEnd_ = nodes_[(mapWidth_ / gridSize_) * (mapHeight_ / gridSize_ - 1) + (mapWidth_ / gridSize_ - 1)]; + nodeEnd_->setGoal(true); } } void GraphBased::endState() {} -void GraphBased::updateKeybinds() { checkForQuit(); } - /** * Getter function for Algorithm key timer. * @@ -136,12 +138,10 @@ void GraphBased::updateKeyTime(const float& dt) { } } -void GraphBased::update(const float& dt) { +void GraphBased::update(const float& dt, const ImVec2& mousePos) { // from base classes updateKeyTime(dt); - updateMousePosition(); - updateKeybinds(); - // updateButtons(); + updateMousePosition(mousePos); if (is_reset_) { initNodes(false, false); @@ -236,13 +236,53 @@ void GraphBased::renderGui() { } } -void GraphBased::render() { +void GraphBased::renderConfig() { renderGui(); } + +void GraphBased::renderScene(sf::RenderTexture& render_texture) { // virtual function renderNodes() // need to be implemented by derived class - renderNodes(); + renderNodes(render_texture); +} - // render gui - renderGui(); +void GraphBased::solveConcurrently( + std::shared_ptr nodeStart, std::shared_ptr nodeEnd, + std::shared_ptr> message_queue) { + // copy assignment + // thread-safe due to shared_ptrs + std::shared_ptr s_nodeStart = nodeStart; + std::shared_ptr s_nodeEnd = nodeEnd; + std::shared_ptr> s_message_queue = message_queue; + + bool solved = false; + + double cycleDuration = 1.0; // duration of a single simulation cycle in ms + // init stop watch + auto lastUpdate = std::chrono::system_clock::now(); + + while (true) { + // compute time difference to stop watch + long timeSinceLastUpdate = + std::chrono::duration_cast( + std::chrono::system_clock::now() - lastUpdate) + .count(); + + if (timeSinceLastUpdate >= cycleDuration) { + updatePlanner(solved, *s_nodeStart, *s_nodeEnd); + + // reset stop watch for next cycle + lastUpdate = std::chrono::system_clock::now(); + } + + // sends an update method to the message queue using move semantics + auto ftr = std::async(std::launch::async, &MessageQueue::send, + s_message_queue, std::move(solved)); + ftr.wait(); + + if (solved) return; + + // sleep at every iteration to reduce CPU usage + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } } } // namespace graph_based diff --git a/src/States/Algorithms/GraphBased/Node.cpp b/src/States/Algorithms/GraphBased/Node.cpp index d0b33a4..1a26e2d 100644 --- a/src/States/Algorithms/GraphBased/Node.cpp +++ b/src/States/Algorithms/GraphBased/Node.cpp @@ -9,6 +9,8 @@ Node::Node() isVisited_{false}, isFrontier_{false}, isPath_{false}, + isStart_{false}, + isGoal_{false}, parent_{nullptr}, gDist_{INFINITY}, fDist_{INFINITY} {} @@ -25,8 +27,12 @@ const bool Node::isFrontier() const { return isFrontier_; } const bool Node::isPath() const { return isPath_; } +const bool Node::isStart() const { return isStart_; } + +const bool Node::isGoal() const { return isGoal_; } + // Accessors -sf::Vector2i Node::getPos() { return pos_; } +sf::Vector2i Node::getPos() const { return pos_; } std::shared_ptr Node::getParentNode() { return parent_; } @@ -49,6 +55,10 @@ void Node::setFrontier(bool b) { isFrontier_ = b; } void Node::setPath(bool b) { isPath_ = b; } +void Node::setStart(bool b) { isStart_ = b; } + +void Node::setGoal(bool b) { isGoal_ = b; } + void Node::setPosition(sf::Vector2i pos) { pos_ = pos; } void Node::setNeighbours(std::shared_ptr node) { diff --git a/src/States/Algorithms/SamplingBased/RRT/RRT.cpp b/src/States/Algorithms/SamplingBased/RRT/RRT.cpp index eddaf09..a61f873 100644 --- a/src/States/Algorithms/SamplingBased/RRT/RRT.cpp +++ b/src/States/Algorithms/SamplingBased/RRT/RRT.cpp @@ -4,9 +4,8 @@ namespace path_finding_visualizer { namespace sampling_based { // Constructor -RRT::RRT(sf::RenderWindow *window, std::stack> &states, - std::shared_ptr logger_panel, const std::string &name) - : SamplingBased(window, states, logger_panel, name) { +RRT::RRT(std::shared_ptr logger_panel, const std::string &name) + : SamplingBased(logger_panel, name) { initParameters(); initialize(); } @@ -46,20 +45,23 @@ void RRT::initPlanner() { vertices_.emplace_back(start_vertex_); } -void RRT::renderPlannerData() { +void RRT::renderPlannerData(sf::RenderTexture &render_texture) { // render edges std::unique_lock lck(mutex_); for (const auto &edge : edges_) { - double p1_y = utils::map(edge.first->y, 0.0, 1.0, 0.0, 700.0); - double p1_x = utils::map(edge.first->x, 0.0, 1.0, 0.0, 700.0); - double p2_y = utils::map(edge.second->y, 0.0, 1.0, 0.0, 700.0); - double p2_x = utils::map(edge.second->x, 0.0, 1.0, 0.0, 700.0); - - sf::Vertex line[] = { - sf::Vertex(sf::Vector2f(350 + p1_y, 18 + p1_x), EDGE_COL), - sf::Vertex(sf::Vector2f(350 + p2_y, 18 + p2_x), EDGE_COL)}; - - window_->draw(line, 2, sf::Lines); + double p1_y = utils::map(edge.first->y, 0.0, 1.0, init_grid_xy_.x, + init_grid_xy_.x + 700.0); + double p1_x = utils::map(edge.first->x, 0.0, 1.0, init_grid_xy_.y, + init_grid_xy_.y + 700.0); + double p2_y = utils::map(edge.second->y, 0.0, 1.0, init_grid_xy_.x, + init_grid_xy_.x + 700.0); + double p2_x = utils::map(edge.second->x, 0.0, 1.0, init_grid_xy_.y, + init_grid_xy_.y + 700.0); + + sf::Vertex line[] = {sf::Vertex(sf::Vector2f(p1_y, p1_x), EDGE_COL), + sf::Vertex(sf::Vector2f(p2_y, p2_x), EDGE_COL)}; + + render_texture.draw(line, 2, sf::Lines); } lck.unlock(); @@ -67,14 +69,18 @@ void RRT::renderPlannerData() { if (goal_vertex_->parent) { std::shared_ptr current = goal_vertex_; while (current->parent && current != start_vertex_) { - double p1_y = utils::map(current->y, 0.0, 1.0, 0.0, 700.0); - double p1_x = utils::map(current->x, 0.0, 1.0, 0.0, 700.0); - double p2_y = utils::map(current->parent->y, 0.0, 1.0, 0.0, 700.0); - double p2_x = utils::map(current->parent->x, 0.0, 1.0, 0.0, 700.0); - - utils::sfPath path(sf::Vector2f(350 + p1_y, 18 + p1_x), - sf::Vector2f(350 + p2_y, 18 + p2_x), 4.f, PATH_COL); - window_->draw(path); + double p1_y = utils::map(current->y, 0.0, 1.0, init_grid_xy_.x, + init_grid_xy_.x + 700.0); + double p1_x = utils::map(current->x, 0.0, 1.0, init_grid_xy_.y, + init_grid_xy_.y + 700.0); + double p2_y = utils::map(current->parent->y, 0.0, 1.0, init_grid_xy_.x, + init_grid_xy_.x + 700.0); + double p2_x = utils::map(current->parent->x, 0.0, 1.0, init_grid_xy_.y, + init_grid_xy_.y + 700.0); + + utils::sfPath path(sf::Vector2f(p1_y, p1_x), sf::Vector2f(p2_y, p2_x), + 4.f, PATH_COL); + render_texture.draw(path); current = current->parent; } } @@ -83,18 +89,22 @@ void RRT::renderPlannerData() { sf::CircleShape start_goal_circle(obst_size_ / 2.0); start_goal_circle.setOrigin(start_goal_circle.getRadius(), start_goal_circle.getRadius()); - double start_y = utils::map(start_vertex_->y, 0.0, 1.0, 0.0, 700.0); - double start_x = utils::map(start_vertex_->x, 0.0, 1.0, 0.0, 700.0); - double goal_y = utils::map(goal_vertex_->y, 0.0, 1.0, 0.0, 700.0); - double goal_x = utils::map(goal_vertex_->x, 0.0, 1.0, 0.0, 700.0); - - start_goal_circle.setPosition(350 + start_y, 18 + start_x); + double start_y = utils::map(start_vertex_->y, 0.0, 1.0, init_grid_xy_.x, + init_grid_xy_.x + 700.0); + double start_x = utils::map(start_vertex_->x, 0.0, 1.0, init_grid_xy_.y, + init_grid_xy_.y + 700.0); + double goal_y = utils::map(goal_vertex_->y, 0.0, 1.0, init_grid_xy_.x, + init_grid_xy_.x + 700.0); + double goal_x = utils::map(goal_vertex_->x, 0.0, 1.0, init_grid_xy_.y, + init_grid_xy_.y + 700.0); + + start_goal_circle.setPosition(start_y, start_x); start_goal_circle.setFillColor(START_COL); - window_->draw(start_goal_circle); + render_texture.draw(start_goal_circle); - start_goal_circle.setPosition(350 + goal_y, 18 + goal_x); + start_goal_circle.setPosition(goal_y, goal_x); start_goal_circle.setFillColor(GOAL_COL); - window_->draw(start_goal_circle); + render_texture.draw(start_goal_circle); } void RRT::renderParametersGui() { @@ -178,8 +188,7 @@ bool RRT::isCollision(const std::shared_ptr &from_v, double pixel_y = utils::map(temp_v->y, 0.0, 1.0, 0.0, 700.0); double pixel_x = utils::map(temp_v->x, 0.0, 1.0, 0.0, 700.0); for (const auto &obst : obstacles_) { - if (obst->getGlobalBounds().contains( - sf::Vector2f(pixel_y + 350, pixel_x + 18))) { + if (obst->getGlobalBounds().contains(sf::Vector2f(pixel_y, pixel_x))) { return true; } } @@ -190,8 +199,7 @@ bool RRT::isCollision(const std::shared_ptr &from_v, double pixel_y = utils::map(to_v->y, 0.0, 1.0, 0.0, 700.0); double pixel_x = utils::map(to_v->x, 0.0, 1.0, 0.0, 700.0); for (const auto &obst : obstacles_) { - if (obst->getGlobalBounds().contains( - sf::Vector2f(pixel_y + 350, pixel_x + 18))) { + if (obst->getGlobalBounds().contains(sf::Vector2f(pixel_y, pixel_x))) { return true; } } diff --git a/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp b/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp index f4c4916..bed5563 100644 --- a/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp +++ b/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp @@ -4,11 +4,9 @@ namespace path_finding_visualizer { namespace sampling_based { // Constructor -RRT_STAR::RRT_STAR(sf::RenderWindow *window, - std::stack> &states, - std::shared_ptr logger_panel, +RRT_STAR::RRT_STAR(std::shared_ptr logger_panel, const std::string &name) - : RRT(window, states, logger_panel, name) { + : RRT(logger_panel, name) { initParameters(); initialize(); } diff --git a/src/States/Algorithms/SamplingBased/SamplingBased.cpp b/src/States/Algorithms/SamplingBased/SamplingBased.cpp index e7e607a..7301411 100644 --- a/src/States/Algorithms/SamplingBased/SamplingBased.cpp +++ b/src/States/Algorithms/SamplingBased/SamplingBased.cpp @@ -4,11 +4,9 @@ namespace path_finding_visualizer { namespace sampling_based { // Constructor -SamplingBased::SamplingBased(sf::RenderWindow* window, - std::stack>& states, - std::shared_ptr logger_panel, +SamplingBased::SamplingBased(std::shared_ptr logger_panel, const std::string& name) - : State(window, states, logger_panel), key_time_max_{1.f}, key_time_{0.f} { + : State(logger_panel), key_time_max_{1.f}, key_time_{0.f} { logger_panel_->info("Initialize " + name + " planner"); initVariables(); @@ -50,8 +48,6 @@ void SamplingBased::initVariables() { void SamplingBased::endState() {} -void SamplingBased::updateKeybinds() { checkForQuit(); } - /** * Getter function for Algorithm key timer. * @@ -78,11 +74,10 @@ void SamplingBased::updateKeyTime(const float& dt) { } } -void SamplingBased::update(const float& dt) { +void SamplingBased::update(const float& dt, const ImVec2& mousePos) { // from base classes updateKeyTime(dt); - updateMousePosition(); - updateKeybinds(); + updateMousePosition(mousePos); if (is_reset_) { is_running_ = false; @@ -152,13 +147,18 @@ void SamplingBased::update(const float& dt) { void SamplingBased::updateUserInput() { if (sf::Mouse::isButtonPressed(sf::Mouse::Left) && getKeyTime()) { - if (mousePositionWindow_.x > 350 && mousePositionWindow_.x < 1050 && - mousePositionWindow_.y > 18 && mousePositionWindow_.y < 718) { - sf::Vector2f mousePos = sf::Vector2f(mousePositionWindow_); - + if (mousePositionWindow_.x > init_grid_xy_.x && + mousePositionWindow_.x < init_grid_xy_.x + 700 && + mousePositionWindow_.y > init_grid_xy_.y && + mousePositionWindow_.y < init_grid_xy_.y + 700) { bool setObstacle = true; + sf::Vector2f relative_mouse_pos = + sf::Vector2f(mousePositionWindow_.x - init_grid_xy_.x, + mousePositionWindow_.y - init_grid_xy_.y); + std::cout << relative_mouse_pos.x << " " << relative_mouse_pos.y + << std::endl; for (std::size_t i = 0, e = obstacles_.size(); i != e; ++i) { - if (obstacles_[i]->getGlobalBounds().contains(mousePos)) { + if (obstacles_[i]->getGlobalBounds().contains(relative_mouse_pos)) { obstacles_.erase(obstacles_.begin() + i); setObstacle = false; break; @@ -169,16 +169,20 @@ void SamplingBased::updateUserInput() { if (sf::Keyboard::isKeyPressed(sf::Keyboard::LShift)) { if (setObstacle) { start_vertex_->y = - utils::map(mousePositionWindow_.x - 350, 0.0, 700.0, 0.0, 1.0); + utils::map(mousePositionWindow_.x, init_grid_xy_.x, + init_grid_xy_.x + 700.0, 0.0, 1.0); start_vertex_->x = - utils::map(mousePositionWindow_.y - 18, 0.0, 700.0, 0.0, 1.0); + utils::map(mousePositionWindow_.y, init_grid_xy_.y, + init_grid_xy_.y + 700.0, 0.0, 1.0); } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl)) { if (setObstacle) { goal_vertex_->y = - utils::map(mousePositionWindow_.x - 350, 0.0, 700.0, 0.0, 1.0); + utils::map(mousePositionWindow_.x, init_grid_xy_.x, + init_grid_xy_.x + 700.0, 0.0, 1.0); goal_vertex_->x = - utils::map(mousePositionWindow_.y - 18, 0.0, 700.0, 0.0, 1.0); + utils::map(mousePositionWindow_.y, init_grid_xy_.y, + init_grid_xy_.y + 700.0, 0.0, 1.0); } } else { // add new obstacle @@ -186,7 +190,7 @@ void SamplingBased::updateUserInput() { std::shared_ptr obstShape = std::make_shared( sf::Vector2f(obst_size_, obst_size_)); - obstShape->setPosition(mousePos); + obstShape->setPosition(relative_mouse_pos); obstShape->setFillColor(OBST_COL); obstacles_.emplace_back(std::move(obstShape)); } @@ -195,9 +199,9 @@ void SamplingBased::updateUserInput() { if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl)) { if (setObstacle) { goal_vertex_->y = - utils::map(mousePositionWindow_.x - 350, 0.0, 700.0, 0.0, 1.0); + utils::map(mousePositionWindow_.x, 0.0, 700.0, 0.0, 1.0); goal_vertex_->x = - utils::map(mousePositionWindow_.y - 18, 0.0, 700.0, 0.0, 1.0); + utils::map(mousePositionWindow_.y, 0.0, 700.0, 0.0, 1.0); // TODO: Find nearest node from goal point and set it as parent } @@ -207,9 +211,13 @@ void SamplingBased::updateUserInput() { } } -void SamplingBased::renderObstacles() { +void SamplingBased::renderObstacles(sf::RenderTexture& render_texture) { for (auto& shape : obstacles_) { - window_->draw(*shape); + sf::RectangleShape obst(sf::Vector2f(obst_size_, obst_size_)); + obst.setPosition(sf::Vector2f(init_grid_xy_.x + shape->getPosition().x, + init_grid_xy_.y + shape->getPosition().y)); + obst.setFillColor(OBST_COL); + render_texture.draw(obst); } } @@ -223,7 +231,7 @@ void SamplingBased::renderGui() { static_cast(max_iterations_), 0.0, 1.0)); const std::string buf = std::to_string(curr_iter_no_) + "/" + std::to_string(max_iterations_); - ImGui::ProgressBar(progress, ImVec2(317.0f, 0.0f), buf.c_str()); + ImGui::ProgressBar(progress, ImVec2(-1.0f, 0.0f), buf.c_str()); } if (ImGui::CollapsingHeader("Configuration", @@ -254,15 +262,23 @@ void SamplingBased::renderGui() { } } -void SamplingBased::render() { - renderObstacles(); +void SamplingBased::renderConfig() { + // render gui + renderGui(); +} +void SamplingBased::renderScene(sf::RenderTexture& render_texture) { + const auto texture_size = render_texture.getSize(); + + init_grid_xy_.x = (texture_size.x / 2.) - (map_width_ / 2.); + init_grid_xy_.y = (texture_size.y / 2.) - (map_height_ / 2.); + // std::cout << init_grid_xy_.x << " " << init_grid_xy_.y << std::endl; + // std::cout << "Mouse: " << mousePositionWindow_.x << " " + // << mousePositionWindow_.y << std::endl; + renderObstacles(render_texture); // virtual function renderPlannerData() // need to be implemented by derived class - renderPlannerData(); - - // render gui - renderGui(); + renderPlannerData(render_texture); } void SamplingBased::solveConcurrently( diff --git a/src/main.cpp b/src/main.cpp index a1a47f3..357992b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,15 +8,21 @@ #include "Game.h" int main() { - sf::VideoMode videoMode(1068u, 736u); - sf::RenderWindow window(videoMode, "Path-Finding Visualizer", - sf::Style::Titlebar | sf::Style::Close); + sf::VideoMode videoMode(1600u, 960u); + sf::RenderWindow window( + videoMode, "Path-Finding Visualizer", + sf::Style::Titlebar | sf::Style::Close | sf::Style::Resize); // setting frame limit window.setFramerateLimit(100u); + + sf::RenderTexture render_texture; + if (!render_texture.create(700u, 700u)) { + } + ImGui::SFML::Init(window, false); // Initialize Game - path_finding_visualizer::Game game(&window); + path_finding_visualizer::Game game(&window, &render_texture); // Game Loop while (game.running()) { From 2f589f1e13a1b4a018facf31b9da5ebcf7fed011 Mon Sep 17 00:00:00 2001 From: mlsdpk Date: Thu, 2 Dec 2021 13:08:02 +0630 Subject: [PATCH 4/4] Improve UI styling - Make UI buttons and headers looks nicer - Allow maps width and height to be controllable - Fix some bugs --- README.md | 8 +- figures/img0.png | Bin 29105 -> 200881 bytes include/Game.h | 12 +- include/{LoggerPanel.h => Gui.h} | 58 ++++ include/State.h | 6 +- .../Algorithms/GraphBased/ASTAR/ASTAR.h | 2 +- .../States/Algorithms/GraphBased/BFS/BFS.h | 2 +- .../States/Algorithms/GraphBased/DFS/DFS.h | 2 +- .../Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h | 2 +- .../States/Algorithms/GraphBased/GraphBased.h | 13 +- .../States/Algorithms/SamplingBased/RRT/RRT.h | 2 +- .../SamplingBased/RRT_STAR/RRT_STAR.h | 3 +- .../Algorithms/SamplingBased/SamplingBased.h | 8 +- src/Game.cpp | 264 ++++++++++-------- src/State.cpp | 2 +- .../Algorithms/GraphBased/ASTAR/ASTAR.cpp | 3 +- src/States/Algorithms/GraphBased/BFS/BFS.cpp | 24 +- src/States/Algorithms/GraphBased/DFS/DFS.cpp | 2 +- .../GraphBased/DIJKSTRA/DIJKSTRA.cpp | 2 +- .../Algorithms/GraphBased/GraphBased.cpp | 138 ++++++--- .../Algorithms/SamplingBased/RRT/RRT.cpp | 47 ++-- .../SamplingBased/RRT_STAR/RRT_STAR.cpp | 20 +- .../SamplingBased/SamplingBased.cpp | 145 +++++++--- 23 files changed, 487 insertions(+), 278 deletions(-) rename include/{LoggerPanel.h => Gui.h} (51%) diff --git a/README.md b/README.md index 1bafec8..61b854a 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,10 @@ A tool for visualizing numerous pathfinding algorithms in two dimensions. -This project involves minimal implementations of the popular planning algorithms, including both graph-based and sampling-based planners. We provide an easy-to-use GUI to control the animation process and explore different planner configurations. This is an ongoing work and current implementation of the project only involves four search-based planning algorithms: BFS, DFS, DIJKSTRA and A-Star and two sampling-based planners: RRT and RRT*. The project extensively uses SFML, ImGui and Modern C++ features such as smart pointers, lamda expressions along with multi-threading concepts. +This project involves minimal implementations of the popular planning algorithms, including both graph-based and sampling-based planners. We provide an easy-to-use GUI to control the animation process and explore different planner configurations. Current implementation of the project involves four search-based planning algorithms: BFS, DFS, DIJKSTRA and A-Star and two sampling-based planners: RRT and RRT*. The project extensively uses SFML, ImGui and Modern C++ features such as smart pointers, lamda expressions along with multi-threading concepts. ![](figures/img0.png) -## How to use - -- to place/remove obstacle cells (`left-CLICKED`) -- to change starting cell (`left-SHIFT + left-CLICKED`) -- to change end cell (`left-CTRL + left-CLICKED`) - ## Dependencies * cmake >= 3.14 diff --git a/figures/img0.png b/figures/img0.png index 356d8c811d8d0c19a6d680d678e53d853098a5d6..c9390c6f645bc90165fd3e7438035bd5c1578c35 100644 GIT binary patch literal 200881 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%0AqYg$PJeg_HWt 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 C!`!F< literal 29105 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 BK6C&8 diff --git a/include/Game.h b/include/Game.h index d13d369..2e496ec 100644 --- a/include/Game.h +++ b/include/Game.h @@ -36,7 +36,8 @@ class Game { void update(); void render(); void initGuiTheme(); - void renderMenuBar(ImGuiIO& io); + void renderNewPlannerMenu(); + void renderRunMenu(ImGuiIO& io); void setGraphBasedPlanner(const int id); void setSamplingBasedPlanner(const int id); void showHowToUseWindow(); @@ -52,10 +53,13 @@ class Game { float dt_; std::stack> states_; std::string curr_planner_; - std::shared_ptr logger_panel_; + std::shared_ptr logger_panel_; bool disable_run_; - bool show_how_to_use_window_{false}; - bool show_about_window_{false}; + bool show_how_to_use_window_{true}; + bool show_about_window_{true}; + bool show_control_panel_{true}; + bool show_console_{true}; + bool show_stats_panel_{true}; }; } // namespace path_finding_visualizer \ No newline at end of file diff --git a/include/LoggerPanel.h b/include/Gui.h similarity index 51% rename from include/LoggerPanel.h rename to include/Gui.h index 407ca69..e142bda 100644 --- a/include/LoggerPanel.h +++ b/include/Gui.h @@ -4,6 +4,7 @@ #include namespace path_finding_visualizer { +namespace gui { class LoggerPanel { public: @@ -73,4 +74,61 @@ class LoggerPanel { } }; +// Helper to display a little (?) mark which shows a tooltip when hovered. +// In your own code you may want to display an actual icon if you are using a +// merged icon fonts (see docs/FONTS.md) +inline void HelpMarker(const char* desc) { + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +inline bool inputInt(const std::string& label, int* val, const int& min_val, + const int& max_val, const int& step = 1, + const int& step_fast = 100, + const std::string& help_marker = "", + ImGuiInputTextFlags flags = 0) { + flags |= ImGuiInputTextFlags_EnterReturnsTrue; + bool is_active = ImGui::InputInt(label.c_str(), val, step, step_fast, flags); + if (!help_marker.empty()) { + ImGui::SameLine(); + HelpMarker(help_marker.c_str()); + } + if (is_active) { + if (*val < min_val) + *val = min_val; + else if (*val > max_val) + *val = max_val; + } + return is_active; +} + +inline bool inputDouble(const std::string& label, double* val, + const double& min_val, const double& max_val, + const double& step = 0.0, const double& step_fast = 0.0, + const std::string& help_marker = "", + const char* format = "%.6f", + ImGuiInputTextFlags flags = 0) { + flags |= ImGuiInputTextFlags_EnterReturnsTrue; + bool is_active = + ImGui::InputDouble(label.c_str(), val, step, step_fast, format, flags); + if (!help_marker.empty()) { + ImGui::SameLine(); + HelpMarker(help_marker.c_str()); + } + if (is_active) { + if (*val < min_val) + *val = min_val; + else if (*val > max_val) + *val = max_val; + } + return is_active; +} + +} // namespace gui } // namespace path_finding_visualizer \ No newline at end of file diff --git a/include/State.h b/include/State.h index 324869d..09910e7 100644 --- a/include/State.h +++ b/include/State.h @@ -11,7 +11,7 @@ #include #include -#include "LoggerPanel.h" +#include "Gui.h" /* State Base Class @@ -22,14 +22,14 @@ namespace path_finding_visualizer { class State { private: protected: - std::shared_ptr logger_panel_; + std::shared_ptr logger_panel_; sf::Vector2f mousePositionWindow_; bool is_reset_; bool is_running_; public: // Constructor - State(std::shared_ptr logger_panel); + State(std::shared_ptr logger_panel); // Destructor virtual ~State(); diff --git a/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h b/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h index bbab3e7..eed86a5 100644 --- a/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h +++ b/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h @@ -21,7 +21,7 @@ struct MinimumDistanceASTAR { class ASTAR : public BFS { public: // Constructor - ASTAR(std::shared_ptr logger_panel); + ASTAR(std::shared_ptr logger_panel); // Destructor virtual ~ASTAR(); diff --git a/include/States/Algorithms/GraphBased/BFS/BFS.h b/include/States/Algorithms/GraphBased/BFS/BFS.h index ebc3894..eb74814 100644 --- a/include/States/Algorithms/GraphBased/BFS/BFS.h +++ b/include/States/Algorithms/GraphBased/BFS/BFS.h @@ -11,7 +11,7 @@ namespace graph_based { class BFS : public GraphBased { public: // Constructor - BFS(std::shared_ptr logger_panel); + BFS(std::shared_ptr logger_panel); // Destructor virtual ~BFS(); diff --git a/include/States/Algorithms/GraphBased/DFS/DFS.h b/include/States/Algorithms/GraphBased/DFS/DFS.h index 88708e1..d946a65 100644 --- a/include/States/Algorithms/GraphBased/DFS/DFS.h +++ b/include/States/Algorithms/GraphBased/DFS/DFS.h @@ -10,7 +10,7 @@ namespace graph_based { class DFS : public BFS { public: // Constructor - DFS(std::shared_ptr logger_panel); + DFS(std::shared_ptr logger_panel); // Destructor virtual ~DFS(); diff --git a/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h b/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h index 07f0bb0..9d5ea07 100644 --- a/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h +++ b/include/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.h @@ -21,7 +21,7 @@ struct MinimumDistanceDIJKSTRA { class DIJKSTRA : public BFS { public: // Constructor - DIJKSTRA(std::shared_ptr logger_panel); + DIJKSTRA(std::shared_ptr logger_panel); // Destructor virtual ~DIJKSTRA(); diff --git a/include/States/Algorithms/GraphBased/GraphBased.h b/include/States/Algorithms/GraphBased/GraphBased.h index 471d580..b1746be 100644 --- a/include/States/Algorithms/GraphBased/GraphBased.h +++ b/include/States/Algorithms/GraphBased/GraphBased.h @@ -10,7 +10,7 @@ #include #include -#include "LoggerPanel.h" +#include "Gui.h" #include "MessageQueue.h" #include "State.h" #include "States/Algorithms/GraphBased/Node.h" @@ -22,7 +22,7 @@ namespace graph_based { class GraphBased : public State { public: // Constructor - GraphBased(std::shared_ptr logger_panel); + GraphBased(std::shared_ptr logger_panel); // Destructor virtual ~GraphBased(); @@ -55,6 +55,7 @@ class GraphBased : public State { // initialization Functions void initColors(); void initVariables(); + void initGridMapParams(); void initNodes(bool reset = true, bool reset_neighbours_only = false); // colors @@ -68,13 +69,13 @@ class GraphBased : public State { // Map Variables int no_of_grid_rows_; int no_of_grid_cols_; - int gridSize_; - int slider_grid_size_; + int grid_size_; + int ui_grid_size_; sf::Vector2f init_grid_xy_; // 0 = 4 connected grid, 1 = 8 connected grid int grid_connectivity_; - unsigned int mapWidth_; - unsigned int mapHeight_; + unsigned int map_width_; + unsigned int map_height_; // Algorithm related std::string algo_name_; diff --git a/include/States/Algorithms/SamplingBased/RRT/RRT.h b/include/States/Algorithms/SamplingBased/RRT/RRT.h index 8efcc2f..b3906ca 100644 --- a/include/States/Algorithms/SamplingBased/RRT/RRT.h +++ b/include/States/Algorithms/SamplingBased/RRT/RRT.h @@ -11,7 +11,7 @@ namespace sampling_based { class RRT : public SamplingBased { public: // Constructor - RRT(std::shared_ptr logger_panel, const std::string &name); + RRT(std::shared_ptr logger_panel, const std::string &name); // Destructor virtual ~RRT(); diff --git a/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h b/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h index 90f3990..e7ed817 100644 --- a/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h +++ b/include/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.h @@ -10,7 +10,8 @@ namespace sampling_based { class RRT_STAR : public RRT { public: // Constructor - RRT_STAR(std::shared_ptr logger_panel, const std::string& name); + RRT_STAR(std::shared_ptr logger_panel, + const std::string& name); // Destructor virtual ~RRT_STAR(); diff --git a/include/States/Algorithms/SamplingBased/SamplingBased.h b/include/States/Algorithms/SamplingBased/SamplingBased.h index 83aa7c9..feb7e7d 100644 --- a/include/States/Algorithms/SamplingBased/SamplingBased.h +++ b/include/States/Algorithms/SamplingBased/SamplingBased.h @@ -31,7 +31,7 @@ struct Vertex { class SamplingBased : public State { public: // Constructor - SamplingBased(std::shared_ptr logger_panel, + SamplingBased(std::shared_ptr logger_panel, const std::string &name); // Destructor @@ -44,8 +44,10 @@ class SamplingBased : public State { void renderScene(sf::RenderTexture &render_texture) override; void updateUserInput(); + void renderMap(sf::RenderTexture &render_texture); void renderObstacles(sf::RenderTexture &render_texture); void clearObstacles(); + void initMapVariables(); void initVariables(); void updateKeyTime(const float &dt); const bool getKeyTime(); @@ -88,8 +90,8 @@ class SamplingBased : public State { // Map related sf::Vector2f init_grid_xy_; unsigned int obst_size_; - unsigned int map_width_; - unsigned int map_height_; + int map_width_; + int map_height_; std::vector> obstacles_; /** diff --git a/src/Game.cpp b/src/Game.cpp index 38b751a..2166bb9 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -19,7 +19,7 @@ namespace path_finding_visualizer { // Constructor Game::Game(sf::RenderWindow* window, sf::RenderTexture* render_texture) : window_{window}, render_texture_{render_texture}, disable_run_{false} { - logger_panel_ = std::make_shared(); + logger_panel_ = std::make_shared(); curr_planner_ = GRAPH_BASED_PLANNERS[0]; // manually add BFS for now states_.push(std::make_unique(logger_panel_)); @@ -50,7 +50,7 @@ void Game::pollEvents() { } } -void Game::updateDt() { dt_ = dtClock_.restart().asSeconds(); } +void Game::updateDt() { dt_ = dtClock_.getElapsedTime().asSeconds(); } void Game::update() { pollEvents(); @@ -62,84 +62,76 @@ void Game::update() { // End the Application window_->close(); } - ImGui::SFML::Update(*window_, dtClock_.restart()); } -void Game::renderMenuBar(ImGuiIO& io) { - if (ImGui::BeginMenuBar()) { - if (ImGui::BeginMenu("Planners")) { - if (ImGui::BeginMenu("Graph-based Planners")) { - for (int n = 0; n < GRAPH_BASED_PLANNERS.size(); n++) { - bool selected = (GRAPH_BASED_PLANNERS[n] == curr_planner_); - if (ImGui::MenuItem(GRAPH_BASED_PLANNERS[n].c_str(), nullptr, - selected, !selected)) { - if (!selected) { - // change the planner - logger_panel_->clear(); - disable_run_ = false; - setGraphBasedPlanner(n); - } - curr_planner_ = GRAPH_BASED_PLANNERS[n]; +void Game::renderNewPlannerMenu() { + if (ImGui::BeginMenu("New Planner")) { + if (ImGui::BeginMenu("Graph-based Planners")) { + for (int n = 0; n < GRAPH_BASED_PLANNERS.size(); n++) { + bool selected = (GRAPH_BASED_PLANNERS[n] == curr_planner_); + if (ImGui::MenuItem(GRAPH_BASED_PLANNERS[n].c_str(), nullptr, selected, + !selected)) { + if (!selected) { + // change the planner + logger_panel_->clear(); + disable_run_ = false; + setGraphBasedPlanner(n); } + curr_planner_ = GRAPH_BASED_PLANNERS[n]; } - ImGui::EndMenu(); } + ImGui::EndMenu(); + } - if (ImGui::BeginMenu("Sampling-based Planners")) { - for (int n = 0; n < SAMPLING_BASED_PLANNERS.size(); n++) { - bool selected = (SAMPLING_BASED_PLANNERS[n] == curr_planner_); - if (ImGui::MenuItem(SAMPLING_BASED_PLANNERS[n].c_str(), nullptr, - selected, !selected)) { - if (!selected) { - // change the planner - logger_panel_->clear(); - disable_run_ = false; - setSamplingBasedPlanner(n); - } - curr_planner_ = SAMPLING_BASED_PLANNERS[n]; + if (ImGui::BeginMenu("Sampling-based Planners")) { + for (int n = 0; n < SAMPLING_BASED_PLANNERS.size(); n++) { + bool selected = (SAMPLING_BASED_PLANNERS[n] == curr_planner_); + if (ImGui::MenuItem(SAMPLING_BASED_PLANNERS[n].c_str(), nullptr, + selected, !selected)) { + if (!selected) { + // change the planner + logger_panel_->clear(); + disable_run_ = false; + setSamplingBasedPlanner(n); } + curr_planner_ = SAMPLING_BASED_PLANNERS[n]; } - ImGui::EndMenu(); } - ImGui::EndMenu(); } - if (ImGui::BeginMenu("Run")) { - { - if (disable_run_) ImGui::BeginDisabled(); - bool clicked = ImGui::MenuItem("Start Planning"); - if (disable_run_) ImGui::EndDisabled(); - if (clicked) { - logger_panel_->info("RUN button pressed. Planning started."); - disable_run_ = true; - if (!states_.empty()) { - states_.top()->setRunning(true); - } + ImGui::EndMenu(); + } +} + +void Game::renderRunMenu(ImGuiIO& io) { + if (ImGui::BeginMenu("Run")) { + { + if (disable_run_) ImGui::BeginDisabled(); + bool clicked = ImGui::MenuItem("Start Planning"); + if (disable_run_) ImGui::EndDisabled(); + if (clicked) { + logger_panel_->info("RUN button pressed. Planning started."); + disable_run_ = true; + if (!states_.empty()) { + states_.top()->setRunning(true); } } - { - if (!disable_run_) ImGui::BeginDisabled(); - bool clicked = ImGui::MenuItem("Reset Planner Data"); - if (!disable_run_) ImGui::EndDisabled(); - if (clicked) { - logger_panel_->info("RESET button pressed. Planning resetted."); - disable_run_ = false; - if (!states_.empty()) { - states_.top()->setReset(true); - } + } + { + if (!disable_run_) ImGui::BeginDisabled(); + bool clicked = ImGui::MenuItem("Reset Planner Data"); + if (!disable_run_) ImGui::EndDisabled(); + if (clicked) { + logger_panel_->info("RESET button pressed. Planning resetted."); + disable_run_ = false; + if (!states_.empty()) { + states_.top()->setReset(true); } } - ImGui::EndMenu(); } - - if (ImGui::BeginMenu("Help")) { - ImGui::MenuItem("How To Use", nullptr, &show_how_to_use_window_); - ImGui::MenuItem("About", nullptr, &show_about_window_); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); + ImGui::EndMenu(); } } @@ -186,86 +178,93 @@ void Game::render() { // Submit the DockSpace ImGuiIO& io = ImGui::GetIO(); - ImGuiStyle& style = ImGui::GetStyle(); - float min_window_size_x = style.WindowMinSize.x; - style.WindowMinSize.x = 332.f; if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) { ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); } - style.WindowMinSize.x = min_window_size_x; if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("Close", NULL, false)) window_->close(); + renderNewPlannerMenu(); + ImGui::Separator(); + if (ImGui::MenuItem("Exit", NULL, false)) window_->close(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("View")) { + ImGui::MenuItem("Show Control Panel", nullptr, &show_control_panel_); + ImGui::MenuItem("Show Console", nullptr, &show_console_); + ImGui::MenuItem("Show Stats", nullptr, &show_stats_panel_); + ImGui::EndMenu(); + } + renderRunMenu(io); + if (ImGui::BeginMenu("Help")) { + ImGui::MenuItem("How To Use", nullptr, &show_how_to_use_window_); + ImGui::MenuItem("About", nullptr, &show_about_window_); ImGui::EndMenu(); } ImGui::EndMenuBar(); } - //////////////////////////////// - // Configurations - //////////////////////////////// - ImGui::Begin("Configurations", nullptr, ImGuiWindowFlags_MenuBar); - renderMenuBar(io); - - ImGui::Text("Current Planner: %s", curr_planner_.c_str()); - ImGui::Spacing(); - ImGui::Spacing(); - - // render planner specific configurations - states_.top()->renderConfig(); - if (show_how_to_use_window_) { - if (ImGui::CollapsingHeader("How To Use")) { - ImGui::Text("USAGE GUIDE:"); - ImGui::BulletText("Left-click to place/remove obstacle cells"); - ImGui::BulletText("Left-SHIFT+left-click to change starting cell"); - ImGui::BulletText("Left-CTRL+left-click to change end cell"); - } + ImGui::Begin("How To Use"); + ImGui::Text("USAGE GUIDE:"); + ImGui::BulletText("Left-click to place/remove obstacle cells"); + ImGui::BulletText("Left-SHIFT+left-click to change starting cell"); + ImGui::BulletText("Left-CTRL+left-click to change end cell"); + ImGui::End(); } if (show_about_window_) { - if (ImGui::CollapsingHeader("About")) { - ImGui::Text("Path-finding Visualizer (v1.0.0)"); - ImGui::Text("Developed and maintained by Phone Thiha Kyaw."); - ImGui::Text("Email: mlsdphonethk @ gmail dot com"); - ImGui::Separator(); - ImGui::Text("ABOUT THIS VISUALIZER:"); - ImGui::Text( - "This project involves minimal implementations of\nthe popular " - "planning algorithms, including\nboth graph-based and " - "sampling-based planners."); + ImGui::Begin("About"); + ImGui::Text("Path-finding Visualizer (v1.0.0)"); + ImGui::Text("Developed and maintained by Phone Thiha Kyaw."); + ImGui::Text("Email: mlsdphonethk @ gmail dot com"); + ImGui::Separator(); + ImGui::Text("ABOUT THIS VISUALIZER:"); + ImGui::Text( + "This project involves minimal implementations of\nthe popular " + "planning algorithms, including\nboth graph-based and " + "sampling-based planners."); + + ImGui::Separator(); + + ImGui::Text("AVAILABLE PLANNERS:"); + + ImGui::BulletText("Graph-based Planners:"); + ImGui::Indent(); + ImGui::BulletText("Breadth-first search (BFS)"); + ImGui::BulletText("Depth-first search (DFS)"); + ImGui::BulletText("Dijkstra"); + ImGui::BulletText("A*"); + + ImGui::Unindent(); + ImGui::BulletText("Sampling-based Planners:"); + ImGui::Indent(); + ImGui::BulletText("Rapidly-exploring random trees (RRT)"); + ImGui::BulletText("RRT*"); + ImGui::Unindent(); + ImGui::End(); + } - ImGui::Separator(); + if (show_control_panel_) { + //////////////////////////////// + // Control Panel + //////////////////////////////// + ImGui::Begin("Control Panel"); - ImGui::Text("AVAILABLE PLANNERS:"); - - ImGui::BulletText("Graph-based Planners:"); - ImGui::Indent(); - ImGui::BulletText("Breadth-first search (BFS)"); - ImGui::BulletText("Depth-first search (DFS)"); - ImGui::BulletText("Dijkstra"); - ImGui::BulletText("A*"); - - ImGui::Unindent(); - ImGui::BulletText("Sampling-based Planners:"); - ImGui::Indent(); - ImGui::BulletText("Rapidly-exploring random trees (RRT)"); - ImGui::BulletText("RRT*"); - ImGui::Unindent(); - } - } + // render planner specific configurations + states_.top()->renderConfig(); - ImGui::End(); // end Configurations - //////////////////////////////// + ImGui::End(); // end Configurations + } - ////////////////////////// - // Console Panel - ////////////////////////// - ImGui::Begin("Console"); - ImGui::End(); - ////////////////////////// + if (show_stats_panel_) { + ImGui::Begin("Stats"); + ImGui::Text("Current Planner: %s", curr_planner_.c_str()); + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::End(); + } ////////////////////////////////////////////////////////////////////// // Planning Scene Panel @@ -310,9 +309,27 @@ void Game::render() { ImGui::PopStyleVar(); ////////////////////////////////////////////////////////////////////// - logger_panel_->render("Console"); + if (show_console_) { + ////////////////////////// + // Console Panel + ////////////////////////// + ImGui::Begin("Console"); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.f); + if (ImGui::Button("Clear##console_clear")) { + logger_panel_->clear(); + } + ImGui::PopStyleVar(); + ImGui::Spacing(); + ImGui::Separator(); + ImGui::End(); + logger_panel_->render("Console"); + ////////////////////////// + } + ImGui::End(); // dockspace end + // ImGui::ShowDemoWindow(); + ImGui::SFML::Render(*window_); window_->display(); render_texture_->display(); @@ -329,6 +346,7 @@ void Game::initGuiTheme() { io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGuiStyle* style = &ImGui::GetStyle(); + style->FramePadding = ImVec2(8.f, 4.f); // dark theme colors auto& colors = ImGui::GetStyle().Colors; diff --git a/src/State.cpp b/src/State.cpp index 9ab5f89..eb155c2 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -2,7 +2,7 @@ namespace path_finding_visualizer { -State::State(std::shared_ptr logger_panel) +State::State(std::shared_ptr logger_panel) : logger_panel_{logger_panel} {} State::~State() {} diff --git a/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp b/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp index cbbb0de..ea5f568 100644 --- a/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp +++ b/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp @@ -4,7 +4,8 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -ASTAR::ASTAR(std::shared_ptr logger_panel) : BFS(logger_panel) {} +ASTAR::ASTAR(std::shared_ptr logger_panel) + : BFS(logger_panel) {} // Destructor ASTAR::~ASTAR() {} diff --git a/src/States/Algorithms/GraphBased/BFS/BFS.cpp b/src/States/Algorithms/GraphBased/BFS/BFS.cpp index 295e391..5d6fd47 100644 --- a/src/States/Algorithms/GraphBased/BFS/BFS.cpp +++ b/src/States/Algorithms/GraphBased/BFS/BFS.cpp @@ -4,7 +4,7 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -BFS::BFS(std::shared_ptr logger_panel) +BFS::BFS(std::shared_ptr logger_panel) : GraphBased(logger_panel) {} // Destructor @@ -23,14 +23,14 @@ void BFS::initAlgorithm() { // override updateNodes() function void BFS::updateNodes() { if (sf::Mouse::isButtonPressed(sf::Mouse::Left) && getKeyTime()) { - int localY = ((mousePositionWindow_.x - init_grid_xy_.x) / gridSize_); - int localX = ((mousePositionWindow_.y - init_grid_xy_.y) / gridSize_); + int localY = ((mousePositionWindow_.x - init_grid_xy_.x) / grid_size_); + int localX = ((mousePositionWindow_.y - init_grid_xy_.y) / grid_size_); - if (localX >= 0 && localX < mapHeight_ / gridSize_) { - if (localY >= 0 && localY < mapWidth_ / gridSize_) { + if (localX >= 0 && localX < map_height_ / grid_size_) { + if (localY >= 0 && localY < map_width_ / grid_size_) { // get the selected node std::shared_ptr selectedNode = - nodes_[(mapWidth_ / gridSize_) * localX + localY]; + nodes_[(map_width_ / grid_size_) * localX + localY]; // check the position is Obstacle free or not bool isObstacle = false; @@ -78,19 +78,19 @@ void BFS::updateNodes() { void BFS::renderNodes(sf::RenderTexture &render_texture) { const auto texture_size = render_texture.getSize(); - init_grid_xy_.x = (texture_size.x / 2.) - (mapWidth_ / 2.); - init_grid_xy_.y = (texture_size.y / 2.) - (mapHeight_ / 2.); + init_grid_xy_.x = (texture_size.x / 2.) - (map_width_ / 2.); + init_grid_xy_.y = (texture_size.y / 2.) - (map_height_ / 2.); - for (int x = 0; x < mapHeight_ / gridSize_; x++) { - for (int y = 0; y < mapWidth_ / gridSize_; y++) { - float size = static_cast(gridSize_); + for (int x = 0; x < map_height_ / grid_size_; x++) { + for (int y = 0; y < map_width_ / grid_size_; y++) { + float size = static_cast(grid_size_); sf::RectangleShape rectangle(sf::Vector2f(size, size)); rectangle.setOutlineThickness(2.f); rectangle.setOutlineColor(BGN_COL); rectangle.setPosition(init_grid_xy_.x + y * size, init_grid_xy_.y + x * size); - int nodeIndex = (mapWidth_ / gridSize_) * x + y; + int nodeIndex = (map_width_ / grid_size_) * x + y; if (nodes_[nodeIndex]->isObstacle()) { rectangle.setFillColor(OBST_COL); diff --git a/src/States/Algorithms/GraphBased/DFS/DFS.cpp b/src/States/Algorithms/GraphBased/DFS/DFS.cpp index 7b64147..cf58464 100644 --- a/src/States/Algorithms/GraphBased/DFS/DFS.cpp +++ b/src/States/Algorithms/GraphBased/DFS/DFS.cpp @@ -4,7 +4,7 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -DFS::DFS(std::shared_ptr logger_panel) : BFS(logger_panel) {} +DFS::DFS(std::shared_ptr logger_panel) : BFS(logger_panel) {} // Destructor DFS::~DFS() {} diff --git a/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp b/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp index 25a382c..aab3635 100644 --- a/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp +++ b/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp @@ -4,7 +4,7 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -DIJKSTRA::DIJKSTRA(std::shared_ptr logger_panel) +DIJKSTRA::DIJKSTRA(std::shared_ptr logger_panel) : BFS(logger_panel) {} // Destructor diff --git a/src/States/Algorithms/GraphBased/GraphBased.cpp b/src/States/Algorithms/GraphBased/GraphBased.cpp index bec382b..141549b 100644 --- a/src/States/Algorithms/GraphBased/GraphBased.cpp +++ b/src/States/Algorithms/GraphBased/GraphBased.cpp @@ -4,7 +4,7 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -GraphBased::GraphBased(std::shared_ptr logger_panel) +GraphBased::GraphBased(std::shared_ptr logger_panel) : State(logger_panel), keyTimeMax_{1.f}, keyTime_{0.f} { initVariables(); initNodes(); @@ -19,16 +19,9 @@ GraphBased::~GraphBased() { } void GraphBased::initVariables() { - // these variables depend on the visualizer - // for now, just use these and can improve it later - slider_grid_size_ = 20; - gridSize_ = slider_grid_size_; + initGridMapParams(); grid_connectivity_ = 0; - no_of_grid_rows_ = no_of_grid_cols_ = 10; - mapWidth_ = no_of_grid_cols_ * gridSize_; - mapHeight_ = no_of_grid_rows_ * gridSize_; - message_queue_ = std::make_shared>(); is_running_ = false; @@ -40,6 +33,14 @@ void GraphBased::initVariables() { disable_gui_parameters_ = false; } +void GraphBased::initGridMapParams() { + ui_grid_size_ = 20; + grid_size_ = ui_grid_size_; + no_of_grid_rows_ = no_of_grid_cols_ = 10; + map_width_ = no_of_grid_cols_ * grid_size_; + map_height_ = no_of_grid_rows_ * grid_size_; +} + void GraphBased::initColors() { BGN_COL = sf::Color(246, 229, 245, 255); FONT_COL = sf::Color(78, 95, 131, 255); @@ -55,19 +56,22 @@ void GraphBased::initColors() { } void GraphBased::initNodes(bool reset, bool reset_neighbours_only) { + map_width_ = no_of_grid_cols_ * grid_size_; + map_height_ = no_of_grid_rows_ * grid_size_; + if (reset) { nodes_.clear(); - for (int i = 0; i < (mapWidth_ / gridSize_) * (mapHeight_ / gridSize_); + for (int i = 0; i < (map_width_ / grid_size_) * (map_height_ / grid_size_); i++) { nodes_.emplace_back(std::make_shared()); } } // set all nodes to free obsts and respective positions - for (int x = 0; x < mapHeight_ / gridSize_; x++) { - for (int y = 0; y < mapWidth_ / gridSize_; y++) { - int nodeIndex = (mapWidth_ / gridSize_) * x + y; + for (int x = 0; x < map_height_ / grid_size_; x++) { + for (int y = 0; y < map_width_ / grid_size_; y++) { + int nodeIndex = (map_width_ / grid_size_) * x + y; if (reset) { nodes_[nodeIndex]->setPosition(sf::Vector2i(x, y)); nodes_[nodeIndex]->setObstacle(false); @@ -85,13 +89,13 @@ void GraphBased::initNodes(bool reset, bool reset_neighbours_only) { // add neighbours based on 4 or 8 connectivity grid if (reset || reset_neighbours_only) { - for (int x = 0; x < mapHeight_ / gridSize_; x++) { - for (int y = 0; y < mapWidth_ / gridSize_; y++) { - int nodeIndex = (mapWidth_ / gridSize_) * x + y; + for (int x = 0; x < map_height_ / grid_size_; x++) { + for (int y = 0; y < map_width_ / grid_size_; y++) { + int nodeIndex = (map_width_ / grid_size_) * x + y; nodes_[nodeIndex]->clearNeighbours(); utils::addNeighbours(nodes_, nodeIndex, - static_cast(mapWidth_ / gridSize_), - static_cast(mapHeight_ / gridSize_), + static_cast(map_width_ / grid_size_), + static_cast(map_height_ / grid_size_), [](int connectivity) { return (connectivity == 1) ? true : false; }(grid_connectivity_)); @@ -101,11 +105,12 @@ void GraphBased::initNodes(bool reset, bool reset_neighbours_only) { if (reset) { // initialize Start and End nodes ptrs (upper left and lower right corners) - nodeStart_ = nodes_[(mapWidth_ / gridSize_) * 0 + 0]; + nodeStart_ = nodes_[(map_width_ / grid_size_) * 0 + 0]; nodeStart_->setParentNode(nullptr); nodeStart_->setStart(true); - nodeEnd_ = nodes_[(mapWidth_ / gridSize_) * (mapHeight_ / gridSize_ - 1) + - (mapWidth_ / gridSize_ - 1)]; + nodeEnd_ = + nodes_[(map_width_ / grid_size_) * (map_height_ / grid_size_ - 1) + + (map_width_ / grid_size_ - 1)]; nodeEnd_->setGoal(true); } } @@ -190,24 +195,82 @@ void GraphBased::update(const float& dt, const ImVec2& mousePos) { } void GraphBased::clearObstacles() { - for (int x = 0; x < mapHeight_ / gridSize_; x++) { - for (int y = 0; y < mapWidth_ / gridSize_; y++) { - int nodeIndex = (mapWidth_ / gridSize_) * x + y; + for (int x = 0; x < map_height_ / grid_size_; x++) { + for (int y = 0; y < map_width_ / grid_size_; y++) { + int nodeIndex = (map_width_ / grid_size_) * x + y; nodes_[nodeIndex]->setObstacle(false); } } } void GraphBased::renderGui() { - if (ImGui::CollapsingHeader("Configuration", - ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 8.f)); + if (ImGui::CollapsingHeader("Edit", ImGuiTreeNodeFlags_DefaultOpen)) { if (disable_gui_parameters_) ImGui::BeginDisabled(); - // grid size slider - if (ImGui::SliderInt("Grid Size", &slider_grid_size_, 10, 100)) { - gridSize_ = slider_grid_size_; + ImGui::Indent(8.f); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(2.f, 4.f)); + + ImGui::Text("Gridmap:"); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 2.f)); + + if (gui::inputInt("rows", &no_of_grid_rows_, 5, 1000, 1, 10, + "Number of rows in the gridmap")) + initNodes(true, false); + + if (gui::inputInt("cols", &no_of_grid_cols_, 5, 1000, 1, 10, + "Number of columns in the gridmap")) + initNodes(true, false); + + ImGui::PopStyleVar(); + + if (gui::inputInt("grid size", &ui_grid_size_, 10, 100, 1, 10, + "The size of a grid. Set this size larger to zoom in the " + "gridmap.")) { + grid_size_ = ui_grid_size_; initNodes(true, false); } + ImGui::Text("Random Obstacles:"); + ImGui::SameLine(); + gui::HelpMarker("TODO: Randomly generate obstacles in the gridmap"); + static int rand_obsts_no = 10; + if (ImGui::InputInt("##rand_obst_input", &rand_obsts_no, 1, 10, + ImGuiInputTextFlags_EnterReturnsTrue)) { + // TODO: + } + ImGui::SameLine(); + if (ImGui::Button("Generate")) { + // TODO: + } + + if (ImGui::Button("Clear Obstacles")) { + clearObstacles(); + } + ImGui::SameLine(); + if (ImGui::Button("Restore Defaults")) { + initGridMapParams(); + initNodes(true, false); + } + + ImGui::PopStyleVar(2); + ImGui::Unindent(8.f); + + if (disable_gui_parameters_) ImGui::EndDisabled(); + ImGui::Spacing(); + } + + if (ImGui::CollapsingHeader("Configurations", + ImGuiTreeNodeFlags_DefaultOpen)) { + if (disable_gui_parameters_) ImGui::BeginDisabled(); + + ImGui::Indent(8.f); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(2.f, 4.f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 2.f)); + // radio buttons for choosing 4 or 8 connected grids { bool a, b; @@ -217,23 +280,24 @@ void GraphBased::renderGui() { if (a || b) { initNodes(false, true); } + ImGui::SameLine(); + gui::HelpMarker( + "4-connected and 8-connected represent the number of neighbours of a " + "grid.\nPlanners relying on heuristics usually use Manhattan " + "distance (L1-norm) for 4-connected grids\nand Euclidean Distance " + "(L2-norm) for 8-connected grids."); } // virtual function renderParametersGui() // need to be implemented by derived class renderParametersGui(); - { - if (ImGui::Button("CLEAR OBSTACLES", ImVec2(156.5f, 0.f))) { - clearObstacles(); - } - ImGui::SameLine(); - if (ImGui::Button("RESET PARAMETERS", ImVec2(156.5f, 0.f))) { - } - } + ImGui::PopStyleVar(3); + ImGui::Unindent(8.f); if (disable_gui_parameters_) ImGui::EndDisabled(); } + ImGui::PopStyleVar(); } void GraphBased::renderConfig() { renderGui(); } diff --git a/src/States/Algorithms/SamplingBased/RRT/RRT.cpp b/src/States/Algorithms/SamplingBased/RRT/RRT.cpp index a61f873..7491055 100644 --- a/src/States/Algorithms/SamplingBased/RRT/RRT.cpp +++ b/src/States/Algorithms/SamplingBased/RRT/RRT.cpp @@ -4,7 +4,8 @@ namespace path_finding_visualizer { namespace sampling_based { // Constructor -RRT::RRT(std::shared_ptr logger_panel, const std::string &name) +RRT::RRT(std::shared_ptr logger_panel, + const std::string &name) : SamplingBased(logger_panel, name) { initParameters(); initialize(); @@ -50,13 +51,13 @@ void RRT::renderPlannerData(sf::RenderTexture &render_texture) { std::unique_lock lck(mutex_); for (const auto &edge : edges_) { double p1_y = utils::map(edge.first->y, 0.0, 1.0, init_grid_xy_.x, - init_grid_xy_.x + 700.0); + init_grid_xy_.x + map_width_); double p1_x = utils::map(edge.first->x, 0.0, 1.0, init_grid_xy_.y, - init_grid_xy_.y + 700.0); + init_grid_xy_.y + map_height_); double p2_y = utils::map(edge.second->y, 0.0, 1.0, init_grid_xy_.x, - init_grid_xy_.x + 700.0); + init_grid_xy_.x + map_width_); double p2_x = utils::map(edge.second->x, 0.0, 1.0, init_grid_xy_.y, - init_grid_xy_.y + 700.0); + init_grid_xy_.y + map_height_); sf::Vertex line[] = {sf::Vertex(sf::Vector2f(p1_y, p1_x), EDGE_COL), sf::Vertex(sf::Vector2f(p2_y, p2_x), EDGE_COL)}; @@ -70,13 +71,13 @@ void RRT::renderPlannerData(sf::RenderTexture &render_texture) { std::shared_ptr current = goal_vertex_; while (current->parent && current != start_vertex_) { double p1_y = utils::map(current->y, 0.0, 1.0, init_grid_xy_.x, - init_grid_xy_.x + 700.0); + init_grid_xy_.x + map_width_); double p1_x = utils::map(current->x, 0.0, 1.0, init_grid_xy_.y, - init_grid_xy_.y + 700.0); + init_grid_xy_.y + map_height_); double p2_y = utils::map(current->parent->y, 0.0, 1.0, init_grid_xy_.x, - init_grid_xy_.x + 700.0); + init_grid_xy_.x + map_width_); double p2_x = utils::map(current->parent->x, 0.0, 1.0, init_grid_xy_.y, - init_grid_xy_.y + 700.0); + init_grid_xy_.y + map_height_); utils::sfPath path(sf::Vector2f(p1_y, p1_x), sf::Vector2f(p2_y, p2_x), 4.f, PATH_COL); @@ -90,13 +91,13 @@ void RRT::renderPlannerData(sf::RenderTexture &render_texture) { start_goal_circle.setOrigin(start_goal_circle.getRadius(), start_goal_circle.getRadius()); double start_y = utils::map(start_vertex_->y, 0.0, 1.0, init_grid_xy_.x, - init_grid_xy_.x + 700.0); + init_grid_xy_.x + map_width_); double start_x = utils::map(start_vertex_->x, 0.0, 1.0, init_grid_xy_.y, - init_grid_xy_.y + 700.0); + init_grid_xy_.y + map_height_); double goal_y = utils::map(goal_vertex_->y, 0.0, 1.0, init_grid_xy_.x, - init_grid_xy_.x + 700.0); + init_grid_xy_.x + map_width_); double goal_x = utils::map(goal_vertex_->x, 0.0, 1.0, init_grid_xy_.y, - init_grid_xy_.y + 700.0); + init_grid_xy_.y + map_height_); start_goal_circle.setPosition(start_y, start_x); start_goal_circle.setFillColor(START_COL); @@ -108,14 +109,10 @@ void RRT::renderPlannerData(sf::RenderTexture &render_texture) { } void RRT::renderParametersGui() { - // TODO: instead of manually putting like this, its good to have custom - // declare functions for these parameters with different types - if (ImGui::InputDouble("range", &range_, 0.01, 1.0, "%.3f")) { - if (range_ < 0) range_ = 0.01; - } - if (ImGui::InputDouble("goal_radius", &goal_radius_, 0.01, 1.0, "%.3f")) { - if (goal_radius_ < 0.01) goal_radius_ = 0.01; - } + gui::inputDouble("range", &range_, 0.01, 1000.0, 0.01, 1.0, + "Maximum distance allowed between two vertices", "%.3f"); + gui::inputDouble("goal_radius", &goal_radius_, 0.01, 1000.0, 0.01, 1.0, + "Distance between vertex and goal to stop planning", "%.3f"); } void RRT::updatePlanner(bool &solved, Vertex &start, Vertex &goal) { @@ -185,8 +182,8 @@ bool RRT::isCollision(const std::shared_ptr &from_v, std::shared_ptr temp_v = std::make_shared(); interpolate(from_v, to_v, d / max_dist, temp_v); - double pixel_y = utils::map(temp_v->y, 0.0, 1.0, 0.0, 700.0); - double pixel_x = utils::map(temp_v->x, 0.0, 1.0, 0.0, 700.0); + double pixel_y = utils::map(temp_v->y, 0.0, 1.0, 0.0, map_width_); + double pixel_x = utils::map(temp_v->x, 0.0, 1.0, 0.0, map_height_); for (const auto &obst : obstacles_) { if (obst->getGlobalBounds().contains(sf::Vector2f(pixel_y, pixel_x))) { return true; @@ -196,8 +193,8 @@ bool RRT::isCollision(const std::shared_ptr &from_v, } // now we check the destination vertex to_v - double pixel_y = utils::map(to_v->y, 0.0, 1.0, 0.0, 700.0); - double pixel_x = utils::map(to_v->x, 0.0, 1.0, 0.0, 700.0); + double pixel_y = utils::map(to_v->y, 0.0, 1.0, 0.0, map_width_); + double pixel_x = utils::map(to_v->x, 0.0, 1.0, 0.0, map_height_); for (const auto &obst : obstacles_) { if (obst->getGlobalBounds().contains(sf::Vector2f(pixel_y, pixel_x))) { return true; diff --git a/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp b/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp index bed5563..fdad32b 100644 --- a/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp +++ b/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp @@ -4,7 +4,7 @@ namespace path_finding_visualizer { namespace sampling_based { // Constructor -RRT_STAR::RRT_STAR(std::shared_ptr logger_panel, +RRT_STAR::RRT_STAR(std::shared_ptr logger_panel, const std::string &name) : RRT(logger_panel, name) { initParameters(); @@ -55,18 +55,12 @@ void RRT_STAR::initPlanner() { } void RRT_STAR::renderParametersGui() { - if (ImGui::InputDouble("range", &range_, 0.01, 1.0, "%.3f")) { - if (range_ < 0.01) range_ = 0.01; - } - if (ImGui::InputDouble("rewire_factor", &rewire_factor_, 0.01, 0.1, "%.2f")) { - if (rewire_factor_ < 1.0) - rewire_factor_ = 1.0; - else if (rewire_factor_ > 2.0) - rewire_factor_ = 2.0; - } - if (ImGui::InputDouble("goal_radius", &goal_radius_, 0.01, 1.0, "%.3f")) { - if (goal_radius_ < 0.01) goal_radius_ = 0.01; - } + gui::inputDouble("range", &range_, 0.01, 1000.0, 0.01, 1.0, + "Maximum distance allowed between two vertices", "%.3f"); + gui::inputDouble("rewire_factor", &rewire_factor_, 1.0, 2.0, 0.01, 0.1, + "Rewiring factor", "%.2f"); + gui::inputDouble("goal_radius", &goal_radius_, 0.01, 1000.0, 0.01, 1.0, + "Distance between vertex and goal to stop planning", "%.3f"); } void RRT_STAR::updatePlanner(bool &solved, Vertex &start, Vertex &goal) { diff --git a/src/States/Algorithms/SamplingBased/SamplingBased.cpp b/src/States/Algorithms/SamplingBased/SamplingBased.cpp index 7301411..dea6aaf 100644 --- a/src/States/Algorithms/SamplingBased/SamplingBased.cpp +++ b/src/States/Algorithms/SamplingBased/SamplingBased.cpp @@ -4,7 +4,7 @@ namespace path_finding_visualizer { namespace sampling_based { // Constructor -SamplingBased::SamplingBased(std::shared_ptr logger_panel, +SamplingBased::SamplingBased(std::shared_ptr logger_panel, const std::string& name) : State(logger_panel), key_time_max_{1.f}, key_time_{0.f} { logger_panel_->info("Initialize " + name + " planner"); @@ -27,12 +27,14 @@ SamplingBased::~SamplingBased() { } } -void SamplingBased::initVariables() { - // these variables depend on the visualizer - // for now, just use these and can improve it later +void SamplingBased::initMapVariables() { map_width_ = 700; map_height_ = 700; obst_size_ = 20; +} + +void SamplingBased::initVariables() { + initMapVariables(); message_queue_ = std::make_shared>(); @@ -147,16 +149,17 @@ void SamplingBased::update(const float& dt, const ImVec2& mousePos) { void SamplingBased::updateUserInput() { if (sf::Mouse::isButtonPressed(sf::Mouse::Left) && getKeyTime()) { - if (mousePositionWindow_.x > init_grid_xy_.x && - mousePositionWindow_.x < init_grid_xy_.x + 700 && - mousePositionWindow_.y > init_grid_xy_.y && - mousePositionWindow_.y < init_grid_xy_.y + 700) { + if (mousePositionWindow_.x > init_grid_xy_.x + obst_size_ / 2 && + mousePositionWindow_.x < + init_grid_xy_.x + map_width_ - obst_size_ / 2 && + mousePositionWindow_.y > init_grid_xy_.y + obst_size_ / 2 && + mousePositionWindow_.y < + init_grid_xy_.y + map_height_ - obst_size_ / 2) { bool setObstacle = true; sf::Vector2f relative_mouse_pos = sf::Vector2f(mousePositionWindow_.x - init_grid_xy_.x, mousePositionWindow_.y - init_grid_xy_.y); - std::cout << relative_mouse_pos.x << " " << relative_mouse_pos.y - << std::endl; + for (std::size_t i = 0, e = obstacles_.size(); i != e; ++i) { if (obstacles_[i]->getGlobalBounds().contains(relative_mouse_pos)) { obstacles_.erase(obstacles_.begin() + i); @@ -170,19 +173,19 @@ void SamplingBased::updateUserInput() { if (setObstacle) { start_vertex_->y = utils::map(mousePositionWindow_.x, init_grid_xy_.x, - init_grid_xy_.x + 700.0, 0.0, 1.0); + init_grid_xy_.x + map_width_, 0.0, 1.0); start_vertex_->x = utils::map(mousePositionWindow_.y, init_grid_xy_.y, - init_grid_xy_.y + 700.0, 0.0, 1.0); + init_grid_xy_.y + map_height_, 0.0, 1.0); } } else if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl)) { if (setObstacle) { goal_vertex_->y = utils::map(mousePositionWindow_.x, init_grid_xy_.x, - init_grid_xy_.x + 700.0, 0.0, 1.0); + init_grid_xy_.x + map_width_, 0.0, 1.0); goal_vertex_->x = utils::map(mousePositionWindow_.y, init_grid_xy_.y, - init_grid_xy_.y + 700.0, 0.0, 1.0); + init_grid_xy_.y + map_height_, 0.0, 1.0); } } else { // add new obstacle @@ -190,7 +193,9 @@ void SamplingBased::updateUserInput() { std::shared_ptr obstShape = std::make_shared( sf::Vector2f(obst_size_, obst_size_)); - obstShape->setPosition(relative_mouse_pos); + obstShape->setPosition( + sf::Vector2f(relative_mouse_pos.x - obst_size_ / 2., + relative_mouse_pos.y - obst_size_ / 2.)); obstShape->setFillColor(OBST_COL); obstacles_.emplace_back(std::move(obstShape)); } @@ -199,9 +204,11 @@ void SamplingBased::updateUserInput() { if (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl)) { if (setObstacle) { goal_vertex_->y = - utils::map(mousePositionWindow_.x, 0.0, 700.0, 0.0, 1.0); + utils::map(mousePositionWindow_.x, init_grid_xy_.x, + init_grid_xy_.x + map_width_, 0.0, 1.0); goal_vertex_->x = - utils::map(mousePositionWindow_.y, 0.0, 700.0, 0.0, 1.0); + utils::map(mousePositionWindow_.y, init_grid_xy_.y, + init_grid_xy_.y + map_height_, 0.0, 1.0); // TODO: Find nearest node from goal point and set it as parent } @@ -211,6 +218,15 @@ void SamplingBased::updateUserInput() { } } +void SamplingBased::renderMap(sf::RenderTexture& render_texture) { + sf::RectangleShape planning_map(sf::Vector2f(map_width_, map_height_)); + planning_map.setFillColor(sf::Color(255, 255, 255)); + planning_map.setOutlineThickness(5); + planning_map.setOutlineColor(sf::Color(0, 0, 0)); + planning_map.setPosition(sf::Vector2f(init_grid_xy_.x, init_grid_xy_.y)); + render_texture.draw(planning_map); +} + void SamplingBased::renderObstacles(sf::RenderTexture& render_texture) { for (auto& shape : obstacles_) { sf::RectangleShape obst(sf::Vector2f(obst_size_, obst_size_)); @@ -224,6 +240,7 @@ void SamplingBased::renderObstacles(sf::RenderTexture& render_texture) { void SamplingBased::clearObstacles() { obstacles_.clear(); } void SamplingBased::renderGui() { + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.f); { std::unique_lock iter_no_lck(iter_no_mutex_); const float progress = static_cast( @@ -231,35 +248,95 @@ void SamplingBased::renderGui() { static_cast(max_iterations_), 0.0, 1.0)); const std::string buf = std::to_string(curr_iter_no_) + "/" + std::to_string(max_iterations_); + ImGui::Text("Planning Progress:"); + ImGui::SameLine(); + gui::HelpMarker( + "Shows the current iteration number of the planning progress"); + ImGui::Spacing(); ImGui::ProgressBar(progress, ImVec2(-1.0f, 0.0f), buf.c_str()); } + ImGui::PopStyleVar(); + ImGui::Spacing(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 8.f)); + if (ImGui::CollapsingHeader("Edit", ImGuiTreeNodeFlags_DefaultOpen)) { + if (disable_gui_parameters_) ImGui::BeginDisabled(); + ImGui::Indent(8.f); + + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(2.f, 4.f)); + + ImGui::Text("Map:"); + ImGui::SameLine(); + gui::HelpMarker( + "Set width and height of the planning map.\nInternally these values " + "are mapped between 0 and 1."); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 2.f)); + + if (gui::inputInt("width", &map_width_, 500, 10000, 100, 1000)) { + } + ImGui::PopStyleVar(); + if (gui::inputInt("height", &map_height_, 500, 10000, 100, 1000)) { + } + + ImGui::Text("Random Obstacles:"); + ImGui::SameLine(); + gui::HelpMarker("TODO: Randomly generate obstacles in the gridmap"); + static int rand_obsts_no = 10; + if (ImGui::InputInt("##rand_obst_input", &rand_obsts_no, 1, 10, + ImGuiInputTextFlags_EnterReturnsTrue)) { + // TODO: + } + ImGui::SameLine(); + if (ImGui::Button("Generate")) { + // TODO: + } + + if (ImGui::Button("Clear Obstacles")) { + clearObstacles(); + } + ImGui::SameLine(); + if (ImGui::Button("Restore Defaults##edit_restore")) { + initMapVariables(); + } + + ImGui::Unindent(8.f); + ImGui::PopStyleVar(2); + if (disable_gui_parameters_) ImGui::EndDisabled(); + ImGui::Spacing(); + } + ImGui::PopStyleVar(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 8.f)); if (ImGui::CollapsingHeader("Configuration", ImGuiTreeNodeFlags_DefaultOpen)) { if (disable_gui_parameters_) ImGui::BeginDisabled(); + ImGui::Indent(8.f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(2.f, 4.f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 2.f)); + + gui::inputInt("max_iterations", &max_iterations_, 1, 100000, 1, 1000, + "Maximum number of iterations to run the planner"); - if (ImGui::InputInt("max_iterations", &max_iterations_, 1, 1000)) { - if (max_iterations_ < 1) max_iterations_ = 1; - } // virtual function renderParametersGui() // need to be implemented by derived class renderParametersGui(); - { - if (ImGui::Button("CLEAR OBSTACLES", ImVec2(156.5f, 0.f))) { - clearObstacles(); - logger_panel_->info("Successfully removed all the obstacles."); - } - ImGui::SameLine(); - if (ImGui::Button("RESET PARAMETERS", ImVec2(156.5f, 0.f))) { - initParameters(); - logger_panel_->info( - "Planner related parameters resetted to default ones."); - } + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 8.f)); + ImGui::Spacing(); + ImGui::PopStyleVar(); + if (ImGui::Button("Restore Defaults##config_restore")) { + initParameters(); + logger_panel_->info( + "Planner related parameters resetted to default ones."); } + ImGui::Unindent(8.f); + ImGui::PopStyleVar(3); if (disable_gui_parameters_) ImGui::EndDisabled(); } + ImGui::PopStyleVar(); } void SamplingBased::renderConfig() { @@ -269,12 +346,10 @@ void SamplingBased::renderConfig() { void SamplingBased::renderScene(sf::RenderTexture& render_texture) { const auto texture_size = render_texture.getSize(); - init_grid_xy_.x = (texture_size.x / 2.) - (map_width_ / 2.); init_grid_xy_.y = (texture_size.y / 2.) - (map_height_ / 2.); - // std::cout << init_grid_xy_.x << " " << init_grid_xy_.y << std::endl; - // std::cout << "Mouse: " << mousePositionWindow_.x << " " - // << mousePositionWindow_.y << std::endl; + + renderMap(render_texture); renderObstacles(render_texture); // virtual function renderPlannerData() // need to be implemented by derived class