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/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/figures/img0.png b/figures/img0.png index 356d8c8..c9390c6 100644 Binary files a/figures/img0.png and b/figures/img0.png differ diff --git a/include/Game.h b/include/Game.h index a99efe1..2e496ec 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(); @@ -36,17 +36,30 @@ class Game { void update(); void render(); void initGuiTheme(); - void renderGui(); + void renderNewPlannerMenu(); + void renderRunMenu(ImGuiIO& io); void setGraphBasedPlanner(const int id); void setSamplingBasedPlanner(const int id); + void showHowToUseWindow(); + void showAboutWindow(); 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_; std::stack> states_; std::string curr_planner_; + std::shared_ptr logger_panel_; + bool disable_run_; + 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/Gui.h b/include/Gui.h new file mode 100644 index 0000000..e142bda --- /dev/null +++ b/include/Gui.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include + +namespace path_finding_visualizer { +namespace gui { + +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); + } +}; + +// 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 c55d305..09910e7 100644 --- a/include/State.h +++ b/include/State.h @@ -6,10 +6,13 @@ #include #include #include +#include #include #include #include +#include "Gui.h" + /* State Base Class */ @@ -19,31 +22,29 @@ namespace path_finding_visualizer { class State { private: protected: - std::stack> &states_; - - sf::RenderWindow *window_; - sf::Vector2i mousePositionWindow_; - bool quit_; + std::shared_ptr logger_panel_; + sf::Vector2f mousePositionWindow_; + bool is_reset_; + bool is_running_; public: // Constructor - State(sf::RenderWindow *window, std::stack> &states); + 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 5264e67..eed86a5 100644 --- a/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h +++ b/include/States/Algorithms/GraphBased/ASTAR/ASTAR.h @@ -21,16 +21,17 @@ struct MinimumDistanceASTAR { class ASTAR : public BFS { public: // Constructor - ASTAR(sf::RenderWindow *window, std::stack> &states); + 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 abc7ffe..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(sf::RenderWindow *window, std::stack> &states); + BFS(std::shared_ptr logger_panel); // Destructor virtual ~BFS(); @@ -23,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 11d0d66..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(sf::RenderWindow *window, std::stack> &states); + DFS(std::shared_ptr logger_panel); // Destructor virtual ~DFS(); @@ -18,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 78909e0..9d5ea07 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); + 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 60d8188..b1746be 100644 --- a/include/States/Algorithms/GraphBased/GraphBased.h +++ b/include/States/Algorithms/GraphBased/GraphBased.h @@ -10,6 +10,7 @@ #include #include +#include "Gui.h" #include "MessageQueue.h" #include "State.h" #include "States/Algorithms/GraphBased/Node.h" @@ -21,31 +22,42 @@ namespace graph_based { class GraphBased : public State { public: // Constructor - GraphBased(sf::RenderWindow* window, - std::stack>& states); + 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 initGridMapParams(); + 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; @@ -55,12 +67,15 @@ class GraphBased : public State { float keyTimeMax_; // Map Variables - int gridSize_; - int slider_grid_size_; + int no_of_grid_rows_; + int no_of_grid_cols_; + 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_; @@ -72,9 +87,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_; @@ -82,14 +95,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 15a871c..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(sf::RenderWindow *window, std::stack> &states); + RRT(std::shared_ptr logger_panel, const std::string &name); // Destructor virtual ~RRT(); @@ -22,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 5e93bf5..e7ed817 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(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..feb7e7d 100644 --- a/include/States/Algorithms/SamplingBased/SamplingBased.h +++ b/include/States/Algorithms/SamplingBased/SamplingBased.h @@ -31,21 +31,23 @@ struct Vertex { class SamplingBased : public State { public: // Constructor - SamplingBased(sf::RenderWindow *window, - std::stack> &states); + SamplingBased(std::shared_ptr logger_panel, + const std::string &name); // Destructor virtual ~SamplingBased(); // 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 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(); @@ -56,7 +58,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; @@ -86,9 +88,10 @@ 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_; + int map_width_; + int map_height_; std::vector> obstacles_; /** @@ -116,9 +119,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 5a0985c..2166bb9 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -17,11 +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} { +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_)); - + states_.push(std::make_unique(logger_panel_)); + view_move_xy_.x = view_move_xy_.y = 0.f; initGuiTheme(); } @@ -35,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: @@ -47,47 +50,290 @@ void Game::pollEvents() { } } -void Game::updateDt() { dt_ = dtClock_.restart().asSeconds(); } +void Game::updateDt() { dt_ = dtClock_.getElapsedTime().asSeconds(); } void Game::update() { pollEvents(); 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(); } - ImGui::SFML::Update(*window_, dtClock_.restart()); } +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(); + } + + 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(); + } +} + +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); + } + } + } + ImGui::EndMenu(); + } +} + void Game::render() { - window_->clear(sf::Color::White); + window_->clear(); + render_texture_->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)), - ImGuiCond_None); - - ImGui::Begin("path_finding_visualizer", nullptr, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); - renderGui(); - states_.top()->render(); + // 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; + + 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(); + if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) { + ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); + ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); + } + + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("File")) { + 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(); + } + + if (show_how_to_use_window_) { + 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_) { + 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(); + } + + if (show_control_panel_) { + //////////////////////////////// + // Control Panel + //////////////////////////////// + ImGui::Begin("Control Panel"); + + // render planner specific configurations + states_.top()->renderConfig(); + + ImGui::End(); // end Configurations + } + + 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 + ////////////////////////////////////////////////////////////////////// + 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; + } + + // 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::End(); - } + ImGui::PopStyleVar(); + ////////////////////////////////////////////////////////////////////// + + 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::SFML::Render(*window_); - window_->display(); + ImGui::End(); // dockspace end + + // ImGui::ShowDemoWindow(); + + ImGui::SFML::Render(*window_); + window_->display(); + render_texture_->display(); + } } void Game::initGuiTheme() { @@ -96,52 +342,68 @@ 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->FramePadding = ImVec2(8.f, 4.f); // 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_)); + states_.push(std::make_unique(logger_panel_)); break; case GRAPH_BASED_PLANNERS_IDS::DFS: // DFS - states_.push(std::make_unique(window_, states_)); + states_.push(std::make_unique(logger_panel_)); break; case GRAPH_BASED_PLANNERS_IDS::DIJKSTRA: // Dijkstra - states_.push(std::make_unique(window_, states_)); + states_.push(std::make_unique(logger_panel_)); break; case GRAPH_BASED_PLANNERS_IDS::AStar: // A-Star - states_.push(std::make_unique(window_, states_)); + states_.push(std::make_unique(logger_panel_)); break; default: break; @@ -152,75 +414,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( + 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( + logger_panel_, SAMPLING_BASED_PLANNERS[id])); break; default: break; } } -void Game::renderGui() { - if (ImGui::Button("Choose Planner..")) ImGui::OpenPopup("planners_popup"); - ImGui::SameLine(); - 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(); - } - - // 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/State.cpp b/src/State.cpp index 6233d8d..eb155c2 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -2,23 +2,14 @@ namespace path_finding_visualizer { -State::State(sf::RenderWindow *window, - std::stack> &states) - : window_{window}, states_{states}, 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 7fd8224..ea5f568 100644 --- a/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp +++ b/src/States/Algorithms/GraphBased/ASTAR/ASTAR.cpp @@ -4,9 +4,8 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -ASTAR::ASTAR(sf::RenderWindow *window, - std::stack> &states) - : BFS(window, states) {} +ASTAR::ASTAR(std::shared_ptr logger_panel) + : BFS(logger_panel) {} // Destructor ASTAR::~ASTAR() {} @@ -22,89 +21,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 14f5c7c..5d6fd47 100644 --- a/src/States/Algorithms/GraphBased/BFS/BFS.cpp +++ b/src/States/Algorithms/GraphBased/BFS/BFS.cpp @@ -4,8 +4,8 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -BFS::BFS(sf::RenderWindow* window, std::stack>& states) - : GraphBased(window, states) {} +BFS::BFS(std::shared_ptr logger_panel) + : GraphBased(logger_panel) {} // Destructor BFS::~BFS() {} @@ -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 - 350) / gridSize_); - int localX = ((mousePositionWindow_.y - 18) / 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; @@ -41,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); @@ -53,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); + } } } } @@ -63,16 +75,22 @@ void BFS::updateNodes() { } // override renderNodes() function -void BFS::renderNodes() { - for (int x = 0; x < mapHeight_ / gridSize_; x++) { - for (int y = 0; y < mapWidth_ / gridSize_; y++) { - float size = static_cast(gridSize_); +void BFS::renderNodes(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.); + + 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(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; + int nodeIndex = (map_width_ / grid_size_) * x + y; if (nodes_[nodeIndex]->isObstacle()) { rectangle.setFillColor(OBST_COL); @@ -87,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(); } @@ -108,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 1b7d8a1..cf58464 100644 --- a/src/States/Algorithms/GraphBased/DFS/DFS.cpp +++ b/src/States/Algorithms/GraphBased/DFS/DFS.cpp @@ -4,8 +4,7 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -DFS::DFS(sf::RenderWindow* window, std::stack>& states) - : BFS(window, states) {} +DFS::DFS(std::shared_ptr logger_panel) : BFS(logger_panel) {} // Destructor DFS::~DFS() {} @@ -20,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 16e9426..aab3635 100644 --- a/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp +++ b/src/States/Algorithms/GraphBased/DIJKSTRA/DIJKSTRA.cpp @@ -4,9 +4,8 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -DIJKSTRA::DIJKSTRA(sf::RenderWindow *window, - std::stack> &states) - : BFS(window, states) {} +DIJKSTRA::DIJKSTRA(std::shared_ptr logger_panel) + : BFS(logger_panel) {} // Destructor DIJKSTRA::~DIJKSTRA() {} @@ -21,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 9923284..141549b 100644 --- a/src/States/Algorithms/GraphBased/GraphBased.cpp +++ b/src/States/Algorithms/GraphBased/GraphBased.cpp @@ -4,9 +4,8 @@ namespace path_finding_visualizer { namespace graph_based { // Constructor -GraphBased::GraphBased(sf::RenderWindow* window, - std::stack>& states) - : State(window, states), 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(); @@ -20,13 +19,8 @@ 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; - mapWidth_ = 700; - mapHeight_ = 700; message_queue_ = std::make_shared>(); @@ -39,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); @@ -54,22 +56,27 @@ 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); + nodes_[nodeIndex]->setStart(false); + nodes_[nodeIndex]->setGoal(false); } nodes_[nodeIndex]->setVisited(false); nodes_[nodeIndex]->setFrontier(false); @@ -82,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_)); @@ -98,17 +105,18 @@ 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); - nodeEnd_ = nodes_[(mapWidth_ / gridSize_) * (mapHeight_ / gridSize_ - 1) + - (mapWidth_ / gridSize_ - 1)]; + nodeStart_->setStart(true); + nodeEnd_ = + nodes_[(map_width_ / grid_size_) * (map_height_ / grid_size_ - 1) + + (map_width_ / grid_size_ - 1)]; + nodeEnd_->setGoal(true); } } void GraphBased::endState() {} -void GraphBased::updateKeybinds() { checkForQuit(); } - /** * Getter function for Algorithm key timer. * @@ -135,12 +143,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); @@ -148,6 +154,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 +173,7 @@ void GraphBased::update(const float& dt) { thread_joined_ = false; is_initialized_ = true; + disable_gui_parameters_ = true; } // check the algorithm is solved or not @@ -187,105 +195,158 @@ void GraphBased::update(const float& dt) { } 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() { - // buttons - { - // RESET button - { - if (!disable_run_ || is_running_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RESET", ImVec2(100.f, 40.f)); - if (!disable_run_ || is_running_) ImGui::EndDisabled(); - if (clicked && !is_running_) { - is_reset_ = true; - disable_gui_parameters_ = false; - disable_run_ = false; - } - } + 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::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(2.f, 4.f)); - // TODO: PAUSE button - // always disabled (not implemented yet) - { - if (true) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("PAUSE", ImVec2(100.f, 40.f)); - if (true) ImGui::EndDisabled(); - } + ImGui::Text("Gridmap:"); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.f, 2.f)); - ImGui::SameLine(); + if (gui::inputInt("rows", &no_of_grid_rows_, 5, 1000, 1, 10, + "Number of rows in the gridmap")) + initNodes(true, false); - // RUN button - { - if (disable_run_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RUN", ImVec2(100.f, 40.f)); - if (disable_run_) ImGui::EndDisabled(); - if (clicked && !is_solved_) { - is_running_ = true; - disable_gui_parameters_ = true; - disable_run_ = true; - } - } - } - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); + if (gui::inputInt("cols", &no_of_grid_cols_, 5, 1000, 1, 10, + "Number of columns in the gridmap")) + initNodes(true, false); - if (disable_gui_parameters_) ImGui::BeginDisabled(); + ImGui::PopStyleVar(); - // grid size slider - if (ImGui::SliderInt("Grid Size", &slider_grid_size_, 10, 100)) { - gridSize_ = slider_grid_size_; - initNodes(true, false); - } - - ImGui::Spacing(); + 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); + } - // radio buttons for choosing 4 or 8 connected grids - { - bool a, b; - a = ImGui::RadioButton("4-connected", &grid_connectivity_, 0); + ImGui::Text("Random Obstacles:"); ImGui::SameLine(); - b = ImGui::RadioButton("8-connected", &grid_connectivity_, 1); - if (a || b) { - initNodes(false, true); + 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: } - } - 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))) { + if (ImGui::Button("Clear Obstacles")) { clearObstacles(); } ImGui::SameLine(); - if (ImGui::Button("RESET PARAMETERS", ImVec2(154.f, 40.f))) { + 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 (disable_gui_parameters_) ImGui::EndDisabled(); + 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; + 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::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(); + + ImGui::PopStyleVar(3); + ImGui::Unindent(8.f); + + if (disable_gui_parameters_) ImGui::EndDisabled(); + } + ImGui::PopStyleVar(); } -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 1eefe7c..7491055 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(std::shared_ptr logger_panel, + const std::string &name) + : SamplingBased(logger_panel, name) { initParameters(); initialize(); } @@ -45,20 +46,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 + map_width_); + double p1_x = utils::map(edge.first->x, 0.0, 1.0, init_grid_xy_.y, + 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 + map_width_); + double p2_x = utils::map(edge.second->x, 0.0, 1.0, init_grid_xy_.y, + 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)}; + + render_texture.draw(line, 2, sf::Lines); } lck.unlock(); @@ -66,14 +70,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 + map_width_); + double p1_x = utils::map(current->x, 0.0, 1.0, init_grid_xy_.y, + 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 + map_width_); + double p2_x = utils::map(current->parent->x, 0.0, 1.0, init_grid_xy_.y, + 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); + render_texture.draw(path); current = current->parent; } } @@ -82,30 +90,29 @@ 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 + map_width_); + double start_x = utils::map(start_vertex_->x, 0.0, 1.0, init_grid_xy_.y, + 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 + map_width_); + double goal_x = utils::map(goal_vertex_->x, 0.0, 1.0, init_grid_xy_.y, + init_grid_xy_.y + map_height_); + + 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() { - // 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; - } - ImGui::Spacing(); - 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) { @@ -175,11 +182,10 @@ 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 + 350, pixel_x + 18))) { + if (obst->getGlobalBounds().contains(sf::Vector2f(pixel_y, pixel_x))) { return true; } } @@ -187,11 +193,10 @@ 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 + 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 5f198e6..fdad32b 100644 --- a/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp +++ b/src/States/Algorithms/SamplingBased/RRT_STAR/RRT_STAR.cpp @@ -4,9 +4,9 @@ namespace path_finding_visualizer { namespace sampling_based { // Constructor -RRT_STAR::RRT_STAR(sf::RenderWindow *window, - std::stack> &states) - : RRT(window, states) { +RRT_STAR::RRT_STAR(std::shared_ptr logger_panel, + const std::string &name) + : RRT(logger_panel, name) { initParameters(); initialize(); } @@ -55,20 +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; - } - 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; - } + 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 e0d974b..dea6aaf 100644 --- a/src/States/Algorithms/SamplingBased/SamplingBased.cpp +++ b/src/States/Algorithms/SamplingBased/SamplingBased.cpp @@ -4,9 +4,10 @@ namespace path_finding_visualizer { namespace sampling_based { // Constructor -SamplingBased::SamplingBased(sf::RenderWindow* window, - std::stack>& states) - : State(window, states), key_time_max_{1.f}, key_time_{0.f} { +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"); initVariables(); start_vertex_ = std::make_shared(); @@ -26,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>(); @@ -47,8 +50,6 @@ void SamplingBased::initVariables() { void SamplingBased::endState() {} -void SamplingBased::updateKeybinds() { checkForQuit(); } - /** * Getter function for Algorithm key timer. * @@ -75,17 +76,17 @@ 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; is_initialized_ = false; is_reset_ = false; is_solved_ = false; + disable_gui_parameters_ = false; std::unique_lock lck(mutex_); is_stopped_ = true; @@ -115,6 +116,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 +126,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 +137,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 @@ -142,13 +149,19 @@ 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 + 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); + 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; @@ -159,16 +172,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 + map_width_, 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 + map_height_, 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 + map_width_, 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 + map_height_, 0.0, 1.0); } } else { // add new obstacle @@ -176,7 +193,9 @@ void SamplingBased::updateUserInput() { std::shared_ptr obstShape = std::make_shared( sf::Vector2f(obst_size_, obst_size_)); - obstShape->setPosition(mousePos); + 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)); } @@ -185,9 +204,11 @@ 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, init_grid_xy_.x, + init_grid_xy_.x + map_width_, 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 + map_height_, 0.0, 1.0); // TODO: Find nearest node from goal point and set it as parent } @@ -197,56 +218,29 @@ void SamplingBased::updateUserInput() { } } -void SamplingBased::renderObstacles() { +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_) { - 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); } } void SamplingBased::clearObstacles() { obstacles_.clear(); } void SamplingBased::renderGui() { - // buttons - { - // RESET button - { - if (!disable_run_ || is_running_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RESET", ImVec2(100.f, 40.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(100.f, 40.f)); - if (true) ImGui::EndDisabled(); - } - - ImGui::SameLine(); - - // RUN button - { - if (disable_run_) ImGui::BeginDisabled(); - bool clicked = ImGui::Button("RUN", ImVec2(100.f, 40.f)); - if (disable_run_) ImGui::EndDisabled(); - if (clicked && !is_solved_) { - is_running_ = true; - disable_gui_parameters_ = true; - disable_run_ = true; - } - } - } - - ImGui::Spacing(); - + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.f); { std::unique_lock iter_no_lck(iter_no_mutex_); const float progress = static_cast( @@ -254,47 +248,112 @@ 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::Spacing(); - ImGui::Separator(); + ImGui::PopStyleVar(); ImGui::Spacing(); - if (disable_gui_parameters_) ImGui::BeginDisabled(); + 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); - 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))) { + 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("RESET PARAMETERS", ImVec2(154.f, 40.f))) { + 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"); + + // virtual function renderParametersGui() + // need to be implemented by derived class + renderParametersGui(); + + 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(); +} - if (disable_gui_parameters_) ImGui::EndDisabled(); +void SamplingBased::renderConfig() { + // render gui + renderGui(); } -void SamplingBased::render() { - renderObstacles(); +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.); + renderMap(render_texture); + 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()) {