├── Glass 4.0 UI API.dll ├── Pathfinding Visualization.exe ├── beep.wav ├── checkBoxOff.png ├── checkBoxOn.png ├── font.ttf ├── hdr ├── audio.hpp ├── grid.hpp ├── path.hpp ├── render.hpp ├── resources.hpp ├── ui.hpp └── window.hpp ├── icon.ico ├── icon.png ├── openal32.dll ├── sfml-audio-2.dll ├── sfml-graphics-2.dll ├── sfml-network-2.dll ├── sfml-system-2.dll ├── sfml-window-2.dll └── src ├── audio.cpp ├── grid.cpp ├── main.cpp ├── path.cpp ├── render.cpp ├── ui.cpp └── window.cpp /Glass 4.0 UI API.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/Glass 4.0 UI API.dll -------------------------------------------------------------------------------- /Pathfinding Visualization.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/Pathfinding Visualization.exe -------------------------------------------------------------------------------- /beep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/beep.wav -------------------------------------------------------------------------------- /checkBoxOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/checkBoxOff.png -------------------------------------------------------------------------------- /checkBoxOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/checkBoxOn.png -------------------------------------------------------------------------------- /font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/font.ttf -------------------------------------------------------------------------------- /hdr/audio.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //Dependencies 4 | #include "resources.hpp" 5 | 6 | namespace engine { 7 | namespace audio { 8 | extern sf::SoundBuffer soundBuffer; 9 | extern sf::Sound sounds[mNumOfSoundObjects]; 10 | extern int lastPlayed; 11 | 12 | void loadAssets(); 13 | 14 | void changeVolume(float newVolume); 15 | void playSound(float pitch); 16 | } 17 | } -------------------------------------------------------------------------------- /hdr/grid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //Dependencies 4 | #include "resources.hpp" 5 | 6 | namespace engine { 7 | // Alias for int. Note: If using this for a more complex situation this 8 | // type can be replaced for a more advanced data type. 9 | typedef int Tile; 10 | 11 | // Used for hashing 2D vectors. 12 | struct HashContainer { 13 | size_t operator()(const gs::Vec2i& vec) const; 14 | }; 15 | // Used in the priority frontier to store a position and sort based on a 16 | // cost value. 17 | struct QueueItem { 18 | gs::Vec2i position; 19 | // Value used to sort item in a priority queue. 20 | float cost; 21 | 22 | QueueItem(); 23 | QueueItem(gs::Vec2i position, int cost); 24 | 25 | bool operator<(const QueueItem& item) const; 26 | bool operator>(const QueueItem& item) const; 27 | }; 28 | 29 | // A allocated 2D array data structure. 30 | class Grid { 31 | public: 32 | // Current usable size of grid. Note: Allocated size may be larger. 33 | gs::Vec2i size; 34 | // Starting location of the pathfinding. 35 | gs::Vec2i startMarker; 36 | // Target location of the pathfinding. 37 | gs::Vec2i endMarker; 38 | 39 | bool searching; 40 | // Tiles needing to be searched. 41 | std::queue frontier; 42 | std::priority_queue priorityFrontier; 43 | // Tiles already searched. 44 | std::vector searched; 45 | // Contains a map of locations that is used to construct a path. 46 | std::unordered_map pathMap; 47 | // Contains a map of locations that have been searched and a value that 48 | // is used to prioritize certain pathways. 49 | std::unordered_map costMap; 50 | // Collection of positions that build a cohesive path. 51 | std::vector path; 52 | 53 | Grid(); 54 | ~Grid(); 55 | 56 | // Allocates tiles and prepares grid for use. 57 | void create(gs::Vec2i size); 58 | void update(); 59 | // Will clear the (entire - meaning allocated) grid to a given value. 60 | void clear(Tile tile); 61 | // Fills grid with random tiles based on the coverage amount. 62 | void generateNoise(int coverage); 63 | 64 | void setTile(gs::Vec2i position, Tile tile); 65 | void setSize(gs::Vec2i size); 66 | // Sets the usable size based on a percentage of the allocated size. 67 | void setSize(float percentage); 68 | 69 | Tile getTile(gs::Vec2i position) const; 70 | gs::Vec2i getSize() const; 71 | // Returns the allocated size of the grid. 72 | gs::Vec2i getRealSize() const; 73 | // Checks if a given position is in the current accessable grid. 74 | bool isValidTile(gs::Vec2i position) const; 75 | private: 76 | Tile* tiles; 77 | // Size of grid as allocated. 78 | gs::Vec2i realSize; 79 | // Number of tiles in the grid. Note: Equal to the width * height. 80 | int blockSize; 81 | 82 | // Converts a 2D coordinate to a 1D coordinate for accessing the tiles. 83 | int getArrayCoordinate(gs::Vec2i position) const; 84 | }; 85 | 86 | extern Grid grid; 87 | // Used to interact with the grid in real time. 88 | extern bool pause, inc; 89 | // Speed of the grid flooding. 90 | extern float updateSpeed; 91 | } -------------------------------------------------------------------------------- /hdr/path.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //Dependencies 4 | #include "grid.hpp" 5 | #include "audio.hpp" 6 | 7 | namespace engine { 8 | // The algorithm to be used for pathfinding. 9 | enum class PathfindingMethod { Breadth, Greedy, AStar }; 10 | // Currently selected pathfinding algorithm. 11 | extern PathfindingMethod pathfindingMethod; 12 | // Allows searching to finish early if the end has been found. 13 | extern bool earlyExit; 14 | extern gs::Vec2i focusPoint; 15 | 16 | // Gives a manhattan distance between two points. Used to judge how good 17 | // different moves are. 18 | float heuristic(gs::Vec2i base, gs::Vec2i target); 19 | 20 | // Prepares a grid to be searched in. 21 | void initSearch(Grid& grid); 22 | // Clears search data from a grid. 23 | void clearSearch(Grid& grid); 24 | // Used to search grid. 25 | bool floodGrid(Grid& grid); 26 | // Used to fill the grid using the breadth first search algorithm. 27 | bool breadthFlood(Grid& grid); 28 | // Used to fill the grid using the greedy best first search algorithm. 29 | bool greedyFlood(Grid& grid); 30 | // Used to fill the grid using the A* algorithm. 31 | bool aStarFlood(Grid& grid); 32 | // Generates a path to an end using the pathMap of a grid. 33 | void generatePath(Grid& grid); 34 | } -------------------------------------------------------------------------------- /hdr/render.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //Dependencies 4 | #include "window.hpp" 5 | #include "grid.hpp" 6 | #include "path.hpp" 7 | 8 | namespace engine { 9 | namespace render { 10 | // Rendered size of the grid. 11 | extern const gs::Vec2f constrainedGridSize; 12 | extern const gs::Vec2f gridOffset; 13 | 14 | extern sf::RectangleShape tile; 15 | // Current size of each tile. Note: This changes every time the zoom 16 | // changes. 17 | extern gs::Vec2f tileSize; 18 | extern bool displayFrontier; 19 | extern bool displaySearched; 20 | 21 | // Checks if the mouse position is within the grids bounds. 22 | bool mouseOnGrid(); 23 | // Finds what tile the mouse is currently on. 24 | gs::Vec2i getMouseTile(const Grid& grid); 25 | 26 | void renderGrid(const Grid& grid); 27 | void renderPath(const Grid& grid); 28 | void renderFrontier(const Grid& grid); 29 | void renderPriorityFrontier(const Grid& grid); 30 | void renderSearched(const Grid& grid); 31 | void renderCostMap(const Grid& grid); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hdr/resources.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //Dependencies 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | //Macros 13 | 14 | #define mGetStyle(fullscreen) fullscreen ? sf::Style::Fullscreen :\ 15 | sf::Style::Default 16 | #define mNumOfSoundObjects 20 17 | 18 | //Globals 19 | 20 | extern int ticks; -------------------------------------------------------------------------------- /hdr/ui.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //Dependencies 4 | #include "render.hpp" 5 | 6 | namespace engine { 7 | namespace render { 8 | namespace ui { 9 | extern sf::Font font; 10 | extern gs::Text text; 11 | extern sf::Texture checkBoxOffTexture, checkBoxOnTexture; 12 | extern gs::Menu menu; 13 | // Slider used to adjust the active size of the grid. 14 | extern gs::Slider gridSizeSlider; 15 | // Slider used to adjust the speed of the grid filling animation. 16 | extern gs::Slider animationSpeedSlider; 17 | // Checkbox buttons used to change the pathfinding algorithm. 18 | extern gs::Checkbox methodButtons[3]; 19 | // Used to disable early exiting. 20 | extern gs::Checkbox exitEarlyButton; 21 | // Buttons used to handle the animations. 22 | extern gs::Button animationButtons[4]; 23 | // Names of the animation buttons. 24 | extern std::string animationButtonText[4]; 25 | // Used to hide the frontier from rendering. 26 | extern gs::Checkbox displayFrontierButton; 27 | // Used to hide the seached vector from rendering. 28 | extern gs::Checkbox displaySearchedButton; 29 | // Used to change the framerate. 30 | extern gs::Button framerateButton; 31 | // Buttons used to handle the grid. 32 | extern gs::Button gridButtons[4]; 33 | // Names of the grid buttons. 34 | extern std::string gridButtonText[4]; 35 | // Button to close application; 36 | extern gs::Button exitButton; 37 | // Slider used to change the sound volume. 38 | extern gs::Slider soundSlider; 39 | extern bool startMarkerLocked; 40 | extern bool endMarkerLocked; 41 | 42 | void loadAssets(); 43 | void handleAssets(); 44 | 45 | void updateUI(); 46 | 47 | void renderUI(); 48 | void renderText( 49 | const std::string& string, gs::Vec2f position, float scale, 50 | gs::Color fillColor = gs::Color::White, 51 | float outlineThickness = 0.0f, 52 | gs::Color outlineColor = gs::Color() 53 | ); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /hdr/window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //Dependencies 4 | #include "resources.hpp" 5 | 6 | namespace engine { 7 | namespace render { 8 | namespace window { 9 | extern const gs::Vec2i windowSize; 10 | 11 | extern sf::RenderWindow* winmain; 12 | extern bool isFullscreen; 13 | 14 | // Framerate used for events. 15 | extern int framerate; 16 | // Framerate used for rendering. Note: Can be lower than the actual 17 | // framerate. 18 | extern int virtualFramerate; 19 | extern int mouseDelta; 20 | 21 | extern sf::Image icon; 22 | 23 | void create(bool fullscreen); 24 | void initStates(); 25 | void update(); 26 | void close(); 27 | 28 | void begin(gs::Color backgroundColor = gs::Color()); 29 | void end(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/icon.ico -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/icon.png -------------------------------------------------------------------------------- /openal32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/openal32.dll -------------------------------------------------------------------------------- /sfml-audio-2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/sfml-audio-2.dll -------------------------------------------------------------------------------- /sfml-graphics-2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/sfml-graphics-2.dll -------------------------------------------------------------------------------- /sfml-network-2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/sfml-network-2.dll -------------------------------------------------------------------------------- /sfml-system-2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/sfml-system-2.dll -------------------------------------------------------------------------------- /sfml-window-2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialCodeNoodles/Visualizing-Pathfinding-Algorithms/9b771e6688f66f485c1cf23c32018aaf662aba20/sfml-window-2.dll -------------------------------------------------------------------------------- /src/audio.cpp: -------------------------------------------------------------------------------- 1 | #include "../hdr/audio.hpp" 2 | 3 | namespace engine { 4 | namespace audio { 5 | sf::SoundBuffer soundBuffer; 6 | sf::Sound sounds[mNumOfSoundObjects]; 7 | int lastPlayed = 0; 8 | 9 | void loadAssets() { 10 | soundBuffer.loadFromFile("beep.wav"); 11 | 12 | for (sf::Sound& sound : sounds) { 13 | sound.setBuffer(soundBuffer); 14 | sound.setVolume(25.0f); 15 | } 16 | } 17 | 18 | void changeVolume(float newVolume) { 19 | for (sf::Sound& sound : sounds) 20 | sound.setVolume(newVolume); 21 | } 22 | void playSound(float pitch) { 23 | if (lastPlayed == ticks) 24 | return; 25 | 26 | lastPlayed = ticks; 27 | 28 | for (sf::Sound& sound : sounds) { 29 | if (sound.getStatus() == sf::Sound::Status::Stopped) { 30 | sound.setPitch(pitch); 31 | sound.play(); 32 | break; 33 | } 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/grid.cpp: -------------------------------------------------------------------------------- 1 | #include "../hdr/grid.hpp" 2 | #include "../hdr/path.hpp" 3 | #include "../hdr/window.hpp" 4 | 5 | namespace engine { 6 | size_t HashContainer::operator()(const gs::Vec2i& vec) const { 7 | // Used this post as a reference to make hash for 2 component vector. 8 | // https://stackoverflow.com/questions/45395071/hash-for-a-stdpair-for-use-in-an-unordered-map 9 | return size_t(vec.x) << 32 | vec.y; 10 | } 11 | 12 | QueueItem::QueueItem() : cost(0) { 13 | } 14 | QueueItem::QueueItem(gs::Vec2i position, int cost) : QueueItem() { 15 | this->position = position; 16 | this->cost = cost; 17 | } 18 | 19 | bool QueueItem::operator<(const QueueItem& item) const { 20 | return cost < item.cost; 21 | } 22 | bool QueueItem::operator>(const QueueItem& item) const { 23 | return cost > item.cost; 24 | } 25 | 26 | Grid grid; 27 | bool pause = false, 28 | inc = false; 29 | float updateSpeed = 1.0f; 30 | 31 | Grid::Grid() : endMarker({ 23, 11 }), searching(false), tiles(nullptr), 32 | blockSize(0) { 33 | } 34 | Grid::~Grid() { 35 | delete tiles; 36 | } 37 | 38 | void Grid::create(gs::Vec2i size) { 39 | // Allows for reallocation. 40 | if (tiles != nullptr) delete tiles; 41 | 42 | blockSize = size.x * size.y; 43 | tiles = new Tile[blockSize]; 44 | 45 | realSize = size; 46 | this->size = realSize; 47 | 48 | // Fill grid with empty tiles. 49 | clear(1); 50 | } 51 | void Grid::update() { 52 | bool pathGenerated = false; 53 | 54 | if (inc) { 55 | pause = true; 56 | inc = false; 57 | 58 | // Just cause it's 2022 doesn't mean I can't use goto statements. 59 | goto SEARCHING; 60 | } 61 | 62 | if (searching && !pause) { 63 | // If the updateSpeed is larger that 1 update per second then it 64 | // is updated multiple per second. 65 | if (updateSpeed > 1.0f) { 66 | for (int cycle = 0; cycle < updateSpeed; cycle++) { 67 | pathGenerated = floodGrid(*this); 68 | 69 | if (pathGenerated) 70 | break; 71 | } 72 | } 73 | else if (ticks % static_cast(1.0f / updateSpeed) == 0) 74 | SEARCHING: 75 | pathGenerated = floodGrid(*this); 76 | 77 | if (pathGenerated) { 78 | generatePath(*this); 79 | searching = false; 80 | } 81 | } 82 | } 83 | void Grid::clear(Tile tile) { 84 | for (int xpos = 0; xpos < size.x; xpos++) 85 | for (int ypos = 0; ypos < size.y; ypos++) 86 | setTile({ xpos, ypos }, tile); 87 | 88 | clearSearch(*this); 89 | searching = false; 90 | } 91 | void Grid::generateNoise(int coverage) { 92 | clear(1); 93 | 94 | for (int xpos = 0; xpos < size.x; xpos++) { 95 | for (int ypos = 0; ypos < size.y; ypos++) { 96 | gs::Vec2i position = gs::Vec2i(xpos, ypos); 97 | 98 | if (position == startMarker || position == endMarker) 99 | continue; 100 | 101 | // Only covers a certain percentage of the tiles. 102 | if (rand() % 100 < coverage) 103 | setTile(position, 0); 104 | } 105 | } 106 | } 107 | 108 | void Grid::setTile(gs::Vec2i position, Tile tile) { 109 | if (isValidTile(position)) 110 | tiles[getArrayCoordinate(position)] = tile; 111 | } 112 | void Grid::setSize(gs::Vec2i size) { 113 | this->size = size; 114 | } 115 | void Grid::setSize(float percentage) { 116 | // Resizes usable size based on a percentage of the realSize. 117 | size.x = realSize.x * (percentage / 100.0f); 118 | size.y = realSize.y * (percentage / 100.0f); 119 | } 120 | 121 | Tile Grid::getTile(gs::Vec2i position) const { 122 | if (isValidTile(position)) 123 | return tiles[getArrayCoordinate(position)]; 124 | return Tile(-1); 125 | } 126 | gs::Vec2i Grid::getSize() const { 127 | return size; 128 | } 129 | gs::Vec2i Grid::getRealSize() const { 130 | return realSize; 131 | } 132 | bool Grid::isValidTile(gs::Vec2i position) const { 133 | // Note: This uses the size member not realSize that represents the 134 | // fully allocated size. 135 | return position.x >= 0 && position.x < getSize().x && 136 | position.y >= 0 && position.y < getSize().y; 137 | } 138 | 139 | int Grid::getArrayCoordinate(gs::Vec2i position) const { 140 | if (isValidTile(position)) 141 | // Each row is 1width wide so (y * width) + x gets a 1D coordinate. 142 | return (position.y * getRealSize().x) + position.x; 143 | return Tile(-1); 144 | } 145 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../hdr/ui.hpp" 2 | 3 | //Globals 4 | 5 | int ticks = 0; 6 | 7 | void events() { 8 | engine::render::window::update(); 9 | 10 | // Skips events if the window isn't being focused on. 11 | if (!engine::render::window::winmain->hasFocus()) { 12 | ticks--; 13 | return; 14 | } 15 | 16 | engine::grid.update(); 17 | engine::render::ui::updateUI(); 18 | } 19 | void graphics() { 20 | engine::render::window::begin(gs::Color(220, 220, 220)); 21 | 22 | engine::render::renderGrid(engine::grid); 23 | engine::render::ui::renderUI(); 24 | 25 | engine::render::window::end(); 26 | } 27 | 28 | int main() { 29 | srand(time(0)); 30 | gs::util::Clock timer; 31 | 32 | engine::render::window::create(true); 33 | 34 | HWND hwnd = GetConsoleWindow(); 35 | // Hides console window. 36 | ShowWindow(hwnd, 0); 37 | 38 | engine::render::ui::loadAssets(); 39 | engine::audio::loadAssets(); 40 | 41 | // Creates a grid with a maximum size of 100 by 50. 42 | engine::grid.create(gs::Vec2i(100, 50)); 43 | 44 | while (engine::render::window::winmain->isOpen()) { 45 | timer.begin(); 46 | events(); 47 | 48 | if (engine::render::window::virtualFramerate != 49 | engine::render::window::framerate 50 | ) { 51 | if (ticks % (engine::render::window::framerate / 52 | engine::render::window::virtualFramerate) == 0 53 | ) 54 | graphics(); 55 | } 56 | else 57 | graphics(); 58 | 59 | timer.end(); 60 | timer.wait(engine::render::window::framerate); 61 | ticks++; 62 | } 63 | 64 | engine::render::window::close(); 65 | 66 | return 0; 67 | } 68 | 69 | /* 70 | Select Pathfinding Method 71 | 72 | Early Exit 73 | 74 | 75 | Start Pause Reset Inc 76 | Animation Speed Slider 77 | */ -------------------------------------------------------------------------------- /src/path.cpp: -------------------------------------------------------------------------------- 1 | #include "../hdr/path.hpp" 2 | 3 | namespace engine { 4 | PathfindingMethod pathfindingMethod = PathfindingMethod::Breadth; 5 | bool earlyExit = true; 6 | gs::Vec2i focusPoint; 7 | 8 | float heuristic(gs::Vec2i base, gs::Vec2i target) { 9 | return static_cast( 10 | std::abs(base.x - target.x) + std::abs(base.y - target.y) 11 | ); 12 | } 13 | 14 | void initSearch(Grid& grid) { 15 | clearSearch(grid); 16 | 17 | // Get started with the starting location. 18 | grid.frontier.push(grid.startMarker); 19 | grid.priorityFrontier.push(QueueItem(grid.startMarker, 0)); 20 | grid.searched.push_back(grid.startMarker); 21 | grid.pathMap[grid.startMarker] = grid.startMarker; 22 | grid.costMap[grid.startMarker] = 0; 23 | } 24 | void clearSearch(Grid& grid) { 25 | // Function to clear the frontier queues. 26 | static auto clearFrontiers = [&]() { 27 | while (!grid.frontier.empty()) 28 | grid.frontier.pop(); 29 | while (!grid.priorityFrontier.empty()) 30 | grid.priorityFrontier.pop(); 31 | }; 32 | 33 | clearFrontiers(); 34 | grid.searched.clear(); 35 | grid.pathMap.clear(); 36 | grid.costMap.clear(); 37 | grid.path.clear(); 38 | } 39 | bool floodGrid(Grid& grid) { 40 | bool match; 41 | 42 | switch (pathfindingMethod) { 43 | case PathfindingMethod::Breadth: 44 | match = breadthFlood(grid); 45 | break; 46 | case PathfindingMethod::Greedy: 47 | match = greedyFlood(grid); 48 | break; 49 | case PathfindingMethod::AStar: 50 | match = aStarFlood(grid); 51 | break; 52 | } 53 | 54 | // Distance from start to end in the grid. 55 | float pathDistance = gs::util::distance( 56 | grid.startMarker, grid.endMarker 57 | ); 58 | // Distance from the focus point to the end. 59 | float focusPointDistance = gs::util::distance( 60 | focusPoint, grid.endMarker 61 | ); 62 | 63 | // Makes sure there is no dividing by zero. 64 | if (pathDistance > 0.0f) { 65 | // Normalized distance on the path. 66 | float ratio = focusPointDistance / pathDistance; 67 | // Invert to give lower pitches first. 68 | ratio = 1.0f - ratio; 69 | 70 | // Plays pitch from 0.1 to 5.0. 71 | audio::playSound(0.1f + (ratio * 5.0f)); 72 | } 73 | 74 | return match; 75 | } 76 | bool breadthFlood(Grid& grid) { 77 | // Can only search if there are spots left to search. 78 | if (!grid.frontier.empty()) { 79 | // Next position in the frontier queue. 80 | gs::Vec2i currentPosition = grid.frontier.front(); 81 | // Removes position from queue. 82 | grid.frontier.pop(); 83 | focusPoint = currentPosition; 84 | 85 | // Allows searching to stop early if the end position is reached. 86 | if (currentPosition == grid.endMarker && earlyExit) 87 | return true; 88 | 89 | gs::Vec2i nextPositions[4]; 90 | int neighborCount = 0; 91 | 92 | for (int neighborIndex = 0; neighborIndex < 4; neighborIndex++) { 93 | gs::Vec2i testingPosition = currentPosition; 94 | 95 | // Fancy code to generate the positions around the center. 96 | if (neighborIndex % 2 == 0) 97 | testingPosition.y += neighborIndex == 0 ? -1 : 1; 98 | else 99 | testingPosition.x += neighborIndex == 1 ? 1 : -1; 100 | 101 | // If the testingPositions is a valid tile and not solid then 102 | // add it to the positions to be looked at. 103 | if (grid.isValidTile(testingPosition) && 104 | grid.getTile(testingPosition) != 0 105 | ) { 106 | nextPositions[neighborCount] = testingPosition; 107 | neighborCount++; 108 | } 109 | } 110 | 111 | for (int nextIndex = 0; nextIndex < neighborCount; nextIndex++) { 112 | gs::Vec2i nextPosition = nextPositions[nextIndex]; 113 | 114 | // Checks if nextPosition has already been searched. 115 | if (std::find(grid.searched.begin(), grid.searched.end(), 116 | nextPosition) == grid.searched.end() 117 | ) { 118 | grid.frontier.push(nextPosition); 119 | grid.searched.push_back(nextPosition); 120 | // Creates a link between the next position and the 121 | // previous. 122 | grid.pathMap[nextPosition] = currentPosition; 123 | } 124 | } 125 | 126 | return false; 127 | } 128 | 129 | return true; 130 | } 131 | bool greedyFlood(Grid& grid) { 132 | // Can only search if there are spots left to search. 133 | if (!grid.priorityFrontier.empty()) { 134 | // Next position in the frontier queue. 135 | gs::Vec2i currentPosition = grid.priorityFrontier.top().position; 136 | // Removes position from queue. 137 | grid.priorityFrontier.pop(); 138 | focusPoint = currentPosition; 139 | 140 | // Allows searching to stop early if the end position is reached. 141 | if (currentPosition == grid.endMarker && earlyExit) 142 | return true; 143 | 144 | gs::Vec2i nextPositions[4]; 145 | int neighborCount = 0; 146 | 147 | for (int neighborIndex = 0; neighborIndex < 4; neighborIndex++) { 148 | gs::Vec2i testingPosition = currentPosition; 149 | 150 | // Fancy code to generate the positions around the center. 151 | if (neighborIndex % 2 == 0) 152 | testingPosition.y += neighborIndex == 0 ? -1 : 1; 153 | else 154 | testingPosition.x += neighborIndex == 1 ? 1 : -1; 155 | 156 | // If the testingPositions is a valid tile and not solid then 157 | // add it to the positions to be looked at. 158 | if (grid.isValidTile(testingPosition) && 159 | grid.getTile(testingPosition) != 0 160 | ) { 161 | nextPositions[neighborCount] = testingPosition; 162 | neighborCount++; 163 | } 164 | } 165 | 166 | for (int nextIndex = 0; nextIndex < neighborCount; nextIndex++) { 167 | gs::Vec2i nextPosition = nextPositions[nextIndex]; 168 | 169 | // Checks if nextPosition has already been searched. 170 | if (std::find(grid.searched.begin(), grid.searched.end(), 171 | nextPosition) == grid.searched.end() 172 | ) { 173 | grid.priorityFrontier.push(QueueItem( 174 | nextPosition, -heuristic(grid.endMarker, nextPosition) 175 | )); 176 | grid.searched.push_back(nextPosition); 177 | // Creates a link between the next position and the 178 | // previous. 179 | grid.pathMap[nextPosition] = currentPosition; 180 | } 181 | } 182 | 183 | return false; 184 | } 185 | 186 | return true; 187 | } 188 | bool aStarFlood(Grid& grid) { 189 | // Can only search if there are spots left to search. 190 | if (!grid.priorityFrontier.empty()) { 191 | // Next position in the frontier queue. 192 | gs::Vec2i currentPosition = grid.priorityFrontier.top().position; 193 | // Removes position from queue. 194 | grid.priorityFrontier.pop(); 195 | focusPoint = currentPosition; 196 | 197 | // Allows searching to stop early if the end position is reached. 198 | if (currentPosition == grid.endMarker && earlyExit) 199 | return true; 200 | 201 | gs::Vec2i nextPositions[4]; 202 | int neighborCount = 0; 203 | 204 | for (int neighborIndex = 0; neighborIndex < 4; neighborIndex++) { 205 | gs::Vec2i testingPosition = currentPosition; 206 | 207 | // Fancy code to generate the positions around the center. 208 | if (neighborIndex % 2 == 0) 209 | testingPosition.y += neighborIndex == 0 ? -1 : 1; 210 | else 211 | testingPosition.x += neighborIndex == 1 ? 1 : -1; 212 | 213 | // If the testingPositions is a valid tile and not solid then 214 | // add it to the positions to be looked at. 215 | if (grid.isValidTile(testingPosition) && 216 | grid.getTile(testingPosition) != 0 217 | ) { 218 | nextPositions[neighborCount] = testingPosition; 219 | neighborCount++; 220 | } 221 | } 222 | 223 | for (int nextIndex = 0; nextIndex < neighborCount; nextIndex++) { 224 | gs::Vec2i nextPosition = nextPositions[nextIndex]; 225 | float cost = grid.costMap[currentPosition] + 1.0f; 226 | 227 | if (grid.costMap.count(nextPosition) == 0 || 228 | cost < grid.costMap[nextPosition] 229 | ) { 230 | grid.priorityFrontier.push(QueueItem( 231 | nextPosition, 232 | -cost + -heuristic(grid.endMarker, nextPosition) * 1.4f 233 | )); 234 | // Creates a link between the next position and the 235 | // previous. 236 | grid.pathMap[nextPosition] = currentPosition; 237 | grid.costMap[nextPosition] = cost; 238 | } 239 | } 240 | 241 | return false; 242 | } 243 | 244 | return true; 245 | } 246 | void generatePath(Grid& grid) { 247 | // Position on the path. Note: It starts at the end to work backwards 248 | // when generating a path. 249 | gs::Vec2i currentPosition = grid.endMarker; 250 | gs::Vec2i prvsPosition = gs::Vec2i(-1, -1); 251 | 252 | // Resets the path. 253 | grid.path.clear(); 254 | 255 | // Makes sure the position is actually in the pathMap. 256 | if (grid.pathMap.count(currentPosition) != 0) { 257 | while (currentPosition != grid.startMarker) { 258 | grid.path.push_back(currentPosition); 259 | // Follows path to the next position. 260 | currentPosition = grid.pathMap[currentPosition]; 261 | 262 | // If the prvsPosition is the same as the currentPosition then 263 | // there is not a valid path that can be generated. 264 | if (prvsPosition == currentPosition) { 265 | grid.path.clear(); 266 | return; 267 | } 268 | 269 | prvsPosition = currentPosition; 270 | } 271 | 272 | // Adds final position to the path. 273 | grid.path.push_back(currentPosition); 274 | } 275 | } 276 | } -------------------------------------------------------------------------------- /src/render.cpp: -------------------------------------------------------------------------------- 1 | #include "../hdr/render.hpp" 2 | #include "../hdr/ui.hpp" 3 | 4 | namespace engine { 5 | namespace render { 6 | const gs::Vec2f constrainedGridSize = gs::Vec2f(1600, 800); 7 | const gs::Vec2f gridOffset = gs::Vec2f(3.0f, 3.0f); 8 | 9 | sf::RectangleShape tile; 10 | gs::Vec2f tileSize; 11 | bool displayFrontier = true; 12 | bool displaySearched = true; 13 | 14 | bool mouseOnGrid() { 15 | // Current position of mouse. 16 | const gs::Vec2f mousePosition = gs::input::mousePosition; 17 | return mousePosition.x >= gridOffset.x && 18 | mousePosition.x <= constrainedGridSize.x + gridOffset.x && 19 | mousePosition.y >= gridOffset.y && 20 | mousePosition.y <= constrainedGridSize.y + gridOffset.y; 21 | } 22 | gs::Vec2i getMouseTile(const Grid& grid) { 23 | // Size of tile for rendering. 24 | tileSize = gs::Vec2f( 25 | constrainedGridSize.x / grid.size.x, 26 | constrainedGridSize.y / grid.size.y 27 | ); 28 | 29 | gs::Vec2i blockTile; 30 | 31 | // Constrains tile to bounds of grid. 32 | blockTile.x = gs::util::clamp( 33 | gs::input::mousePosition.x - gridOffset.x, 0.0f, 34 | constrainedGridSize.x 35 | ); 36 | blockTile.y = gs::util::clamp( 37 | gs::input::mousePosition.y - gridOffset.y, 0.0f, 38 | constrainedGridSize.y 39 | ); 40 | 41 | // Scales tile to tileSize. 42 | blockTile.x /= tileSize.x; 43 | blockTile.y /= tileSize.y; 44 | 45 | // Constrains tile position to grid size. 46 | gs::util::clamp(&blockTile.x, 0, grid.size.x - 1); 47 | gs::util::clamp(&blockTile.y, 0, grid.size.y - 1); 48 | 49 | return blockTile; 50 | } 51 | 52 | void renderGrid(const Grid& grid) { 53 | // Size of tile for rendering. 54 | tileSize = gs::Vec2f( 55 | constrainedGridSize.x / grid.size.x, 56 | constrainedGridSize.y / grid.size.y 57 | ); 58 | 59 | tile.setSize(gs::Vec2f( 60 | std::floor(tileSize.x) - 3.0f, // Creates a gap between tiles. 61 | std::floor(tileSize.y) - 3.0f 62 | )); 63 | // Fills gap with outline. 64 | tile.setOutlineThickness(3.0f); 65 | tile.setOutlineColor(gs::Color::White); 66 | 67 | for (int xpos = 0; xpos < grid.size.x; xpos++) { 68 | for (int ypos = 0; ypos < grid.size.y; ypos++) { 69 | Tile currentTile = grid.getTile({ xpos, ypos }); 70 | gs::Color currentTileColor; 71 | 72 | tile.setPosition( 73 | gridOffset.x + (xpos * tileSize.x), 74 | gridOffset.y + (ypos * tileSize.y) 75 | ); 76 | 77 | if (currentTile != -1) { 78 | switch (currentTile) { 79 | case 0: // Solid tile 80 | currentTileColor = gs::Color(70, 70, 75); 81 | break; 82 | case 1: // Empty tile 83 | currentTileColor = gs::Color(200, 200, 180); 84 | break; 85 | } 86 | 87 | // Highlights tile when selected. 88 | if (getMouseTile(grid) == gs::Vec2i(xpos, ypos) && 89 | mouseOnGrid() 90 | ) 91 | currentTileColor = gs::util::approach( 92 | currentTileColor, gs::Color::White, 15.0f 93 | ); 94 | 95 | tile.setFillColor(currentTileColor); 96 | 97 | window::winmain->draw(tile); 98 | } 99 | } 100 | } 101 | 102 | if (displaySearched) { 103 | // Renders costMap instead of the searched vector when using 104 | // the A* Algorithm. 105 | if (pathfindingMethod == PathfindingMethod::AStar) 106 | renderCostMap(grid); 107 | else 108 | renderSearched(grid); 109 | } 110 | if (displayFrontier) { 111 | if (pathfindingMethod == PathfindingMethod::Breadth) 112 | renderFrontier(grid); 113 | // The other pathfinding algorithms use a priority queue 114 | // instead of a normal one. 115 | else 116 | renderPriorityFrontier(grid); 117 | } 118 | renderPath(grid); 119 | 120 | tile.setOutlineColor(gs::Color::Black); 121 | 122 | // Makes sure the start marker is actually on the grid. 123 | if (grid.isValidTile(grid.startMarker)) { 124 | tile.setPosition( 125 | gridOffset.x + (grid.startMarker.x * tileSize.x), 126 | gridOffset.y + (grid.startMarker.y * tileSize.y) 127 | ); 128 | tile.setFillColor(gs::Color(50, 235, 40)); 129 | 130 | // Highlights start marker when selected. 131 | if (ui::startMarkerLocked || (mouseOnGrid() && 132 | getMouseTile(grid) == grid.startMarker) 133 | ) 134 | tile.setFillColor(gs::util::approach( 135 | tile.getFillColor(), gs::Color::White, 15.0f 136 | )); 137 | 138 | window::winmain->draw(tile); 139 | } 140 | if (grid.isValidTile(grid.endMarker)) { 141 | tile.setPosition( 142 | gridOffset.x + (grid.endMarker.x * tileSize.x), 143 | gridOffset.y + (grid.endMarker.y * tileSize.y) 144 | ); 145 | tile.setFillColor(gs::Color(235, 50, 40)); 146 | 147 | // Highlights end marker when selected. 148 | if (ui::endMarkerLocked || (mouseOnGrid() && 149 | getMouseTile(grid) == grid.endMarker) 150 | ) 151 | tile.setFillColor(gs::util::approach( 152 | tile.getFillColor(), gs::Color::White, 15.0f 153 | )); 154 | 155 | window::winmain->draw(tile); 156 | } 157 | } 158 | void renderPath(const Grid& grid) { 159 | static sf::RectangleShape pathSegment; 160 | 161 | // This code is ugly af. 162 | pathSegment.setSize(gs::Vec2f( 163 | tileSize.x / 3.0f, (tileSize.y / 2.0f) + (tileSize.x / 6.0f) 164 | )); 165 | pathSegment.setOrigin( 166 | pathSegment.getSize().x / 2.0f, pathSegment.getSize().y 167 | - (tileSize.x / 6.0f) 168 | ); 169 | pathSegment.setFillColor(gs::Color(200, 0, 255)); 170 | 171 | // Loops through the path positions starting on the second. Note: 172 | // I casted the size to an int to prevent an integer underflow. 173 | for (int pathIndex = 1; pathIndex < 174 | static_cast(grid.path.size()) - 1; pathIndex++ 175 | ) { 176 | // Previous position in path. 177 | gs::Vec2i prvsPosition = grid.path[pathIndex - 1]; 178 | // Center position in path. 179 | gs::Vec2i currentPosition = grid.path[pathIndex]; 180 | // Next position in path. 181 | gs::Vec2i nextPosition = grid.path[pathIndex + 1]; 182 | 183 | if (!grid.isValidTile(currentPosition)) 184 | continue; 185 | 186 | // Vector difference in distance between the previous step and 187 | // the current. This is used to create on half of the path. 188 | gs::Vec2i backDelta = prvsPosition - currentPosition; 189 | // Vector difference in distance between the next step and the 190 | // current. 191 | gs::Vec2i frontDelta = nextPosition - currentPosition; 192 | 193 | // Loops through both deltas to build path. 194 | for (gs::Vec2i segmentDelta : { backDelta, frontDelta }) { 195 | // Upward vertical segment. 196 | if (segmentDelta == gs::Vec2i(0, -1)) 197 | pathSegment.setRotation(0.0f); 198 | // Leftward horizontal segment. 199 | else if (segmentDelta == gs::Vec2i(-1, 0)) 200 | pathSegment.setRotation(-90.0f); 201 | // Downward vertical segment. 202 | else if (segmentDelta == gs::Vec2i(0, 1)) 203 | pathSegment.setRotation(-180.0f); 204 | // Rightward horizontal segment. 205 | else 206 | pathSegment.setRotation(-270.0f); 207 | 208 | pathSegment.setPosition( 209 | gridOffset.x + (currentPosition.x * tileSize.x), 210 | gridOffset.y + (currentPosition.y * tileSize.y) 211 | ); 212 | // Centers segment in the middle of the tile. 213 | pathSegment.move(tileSize * 0.5f); 214 | 215 | window::winmain->draw(pathSegment); 216 | } 217 | } 218 | } 219 | void renderFrontier(const Grid& grid) { 220 | // Copy of the grid frontier. Note: I need to make a copy to access 221 | // all the elements. 222 | std::queue frontier = grid.frontier; 223 | 224 | tile.setOutlineColor(gs::Color::Black); 225 | 226 | while (!frontier.empty()) { 227 | gs::Vec2i frontierPosition = frontier.front(); 228 | frontier.pop(); 229 | 230 | if (grid.isValidTile(frontierPosition)) { 231 | tile.setPosition( 232 | gridOffset.x + (frontierPosition.x * tileSize.x), 233 | gridOffset.y + (frontierPosition.y * tileSize.y) 234 | ); 235 | tile.setFillColor(gs::Color(200, 230, 255, 220)); 236 | 237 | window::winmain->draw(tile); 238 | } 239 | } 240 | } 241 | void renderPriorityFrontier(const Grid& grid) { 242 | // Copy of the grid priority frontier. Note: I need to make a copy 243 | // to access all the elements. 244 | std::priority_queue priorityFrontier = 245 | grid.priorityFrontier; 246 | 247 | tile.setOutlineColor(gs::Color::Black); 248 | 249 | while (!priorityFrontier.empty()) { 250 | gs::Vec2i frontierPosition = priorityFrontier.top().position; 251 | priorityFrontier.pop(); 252 | 253 | if (grid.isValidTile(frontierPosition)) { 254 | tile.setPosition( 255 | gridOffset.x + (frontierPosition.x * tileSize.x), 256 | gridOffset.y + (frontierPosition.y * tileSize.y) 257 | ); 258 | tile.setFillColor(gs::Color(200, 230, 255, 220)); 259 | 260 | window::winmain->draw(tile); 261 | } 262 | } 263 | } 264 | void renderSearched(const Grid& grid) { 265 | for (gs::Vec2i searchedPosition : grid.searched) { 266 | if (grid.isValidTile(searchedPosition)) { 267 | tile.setPosition( 268 | gridOffset.x + (searchedPosition.x * tileSize.x), 269 | gridOffset.y + (searchedPosition.y * tileSize.y) 270 | ); 271 | tile.setFillColor(gs::Color(0, 0, 0, 50)); 272 | 273 | window::winmain->draw(tile); 274 | } 275 | } 276 | } 277 | void renderCostMap(const Grid& grid) { 278 | // Gets the [ Key, Value ] pairs within the costMap. So pair.first 279 | // corresponds to the position key. 280 | for (const auto& pair : grid.costMap) { 281 | if (grid.isValidTile(pair.first)) { 282 | tile.setPosition( 283 | gridOffset.x + (pair.first.x * tileSize.x), 284 | gridOffset.y + (pair.first.y * tileSize.y) 285 | ); 286 | tile.setFillColor(gs::Color(0, 0, 0, 50)); 287 | 288 | window::winmain->draw(tile); 289 | } 290 | } 291 | } 292 | } 293 | } -------------------------------------------------------------------------------- /src/ui.cpp: -------------------------------------------------------------------------------- 1 | #include "../hdr/ui.hpp" 2 | 3 | namespace engine { 4 | namespace render { 5 | namespace ui { 6 | sf::Font font; 7 | gs::Text text; 8 | sf::Texture checkBoxOffTexture, checkBoxOnTexture; 9 | gs::Menu menu; 10 | gs::Slider gridSizeSlider; 11 | gs::Slider animationSpeedSlider; 12 | gs::Checkbox methodButtons[3]; 13 | gs::Checkbox exitEarlyButton; 14 | gs::Button animationButtons[4]; 15 | std::string animationButtonText[4] = { 16 | "Start", "Pause", "Reset", "Inc" 17 | }; 18 | gs::Checkbox displayFrontierButton; 19 | gs::Checkbox displaySearchedButton; 20 | gs::Button framerateButton; 21 | gs::Button gridButtons[4]; 22 | std::string gridButtonText[4] = { 23 | "Clear Grid", "Fill Grid", "Light Noise", "Heavy Noise" 24 | }; 25 | gs::Button exitButton; 26 | gs::Slider soundSlider; 27 | bool startMarkerLocked = false; 28 | bool endMarkerLocked = false; 29 | 30 | void loadAssets() { 31 | font.loadFromFile("font.ttf"); 32 | checkBoxOffTexture.loadFromFile("checkBoxOff.png"); 33 | checkBoxOnTexture.loadFromFile("checkBoxOn.png"); 34 | 35 | handleAssets(); 36 | } 37 | void handleAssets() { 38 | // text 39 | text.setFont(font); 40 | 41 | // gridSizeSlider 42 | gridSizeSlider.setSize(constrainedGridSize.x - 50.0f, 20.0f); 43 | gridSizeSlider.setPosition(gridOffset.x + 25.0f, 880.0f); 44 | gridSizeSlider.setOnColor(gs::Color::Transparent); 45 | gridSizeSlider.setOffColor(gs::Color::Transparent); 46 | gridSizeSlider.setOnOutlineThickness(3.0f); 47 | gridSizeSlider.setOffOutlineThickness(3.0f); 48 | gridSizeSlider.button.setSize(200.0f, 22.0f); 49 | gridSizeSlider.button.setSelectedFillColor(gs::Color( 50 | 100, 100, 100) 51 | ); 52 | gridSizeSlider.button.setScaleModifiers(1.0f); 53 | gridSizeSlider.setPercentage(25.0f); 54 | menu.add(&gridSizeSlider); 55 | 56 | // animationSpeedSlider 57 | animationSpeedSlider = gridSizeSlider; 58 | animationSpeedSlider.setSize(260.0f, 20.0f); 59 | animationSpeedSlider.move(1600.0f, 0.0f); 60 | animationSpeedSlider.button.setSize(50.0f, 22.0f); 61 | animationSpeedSlider.setPercentage(25.0f); 62 | menu.add(&animationSpeedSlider); 63 | 64 | // methodButtons 65 | methodButtons[0].setSize(270.0f, 50.0f); 66 | methodButtons[0].setPosition( 67 | window::windowSize.x - ((window::windowSize.x - 68 | constrainedGridSize.x - methodButtons[0].getSize().x) 69 | / 2.0f) - methodButtons[0].getSize().x, 70 | 188.0f 71 | ); 72 | methodButtons[0].setInactiveTextFillColor(gs::Color::White); 73 | methodButtons[0].setClickedTextFillColor(gs::Color::White); 74 | methodButtons[0].setScaleModifiers(1.0f); 75 | methodButtons[0].setFont(font); 76 | methodButtons[0].setTextScale(0.65f, 0.65f); 77 | methodButtons[0].setString("Breadth First Search"); 78 | menu.add(&methodButtons[0]); 79 | 80 | methodButtons[1] = methodButtons[0]; 81 | methodButtons[1].move(0.0f, 70.0f); 82 | methodButtons[1].setString("Greedy Best First"); 83 | menu.add(&methodButtons[1]); 84 | 85 | methodButtons[2] = methodButtons[1]; 86 | methodButtons[2].move(0.0f, 70.0f); 87 | methodButtons[2].setString("A* Algorithm"); 88 | menu.add(&methodButtons[2]); 89 | 90 | methodButtons[0].eventTriggerer = 91 | gs::Button::EventTriggerer::None; 92 | methodButtons[0].isSelected = true; 93 | methodButtons[0].isClickedOn = true; 94 | methodButtons[0].update(); 95 | methodButtons[0].eventTriggerer = 96 | gs::Button::EventTriggerer::ActiveMouse; 97 | 98 | // exitEarlyButton 99 | exitEarlyButton.setSize(30.0f, 30.0f); 100 | exitEarlyButton.setPosition(1640.0f, 400.0f); 101 | exitEarlyButton.clear(); 102 | exitEarlyButton.eventTriggerer = 103 | gs::Button::EventTriggerer::None; 104 | exitEarlyButton.isSelected = true; 105 | exitEarlyButton.isClickedOn = true; 106 | exitEarlyButton.update(); 107 | exitEarlyButton.eventTriggerer = 108 | gs::Button::EventTriggerer::ActiveMouse; 109 | menu.add(&exitEarlyButton); 110 | 111 | // displayFrontierButton 112 | displayFrontierButton = exitEarlyButton; 113 | displayFrontierButton.move(0.0f, 165.0f); 114 | menu.add(&displayFrontierButton); 115 | 116 | // displaySearchedButton 117 | displaySearchedButton = displayFrontierButton; 118 | displaySearchedButton.move(0.0f, 50.0f); 119 | menu.add(&displaySearchedButton); 120 | 121 | // framerateButton 122 | framerateButton.applyStyle(methodButtons[0].getStyle()); 123 | framerateButton.setSize(220.0f, 50.0f); 124 | framerateButton.setPosition(1625.0f, 675.0f); 125 | framerateButton.setFont(font); 126 | framerateButton.setTextScale(0.65f, 0.65f); 127 | framerateButton.setString("Change Framerate"); 128 | menu.add(&framerateButton); 129 | 130 | // animationButtons 131 | for (int animationButtonIndex = 0; animationButtonIndex < 4; 132 | animationButtonIndex++ 133 | ) { 134 | gs::Button& currentAnimationButton = 135 | animationButtons[animationButtonIndex]; 136 | 137 | currentAnimationButton.applyStyle( 138 | methodButtons[0].getStyle() 139 | ); 140 | currentAnimationButton.setSize(75.0f, 50.0f); 141 | currentAnimationButton.setPosition(1606.0f, 753.0f); 142 | currentAnimationButton.move( 143 | 78.0f * animationButtonIndex, 0.0f 144 | ); 145 | currentAnimationButton.setFont(font); 146 | currentAnimationButton.setTextScale(0.65f, 0.65f); 147 | currentAnimationButton.setString( 148 | animationButtonText[animationButtonIndex] 149 | ); 150 | 151 | menu.add(¤tAnimationButton); 152 | } 153 | 154 | // gridButtons 155 | for (int gridButtonIndex = 0; gridButtonIndex < 4; 156 | gridButtonIndex++ 157 | ) { 158 | gs::Button& currentGridButton = 159 | gridButtons[gridButtonIndex]; 160 | 161 | currentGridButton.applyStyle(methodButtons[0].getStyle()); 162 | currentGridButton.setSize(350.0f, 80.0f); 163 | currentGridButton.setPosition(100.0f, 950.0f); 164 | currentGridButton.move(353.0f * gridButtonIndex, 0.0f); 165 | currentGridButton.setFont(font); 166 | currentGridButton.setTextScale(1.0f, 1.0f); 167 | currentGridButton.setString( 168 | gridButtonText[gridButtonIndex] 169 | ); 170 | 171 | menu.add(¤tGridButton); 172 | } 173 | 174 | // exitButton 175 | exitButton.setSize(100.0f, 50.0f); 176 | exitButton.setPosition(1800.0f, 1010.0f); 177 | exitButton.setShapeColors(gs::Color::Transparent); 178 | exitButton.setScaleModifiers(1.0f); 179 | exitButton.setFont(font); 180 | exitButton.setString("Exit"); 181 | exitButton.setInactiveTextFillColor(gs::Color::Black); 182 | exitButton.setSelectedTextFillColor(gs::Color(100, 100, 100)); 183 | exitButton.setColorAdjustSpeed(100.0f); 184 | menu.add(&exitButton); 185 | 186 | menu.update(); 187 | 188 | // soundSlider 189 | soundSlider = animationSpeedSlider; 190 | soundSlider.move(0.0f, 100.0f); 191 | menu.add(&soundSlider); 192 | } 193 | 194 | void updateUI() { 195 | // Handle UI elements. 196 | 197 | // Moves slider with scroll wheel. 198 | gridSizeSlider.button.move(window::mouseDelta * -32.0f, 0.0f); 199 | 200 | // Sets chech box textures for all of the check boxes. 201 | exitEarlyButton.setTexture( 202 | earlyExit ? checkBoxOnTexture : checkBoxOffTexture 203 | ); 204 | displayFrontierButton.setTexture( 205 | displayFrontier ? checkBoxOnTexture : checkBoxOffTexture 206 | ); 207 | displaySearchedButton.setTexture( 208 | displaySearched ? checkBoxOnTexture : checkBoxOffTexture 209 | ); 210 | 211 | menu.update(); 212 | 213 | float gridScale = std::floor(gridSizeSlider.getPercentage()); 214 | 215 | // Makes sure the percentage is even to prevent uneven tiles. 216 | if (static_cast(gridScale) % 2 == 1) 217 | gridScale -= 1.0f; 218 | 219 | gridScale = std::max(gridScale, 2.0f); 220 | grid.setSize(gridScale); 221 | 222 | float animationSpeed = animationSpeedSlider.getPercentage(); 223 | 224 | // Updates the updating speed for the grid flooding. 225 | if (animationSpeed > 50.0f) { 226 | animationSpeed -= 49.0f; 227 | updateSpeed = animationSpeed; 228 | } 229 | else 230 | updateSpeed = animationSpeed / 50.0f; 231 | 232 | for (int methodButtonIndex = 0; methodButtonIndex < 3; 233 | methodButtonIndex++ 234 | ) { 235 | // Current method button being checked for a mouse event. 236 | gs::Checkbox& methodButton = 237 | methodButtons[methodButtonIndex]; 238 | 239 | if (methodButton.isSelected && methodButton.isClickedOn) { 240 | PathfindingMethod prvsMethod = pathfindingMethod; 241 | 242 | // Updates the pathfinding method. 243 | pathfindingMethod = static_cast( 244 | methodButtonIndex 245 | ); 246 | 247 | if (pathfindingMethod != prvsMethod) { 248 | clearSearch(grid); 249 | grid.searching = false; 250 | 251 | // This loop disables the other method boxes when a new 252 | // one is clicked on. 253 | for (int buttonInc = 1; buttonInc < 3; buttonInc++) { 254 | gs::Checkbox& nextButton = methodButtons[ 255 | (methodButtonIndex + buttonInc) % 3 256 | ]; 257 | 258 | // Only disables button if already selected. 259 | if (nextButton.isClickedOn) { 260 | nextButton.eventTriggerer = 261 | gs::Button::EventTriggerer::None; 262 | nextButton.isSelected = true; 263 | nextButton.update(); 264 | nextButton.eventTriggerer = 265 | gs::Button::EventTriggerer::ActiveMouse; 266 | } 267 | } 268 | } 269 | } 270 | } 271 | 272 | earlyExit = exitEarlyButton.isClickedOn; 273 | displayFrontier = displayFrontierButton.isClickedOn; 274 | displaySearched = displaySearchedButton.isClickedOn; 275 | 276 | // Changes framerate. 277 | if (framerateButton.isSelected && framerateButton.isClickedOn) 278 | window::virtualFramerate = window::virtualFramerate == 30 ? 279 | 60 : 30; 280 | 281 | // Starts search. 282 | if (animationButtons[0].isSelected && 283 | animationButtons[0].isClickedOn 284 | ) { 285 | initSearch(grid); 286 | 287 | grid.searching = true; 288 | pause = false; 289 | } 290 | // Pauses search. 291 | else if (animationButtons[1].isSelected && 292 | animationButtons[1].isClickedOn 293 | ) 294 | pause = !pause; 295 | // Resets search. 296 | else if (animationButtons[2].isSelected && 297 | animationButtons[2].isClickedOn 298 | ) { 299 | clearSearch(grid); 300 | grid.searching = false; 301 | } 302 | // Increments search. 303 | else if (animationButtons[3].isSelected && 304 | animationButtons[3].isClickedOn 305 | ) 306 | inc = true; 307 | 308 | // Clears grid. 309 | if (gridButtons[0].isSelected && gridButtons[0].isClickedOn) 310 | grid.clear(1); 311 | // Fills grid with solid tiles. 312 | else if (gridButtons[1].isSelected && 313 | gridButtons[1].isClickedOn 314 | ) 315 | grid.clear(0); 316 | // Generates noise at a level of 15%. 317 | else if (gridButtons[2].isSelected && 318 | gridButtons[2].isClickedOn 319 | ) 320 | grid.generateNoise(15); 321 | // Generates noise at a level of 30%. 322 | else if (gridButtons[3].isSelected && 323 | gridButtons[3].isClickedOn 324 | ) 325 | grid.generateNoise(30); 326 | 327 | // Closes application if exit is pressed. 328 | if (exitButton.isSelected && exitButton.isClickedOn) 329 | window::winmain->close(); 330 | 331 | audio::changeVolume(soundSlider.getPercentage()); 332 | 333 | // Handle tile code. 334 | 335 | // Start marker interaction. 336 | if (!startMarkerLocked && !endMarkerLocked && 337 | getMouseTile(grid) == grid.startMarker && 338 | gs::input::activeMouseClickL 339 | ) 340 | startMarkerLocked = true; 341 | if (startMarkerLocked) { 342 | if (!gs::input::activeMouseClickL || !mouseOnGrid()) 343 | startMarkerLocked = false; 344 | else { 345 | grid.startMarker = getMouseTile(grid); 346 | // Resets grid if start marker is moved. This is due to 347 | // the path needing to be regenerated to work so it is 348 | // instead reset. 349 | clearSearch(grid); 350 | grid.searching = false; 351 | } 352 | } 353 | // End marker interaction. 354 | if (!endMarkerLocked && !startMarkerLocked && 355 | getMouseTile(grid) == grid.endMarker && 356 | gs::input::activeMouseClickL 357 | ) 358 | endMarkerLocked = true; 359 | if (endMarkerLocked) { 360 | if (!gs::input::activeMouseClickL || !mouseOnGrid()) 361 | endMarkerLocked = false; 362 | else { 363 | grid.endMarker = getMouseTile(grid); 364 | // Updates path after moving end marker. 365 | generatePath(grid); 366 | } 367 | } 368 | 369 | // Allows walls to be placed and erased. 370 | if (mouseOnGrid()) { 371 | if (!startMarkerLocked && !endMarkerLocked) { 372 | // Left mouse click to fill tile. 373 | if (gs::input::activeMouseClickL) 374 | grid.setTile(getMouseTile(grid), 0); 375 | // Right mouse click to delete tile. 376 | else if (gs::input::activeMouseClickR) 377 | grid.setTile(getMouseTile(grid), 1); 378 | } 379 | } 380 | } 381 | 382 | void renderUI() { 383 | // Text information. 384 | 385 | renderText( 386 | "Grid Size", { 720.0f, 820.0f }, 1.0f, gs::Color::Black 387 | ); 388 | 389 | renderText( 390 | "Pathfinding\nSettings", { 1638.0f, 20.0f }, 1.25f, 391 | gs::Color::Black 392 | ); 393 | renderText( 394 | "Method", { 1700.0f, 125.0f }, 1.0f, gs::Color::Black 395 | ); 396 | renderText( 397 | "Exit Search Early", { 1680.0f, 403.0f }, 0.65f, 398 | gs::Color::Black 399 | ); 400 | renderText( 401 | "Display Frontier", { 1680.0f, 568.0f }, 0.65f, 402 | gs::Color::Black 403 | ); 404 | renderText( 405 | "Display Searched", { 1680.0f, 618.0f }, 0.65f, 406 | gs::Color::Black 407 | ); 408 | renderText( 409 | std::to_string(window::virtualFramerate), 410 | { 1868.0f, 687.0f }, 0.75f, gs::Color::Black 411 | ); 412 | 413 | renderText( 414 | "Animation\nSettings", { 1638.0f, 450.0f }, 1.25f, 415 | gs::Color::Black 416 | ); 417 | renderText( 418 | "Animation Speed", { 1622.0f, 820.0f }, 1.0f, 419 | gs::Color::Black 420 | ); 421 | renderText( 422 | "Sound Volume", { 1648.0f, 920.0f }, 1.0f, 423 | gs::Color::Black 424 | ); 425 | 426 | // Note: This renders all components attached to the menu. 427 | gs::draw(window::winmain, menu); 428 | } 429 | void renderText( 430 | const std::string& string, gs::Vec2f position, float scale, 431 | gs::Color fillColor, float outlineThickness, 432 | gs::Color outlineColor 433 | ) { 434 | text.setString(string); 435 | text.setPosition(position); 436 | text.setScale(scale, scale); 437 | text.setFillColor(fillColor); 438 | text.setOutlineThickness(outlineThickness); 439 | text.setOutlineColor(outlineColor); 440 | 441 | gs::draw(window::winmain, text); 442 | } 443 | } 444 | } 445 | } -------------------------------------------------------------------------------- /src/window.cpp: -------------------------------------------------------------------------------- 1 | #include "../hdr/window.hpp" 2 | 3 | namespace engine { 4 | namespace render { 5 | namespace window { 6 | const gs::Vec2i windowSize = gs::Vec2i(1920, 1080); 7 | 8 | sf::RenderWindow* winmain = nullptr; 9 | bool isFullscreen; 10 | 11 | int framerate = 60; 12 | int virtualFramerate = 60; 13 | int mouseDelta; 14 | 15 | sf::Image icon; 16 | 17 | void create(bool fullscreen) { 18 | isFullscreen = fullscreen; 19 | 20 | if (winmain == nullptr) { 21 | winmain = new sf::RenderWindow( 22 | sf::VideoMode(windowSize.x, windowSize.y), 23 | "Pathfinding Visualization", mGetStyle(isFullscreen) 24 | ); 25 | } 26 | else { 27 | winmain->create( 28 | sf::VideoMode(windowSize.x, windowSize.y), 29 | "Pathfinding Visualization", mGetStyle(isFullscreen) 30 | ); 31 | } 32 | 33 | initStates(); 34 | } 35 | void initStates() { 36 | gs::input::setWindow(winmain); 37 | 38 | // Scales window to desktop size. 39 | winmain->setSize(gs::Vec2u( 40 | sf::VideoMode::getDesktopMode().width, 41 | sf::VideoMode::getDesktopMode().height 42 | )); 43 | 44 | if (icon.getSize().x == 0) 45 | icon.loadFromFile("icon.png"); 46 | 47 | winmain->setIcon( 48 | icon.getSize().x, icon.getSize().y, icon.getPixelsPtr() 49 | ); 50 | } 51 | void update() { 52 | sf::Event action; 53 | 54 | gs::input::updateInputs(); 55 | 56 | mouseDelta = 0; 57 | 58 | while (winmain->pollEvent(action)) { 59 | gs::input::updateEvents(action); 60 | 61 | switch (action.type) { 62 | case sf::Event::Closed: 63 | winmain->close(); 64 | break; 65 | case sf::Event::MouseWheelScrolled: 66 | mouseDelta = action.mouseWheelScroll.delta; 67 | break; 68 | case sf::Event::KeyPressed: 69 | switch (action.key.code) { 70 | case sf::Keyboard::Escape: 71 | winmain->close(); 72 | break; 73 | case sf::Keyboard::F11: 74 | create(!isFullscreen); 75 | break; 76 | } 77 | break; 78 | } 79 | } 80 | } 81 | void close() { 82 | delete winmain; 83 | } 84 | 85 | void begin(gs::Color backgroundColor) { 86 | winmain->clear(backgroundColor); 87 | } 88 | void end() { 89 | winmain->display(); 90 | } 91 | } 92 | } 93 | } --------------------------------------------------------------------------------