pathfinding_astar

This commit is contained in:
2026-06-12 17:18:05 +03:30
commit 36e7ba2008
11 changed files with 796 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
cmake_minimum_required(VERSION 3.16)
project(PathfindingAStar VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find packages
find_package(OpenGL REQUIRED)
find_package(glfw3 REQUIRED)
find_package(GLEW REQUIRED)
find_package(glm REQUIRED)
# Source files
set(SOURCES
src/main.cpp
src/Grid.cpp
src/Renderer.cpp
src/AStar.cpp
)
set(HEADERS
src/Grid.h
src/Renderer.h
src/AStar.h
src/Shader.h
)
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_link_libraries(${PROJECT_NAME} PRIVATE
OpenGL::GL
glfw
GLEW::GLEW
glm::glm
)
# Copy shaders to build directory
file(COPY ${CMAKE_SOURCE_DIR}/shaders DESTINATION ${CMAKE_BINARY_DIR})
+8
View File
@@ -0,0 +1,8 @@
#version 330 core
out vec4 FragColor;
uniform vec3 color;
void main() {
FragColor = vec4(color, 1.0);
}
+8
View File
@@ -0,0 +1,8 @@
#version 330 core
layout (location = 0) in vec2 aPos;
uniform mat4 model;
void main() {
gl_Position = model * vec4(aPos, 0.0, 1.0);
}
+104
View File
@@ -0,0 +1,104 @@
#include "AStar.h"
#include <cmath>
#include <algorithm>
AStar::AStar(Grid& grid) : grid_(grid) {}
float AStar::heuristic(int x1, int y1, int x2, int y2) const {
// Euclidean distance
float dx = static_cast<float>(x1 - x2);
float dy = static_cast<float>(y1 - y2);
return std::sqrt(dx * dx + dy * dy);
}
void AStar::reset() {
running_ = false;
found_ = false;
while (!openSet_.empty()) openSet_.pop();
gScore_.clear();
cameFrom_.clear();
}
bool AStar::findPath(int startX, int startY, int endX, int endY) {
reset();
endX_ = endX;
endY_ = endY;
if (!grid_.isWalkable(startX, startY) || !grid_.isWalkable(endX, endY)) {
return false;
}
running_ = true;
Node start;
start.x = startX;
start.y = startY;
start.gCost = 0.0f;
start.hCost = heuristic(startX, startY, endX, endY);
openSet_.push(start);
gScore_[startY * grid_.getWidth() + startX] = 0.0f;
while (!openSet_.empty()) {
Node current = openSet_.top();
openSet_.pop();
if (current.x == endX && current.y == endY) {
reconstructPath(endX, endY);
running_ = false;
found_ = true;
return true;
}
// Mark as closed
if (grid_.getCell(current.x, current.y).type != CellType::Start) {
grid_.setCellType(current.x, current.y, CellType::Closed);
}
auto neighbors = grid_.getNeighbors(current.x, current.y);
for (auto* neighbor : neighbors) {
int nx = neighbor->x;
int ny = neighbor->y;
float tentativeG = current.gCost + heuristic(current.x, current.y, nx, ny);
int key = ny * grid_.getWidth() + nx;
if (gScore_.find(key) == gScore_.end() || tentativeG < gScore_[key]) {
cameFrom_[key] = {current.x, current.y};
gScore_[key] = tentativeG;
Node next;
next.x = nx;
next.y = ny;
next.gCost = tentativeG;
next.hCost = heuristic(nx, ny, endX, endY);
openSet_.push(next);
if (grid_.getCell(nx, ny).type != CellType::End) {
grid_.setCellType(nx, ny, CellType::Open);
}
}
}
}
running_ = false;
return false;
}
void AStar::reconstructPath(int endX, int endY) {
int key = endY * grid_.getWidth() + endX;
while (cameFrom_.find(key) != cameFrom_.end()) {
auto [px, py] = cameFrom_[key];
if (grid_.getCell(px, py).type != CellType::Start) {
grid_.setCellType(px, py, CellType::Path);
}
key = py * grid_.getWidth() + px;
}
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include "Grid.h"
#include <vector>
#include <queue>
#include <unordered_map>
#include <functional>
struct Node {
int x, y;
float gCost = 0.0f;
float hCost = 0.0f;
float fCost() const { return gCost + hCost; }
bool operator>(const Node& other) const {
return fCost() > other.fCost();
}
};
class AStar {
public:
AStar(Grid& grid);
bool findPath(int startX, int startY, int endX, int endY);
void visualizeStep(int startX, int startY, int endX, int endY);
bool isRunning() const { return running_; }
bool isFound() const { return found_; }
void reset();
private:
float heuristic(int x1, int y1, int x2, int y2) const;
void reconstructPath(int endX, int endY);
Grid& grid_;
bool running_ = false;
bool found_ = false;
std::priority_queue<Node, std::vector<Node>, std::greater<Node>> openSet_;
std::unordered_map<int, float> gScore_;
std::unordered_map<int, std::pair<int, int>> cameFrom_;
int endX_, endY_;
};
+105
View File
@@ -0,0 +1,105 @@
#include "Grid.h"
#include <algorithm>
Grid::Grid(int width, int height) : width_(width), height_(height) {
cells_.resize(width * height);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
cells_[y * width + x].x = x;
cells_[y * width + x].y = y;
}
}
}
Cell& Grid::getCell(int x, int y) {
return cells_[y * width_ + x];
}
const Cell& Grid::getCell(int x, int y) const {
return cells_[y * width_ + x];
}
bool Grid::isValid(int x, int y) const {
return x >= 0 && x < width_ && y >= 0 && y < height_;
}
bool Grid::isWalkable(int x, int y) const {
return isValid(x, y) && getCell(x, y).walkable;
}
void Grid::setCellType(int x, int y, CellType type) {
if (!isValid(x, y)) return;
auto& cell = getCell(x, y);
cell.type = type;
switch (type) {
case CellType::Wall:
cell.walkable = false;
break;
case CellType::Start:
case CellType::End:
case CellType::Empty:
case CellType::Open:
case CellType::Closed:
case CellType::Path:
cell.walkable = true;
break;
}
}
void Grid::reset() {
for (auto& cell : cells_) {
cell.type = CellType::Empty;
cell.walkable = true;
}
}
void Grid::clearPath() {
for (auto& cell : cells_) {
if (cell.type == CellType::Open ||
cell.type == CellType::Closed ||
cell.type == CellType::Path) {
cell.type = CellType::Empty;
cell.walkable = true;
}
}
}
glm::vec2 Grid::getCellPosition(int x, int y) const {
return glm::vec2(
static_cast<float>(x) / static_cast<float>(width_) * 2.0f - 1.0f,
static_cast<float>(y) / static_cast<float>(height_) * 2.0f - 1.0f
);
}
glm::vec3 Grid::getCellColor(CellType type) const {
switch (type) {
case CellType::Empty: return glm::vec3(0.15f, 0.15f, 0.15f);
case CellType::Wall: return glm::vec3(0.8f, 0.8f, 0.8f);
case CellType::Start: return glm::vec3(0.0f, 0.8f, 0.0f);
case CellType::End: return glm::vec3(0.8f, 0.0f, 0.0f);
case CellType::Open: return glm::vec3(0.0f, 0.6f, 0.6f);
case CellType::Closed: return glm::vec3(0.6f, 0.0f, 0.6f);
case CellType::Path: return glm::vec3(1.0f, 0.8f, 0.0f);
default: return glm::vec3(0.15f, 0.15f, 0.15f);
}
}
std::vector<Cell*> Grid::getNeighbors(int x, int y) {
std::vector<Cell*> neighbors;
const int dx[] = {-1, 1, 0, 0, -1, -1, 1, 1};
const int dy[] = {0, 0, -1, 1, -1, 1, -1, 1};
for (int i = 0; i < 8; ++i) {
int nx = x + dx[i];
int ny = y + dy[i];
if (isWalkable(nx, ny)) {
neighbors.push_back(&getCell(nx, ny));
}
}
return neighbors;
}
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include <vector>
#include <glm/glm.hpp>
enum class CellType {
Empty = 0,
Wall = 1,
Start = 2,
End = 3,
Open = 4,
Closed = 5,
Path = 6
};
struct Cell {
int x, y;
CellType type = CellType::Empty;
bool walkable = true;
};
class Grid {
public:
Grid(int width, int height);
int getWidth() const { return width_; }
int getHeight() const { return height_; }
Cell& getCell(int x, int y);
const Cell& getCell(int x, int y) const;
bool isValid(int x, int y) const;
bool isWalkable(int x, int y) const;
void setCellType(int x, int y, CellType type);
void reset();
void clearPath();
glm::vec2 getCellPosition(int x, int y) const;
glm::vec3 getCellColor(CellType type) const;
std::vector<Cell*> getNeighbors(int x, int y);
private:
int width_, height_;
std::vector<Cell> cells_;
};
+148
View File
@@ -0,0 +1,148 @@
#include "Renderer.h"
#include <iostream>
Renderer::Renderer(int windowWidth, int windowHeight)
: windowWidth_(windowWidth), windowHeight_(windowHeight) {}
Renderer::~Renderer() {
glDeleteVertexArrays(1, &VAO_);
glDeleteBuffers(1, &VBO_);
glfwTerminate();
}
bool Renderer::initialize() {
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW" << std::endl;
return false;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
window_ = glfwCreateWindow(windowWidth_, windowHeight_, "A* Pathfinding", NULL, NULL);
if (!window_) {
std::cerr << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return false;
}
glfwMakeContextCurrent(window_);
glfwSwapInterval(1);
if (glewInit() != GLEW_OK) {
std::cerr << "Failed to initialize GLEW" << std::endl;
return false;
}
glViewport(0, 0, windowWidth_, windowHeight_);
glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
shader_ = std::make_unique<Shader>("shaders/vertex.glsl", "shaders/fragment.glsl");
setupQuad();
return true;
}
void Renderer::setupQuad() {
float vertices[] = {
// positions
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f
};
glGenVertexArrays(1, &VAO_);
glGenBuffers(1, &VBO_);
glBindVertexArray(VAO_);
glBindBuffer(GL_ARRAY_BUFFER, VBO_);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void Renderer::render(const Grid& grid) {
glClear(GL_COLOR_BUFFER_BIT);
shader_->use();
for (int y = 0; y < grid.getHeight(); ++y) {
for (int x = 0; x < grid.getWidth(); ++x) {
const auto& cell = grid.getCell(x, y);
glm::vec3 color = grid.getCellColor(cell.type);
drawCell(x, y, color, grid);
}
}
}
void Renderer::drawCell(int x, int y, const glm::vec3& color, const Grid& grid) {
float cellW = 2.0f / grid.getWidth();
float cellH = 2.0f / grid.getHeight();
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(
-1.0f + x * cellW + 0.005f,
-1.0f + y * cellH + 0.005f,
0.0f
));
model = glm::scale(model, glm::vec3(
cellW - 0.01f,
cellH - 0.01f,
1.0f
));
shader_->setMat4("model", model);
shader_->setVec3("color", color.r, color.g, color.b);
glBindVertexArray(VAO_);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glBindVertexArray(0);
}
void Renderer::processInput() {
if (glfwGetKey(window_, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window_, true);
}
}
bool Renderer::shouldClose() const {
return glfwWindowShouldClose(window_);
}
void Renderer::swapBuffers() {
glfwSwapBuffers(window_);
}
void Renderer::pollEvents() {
glfwPollEvents();
}
void Renderer::getMouseGridPos(int& gridX, int& gridY) const {
double mx, my;
glfwGetCursorPos(window_, &mx, &my);
// Convert to NDC
float ndcX = (2.0f * mx / windowWidth_) - 1.0f;
float ndcY = 1.0f - (2.0f * my / windowHeight_);
// Convert to grid coordinates
gridX = static_cast<int>((ndcX + 1.0f) / 2.0f * gridWidth_);
gridY = static_cast<int>((ndcY + 1.0f) / 2.0f * gridHeight_);
}
bool Renderer::isMousePressed(int button) const {
return glfwGetMouseButton(window_, button) == GLFW_PRESS;
}
bool Renderer::isKeyPressed(int key) const {
return glfwGetKey(window_, key) == GLFW_PRESS;
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "Grid.h"
#include "Shader.h"
#include <memory>
class Renderer {
public:
Renderer(int windowWidth, int windowHeight);
~Renderer();
bool initialize();
void render(const Grid& grid);
void processInput();
bool shouldClose() const;
void swapBuffers();
void pollEvents();
GLFWwindow* getWindow() const { return window_; }
// Mouse handling
void getMouseGridPos(int& gridX, int& gridY) const;
bool isMousePressed(int button) const;
bool isKeyPressed(int key) const;
private:
void setupQuad();
void drawCell(int x, int y, const glm::vec3& color, const Grid& grid);
int windowWidth_, windowHeight_;
int gridWidth_ = 40;
int gridHeight_ = 30;
GLFWwindow* window_ = nullptr;
std::unique_ptr<Shader> shader_;
GLuint VAO_ = 0, VBO_ = 0;
};
+87
View File
@@ -0,0 +1,87 @@
#pragma once
#include <GL/glew.h>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
class Shader {
public:
GLuint ID;
Shader(const char* vertexPath, const char* fragmentPath) {
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
vShaderFile.close();
fShaderFile.close();
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
} catch (std::ifstream::failure& e) {
std::cerr << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
GLuint vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
GLuint fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
glDeleteShader(vertex);
glDeleteShader(fragment);
}
void use() { glUseProgram(ID); }
void setVec3(const std::string& name, float x, float y, float z) const {
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}
void setMat4(const std::string& name, const glm::mat4& mat) const {
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
private:
void checkCompileErrors(GLuint shader, std::string type) {
GLint success;
GLchar infoLog[1024];
if (type != "PROGRAM") {
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
std::cerr << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << std::endl;
}
} else {
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
std::cerr << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << std::endl;
}
}
}
};
+165
View File
@@ -0,0 +1,165 @@
#include "Renderer.h"
#include "Grid.h"
#include "AStar.h"
#include <iostream>
#include <cstdlib>
#include <ctime>
const int GRID_W = 40;
const int GRID_H = 30;
int main() {
std::srand(static_cast<unsigned>(std::time(nullptr)));
Renderer renderer(800, 600);
if (!renderer.initialize()) {
return -1;
}
Grid grid(GRID_W, GRID_H);
AStar astar(grid);
// Default start and end positions
int startX = 5, startY = 5;
int endX = 34, endY = 24;
grid.setCellType(startX, startY, CellType::Start);
grid.setCellType(endX, endY, CellType::End);
// Generate random walls
for (int i = 0; i < 300; ++i) {
int wx = std::rand() % GRID_W;
int wy = std::rand() % GRID_H;
if ((wx != startX || wy != startY) && (wx != endX || wy != endY)) {
grid.setCellType(wx, wy, CellType::Wall);
}
}
bool pathFound = false;
bool placingWalls = false;
bool removingWalls = false;
bool settingStart = false;
bool settingEnd = false;
std::cout << "=== A* Pathfinding Controls ===" << std::endl;
std::cout << "Left Click : Place walls" << std::endl;
std::cout << "Right Click : Remove walls" << std::endl;
std::cout << "S + Click : Set start position" << std::endl;
std::cout << "E + Click : Set end position" << std::endl;
std::cout << "SPACE : Run A* algorithm" << std::endl;
std::cout << "R : Reset grid (keep walls)" << std::endl;
std::cout << "C : Clear all walls" << std::endl;
std::cout << "G : Generate new random walls" << std::endl;
std::cout << "ESC : Exit" << std::endl;
std::cout << "===============================" << std::endl;
while (!renderer.shouldClose()) {
renderer.processInput();
int mx, my;
renderer.getMouseGridPos(mx, my);
// Check modifier keys
settingStart = renderer.isKeyPressed(GLFW_KEY_S);
settingEnd = renderer.isKeyPressed(GLFW_KEY_E);
// Mouse input handling
if (grid.isValid(mx, my)) {
if (renderer.isMousePressed(GLFW_MOUSE_BUTTON_LEFT) && !settingStart && !settingEnd) {
if (grid.getCell(mx, my).type != CellType::Start &&
grid.getCell(mx, my).type != CellType::End) {
grid.setCellType(mx, my, CellType::Wall);
pathFound = false;
}
}
else if (renderer.isMousePressed(GLFW_MOUSE_BUTTON_RIGHT)) {
if (grid.getCell(mx, my).type != CellType::Start &&
grid.getCell(mx, my).type != CellType::End) {
grid.setCellType(mx, my, CellType::Empty);
pathFound = false;
}
}
else if (renderer.isMousePressed(GLFW_MOUSE_BUTTON_LEFT) && settingStart) {
if (grid.getCell(mx, my).type != CellType::End &&
grid.getCell(mx, my).type != CellType::Wall) {
grid.setCellType(startX, startY, CellType::Empty);
startX = mx;
startY = my;
grid.setCellType(startX, startY, CellType::Start);
pathFound = false;
}
}
else if (renderer.isMousePressed(GLFW_MOUSE_BUTTON_LEFT) && settingEnd) {
if (grid.getCell(mx, my).type != CellType::Start &&
grid.getCell(mx, my).type != CellType::Wall) {
grid.setCellType(endX, endY, CellType::Empty);
endX = mx;
endY = my;
grid.setCellType(endX, endY, CellType::End);
pathFound = false;
}
}
}
// Key actions (with simple debounce using static variable)
static bool spaceWasPressed = false;
static bool rWasPressed = false;
static bool cWasPressed = false;
static bool gWasPressed = false;
bool spacePressed = renderer.isKeyPressed(GLFW_KEY_SPACE);
bool rPressed = renderer.isKeyPressed(GLFW_KEY_R);
bool cPressed = renderer.isKeyPressed(GLFW_KEY_C);
bool gPressed = renderer.isKeyPressed(GLFW_KEY_G);
if (spacePressed && !spaceWasPressed) {
grid.clearPath();
pathFound = astar.findPath(startX, startY, endX, endY);
if (pathFound) {
std::cout << "Path found!" << std::endl;
} else {
std::cout << "No path found!" << std::endl;
}
}
if (rPressed && !rWasPressed) {
grid.clearPath();
pathFound = false;
std::cout << "Path cleared." << std::endl;
}
if (cPressed && !cWasPressed) {
grid.reset();
grid.setCellType(startX, startY, CellType::Start);
grid.setCellType(endX, endY, CellType::End);
pathFound = false;
std::cout << "Grid cleared." << std::endl;
}
if (gPressed && !gWasPressed) {
grid.reset();
grid.setCellType(startX, startY, CellType::Start);
grid.setCellType(endX, endY, CellType::End);
for (int i = 0; i < 300; ++i) {
int wx = std::rand() % GRID_W;
int wy = std::rand() % GRID_H;
if ((wx != startX || wy != startY) && (wx != endX || wy != endY)) {
grid.setCellType(wx, wy, CellType::Wall);
}
}
pathFound = false;
std::cout << "New random walls generated." << std::endl;
}
spaceWasPressed = spacePressed;
rWasPressed = rPressed;
cWasPressed = cPressed;
gWasPressed = gPressed;
renderer.render(grid);
renderer.swapBuffers();
renderer.pollEvents();
}
return 0;
}