pathfinding.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. #include "pathfinding.h"
  2. #include "../map/terrain_service.h"
  3. #include "building_collision_registry.h"
  4. #include "map/terrain.h"
  5. #include <algorithm>
  6. #include <array>
  7. #include <atomic>
  8. #include <cmath>
  9. #include <cstddef>
  10. #include <cstdint>
  11. #include <future>
  12. #include <limits>
  13. #include <mutex>
  14. #include <utility>
  15. #include <vector>
  16. namespace Game::Systems {
  17. Pathfinding::Pathfinding(int width, int height)
  18. : m_width(width), m_height(height) {
  19. m_obstacles.resize(height, std::vector<std::uint8_t>(width, 0));
  20. ensureWorkingBuffers();
  21. m_obstaclesDirty.store(true, std::memory_order_release);
  22. m_workerThread = std::thread(&Pathfinding::workerLoop, this);
  23. }
  24. Pathfinding::~Pathfinding() {
  25. m_stopWorker.store(true, std::memory_order_release);
  26. m_requestCondition.notify_all();
  27. if (m_workerThread.joinable()) {
  28. m_workerThread.join();
  29. }
  30. }
  31. void Pathfinding::setGridOffset(float offset_x, float offset_z) {
  32. m_gridOffsetX = offset_x;
  33. m_gridOffsetZ = offset_z;
  34. }
  35. void Pathfinding::setObstacle(int x, int y, bool isObstacle) {
  36. if (x >= 0 && x < m_width && y >= 0 && y < m_height) {
  37. m_obstacles[y][x] = static_cast<std::uint8_t>(isObstacle);
  38. }
  39. }
  40. auto Pathfinding::isWalkable(int x, int y) const -> bool {
  41. if (x < 0 || x >= m_width || y < 0 || y >= m_height) {
  42. return false;
  43. }
  44. return m_obstacles[y][x] == 0;
  45. }
  46. void Pathfinding::markObstaclesDirty() {
  47. m_obstaclesDirty.store(true, std::memory_order_release);
  48. }
  49. void Pathfinding::updateBuildingObstacles() {
  50. if (!m_obstaclesDirty.load(std::memory_order_acquire)) {
  51. return;
  52. }
  53. std::lock_guard<std::mutex> const lock(m_mutex);
  54. if (!m_obstaclesDirty.load(std::memory_order_acquire)) {
  55. return;
  56. }
  57. for (auto &row : m_obstacles) {
  58. std::fill(row.begin(), row.end(), static_cast<std::uint8_t>(0));
  59. }
  60. auto &terrain_service = Game::Map::TerrainService::instance();
  61. if (terrain_service.isInitialized()) {
  62. const Game::Map::TerrainHeightMap *height_map =
  63. terrain_service.getHeightMap();
  64. const int terrain_width =
  65. (height_map != nullptr) ? height_map->getWidth() : 0;
  66. const int terrain_height =
  67. (height_map != nullptr) ? height_map->getHeight() : 0;
  68. for (int z = 0; z < m_height; ++z) {
  69. for (int x = 0; x < m_width; ++x) {
  70. bool blocked = false;
  71. if (x < terrain_width && z < terrain_height) {
  72. blocked = !terrain_service.isWalkable(x, z);
  73. } else {
  74. blocked = true;
  75. }
  76. if (blocked) {
  77. m_obstacles[z][x] = static_cast<std::uint8_t>(1);
  78. }
  79. }
  80. }
  81. }
  82. auto &registry = BuildingCollisionRegistry::instance();
  83. const auto &buildings = registry.getAllBuildings();
  84. for (const auto &building : buildings) {
  85. auto cells = Game::Systems::BuildingCollisionRegistry::getOccupiedGridCells(
  86. building, m_gridCellSize);
  87. for (const auto &cell : cells) {
  88. int const grid_x =
  89. static_cast<int>(std::round(cell.first - m_gridOffsetX));
  90. int const grid_z =
  91. static_cast<int>(std::round(cell.second - m_gridOffsetZ));
  92. if (grid_x >= 0 && grid_x < m_width && grid_z >= 0 && grid_z < m_height) {
  93. m_obstacles[grid_z][grid_x] = static_cast<std::uint8_t>(1);
  94. }
  95. }
  96. }
  97. m_obstaclesDirty.store(false, std::memory_order_release);
  98. }
  99. auto Pathfinding::findPath(const Point &start,
  100. const Point &end) -> std::vector<Point> {
  101. if (m_obstaclesDirty.load(std::memory_order_acquire)) {
  102. updateBuildingObstacles();
  103. }
  104. std::lock_guard<std::mutex> const lock(m_mutex);
  105. return findPathInternal(start, end);
  106. }
  107. auto Pathfinding::findPathAsync(const Point &start, const Point &end)
  108. -> std::future<std::vector<Point>> {
  109. return std::async(std::launch::async,
  110. [this, start, end]() { return findPath(start, end); });
  111. }
  112. void Pathfinding::submitPathRequest(std::uint64_t request_id,
  113. const Point &start, const Point &end) {
  114. {
  115. std::lock_guard<std::mutex> const lock(m_requestMutex);
  116. m_requestQueue.push({request_id, start, end});
  117. }
  118. m_requestCondition.notify_one();
  119. }
  120. auto Pathfinding::fetchCompletedPaths()
  121. -> std::vector<Pathfinding::PathResult> {
  122. std::vector<PathResult> results;
  123. std::lock_guard<std::mutex> const lock(m_resultMutex);
  124. while (!m_resultQueue.empty()) {
  125. results.push_back(std::move(m_resultQueue.front()));
  126. m_resultQueue.pop();
  127. }
  128. return results;
  129. }
  130. auto Pathfinding::findPathInternal(const Point &start,
  131. const Point &end) -> std::vector<Point> {
  132. ensureWorkingBuffers();
  133. if (!isWalkable(start.x, start.y) || !isWalkable(end.x, end.y)) {
  134. return {};
  135. }
  136. const int start_idx = toIndex(start);
  137. const int end_idx = toIndex(end);
  138. if (start_idx == end_idx) {
  139. return {start};
  140. }
  141. const std::uint32_t generation = nextGeneration();
  142. m_openHeap.clear();
  143. setGCost(start_idx, generation, 0);
  144. setParent(start_idx, generation, start_idx);
  145. pushOpenNode({start_idx, calculateHeuristic(start, end), 0});
  146. const int max_iterations = std::max(m_width * m_height, 1);
  147. int iterations = 0;
  148. int final_cost = -1;
  149. while (!m_openHeap.empty() && iterations < max_iterations) {
  150. ++iterations;
  151. QueueNode const current = popOpenNode();
  152. if (current.gCost > getGCost(current.index, generation)) {
  153. continue;
  154. }
  155. if (isClosed(current.index, generation)) {
  156. continue;
  157. }
  158. setClosed(current.index, generation);
  159. if (current.index == end_idx) {
  160. final_cost = current.gCost;
  161. break;
  162. }
  163. const Point current_point = toPoint(current.index);
  164. std::array<Point, 8> neighbors{};
  165. const std::size_t neighbor_count =
  166. collectNeighbors(current_point, neighbors);
  167. for (std::size_t i = 0; i < neighbor_count; ++i) {
  168. const Point &neighbor = neighbors[i];
  169. if (!isWalkable(neighbor.x, neighbor.y)) {
  170. continue;
  171. }
  172. const int neighbor_idx = toIndex(neighbor);
  173. if (isClosed(neighbor_idx, generation)) {
  174. continue;
  175. }
  176. const int tentative_gcost = current.gCost + 1;
  177. if (tentative_gcost >= getGCost(neighbor_idx, generation)) {
  178. continue;
  179. }
  180. setGCost(neighbor_idx, generation, tentative_gcost);
  181. setParent(neighbor_idx, generation, current.index);
  182. const int h_cost = calculateHeuristic(neighbor, end);
  183. pushOpenNode({neighbor_idx, tentative_gcost + h_cost, tentative_gcost});
  184. }
  185. }
  186. if (final_cost < 0) {
  187. return {};
  188. }
  189. std::vector<Point> path;
  190. path.reserve(final_cost + 1);
  191. buildPath(start_idx, end_idx, generation, final_cost + 1, path);
  192. return path;
  193. }
  194. auto Pathfinding::calculateHeuristic(const Point &a, const Point &b) -> int {
  195. return std::abs(a.x - b.x) + std::abs(a.y - b.y);
  196. }
  197. void Pathfinding::ensureWorkingBuffers() {
  198. const std::size_t total_cells =
  199. static_cast<std::size_t>(m_width) * static_cast<std::size_t>(m_height);
  200. if (m_closedGeneration.size() != total_cells) {
  201. m_closedGeneration.assign(total_cells, 0);
  202. m_gCostGeneration.assign(total_cells, 0);
  203. m_gCostValues.assign(total_cells, std::numeric_limits<int>::max());
  204. m_parentGeneration.assign(total_cells, 0);
  205. m_parentValues.assign(total_cells, -1);
  206. }
  207. const std::size_t min_open_capacity =
  208. std::max<std::size_t>(total_cells / 8, 64);
  209. if (m_openHeap.capacity() < min_open_capacity) {
  210. m_openHeap.reserve(min_open_capacity);
  211. }
  212. }
  213. auto Pathfinding::nextGeneration() -> std::uint32_t {
  214. auto next = ++m_generationCounter;
  215. if (next == 0) {
  216. resetGenerations();
  217. next = ++m_generationCounter;
  218. }
  219. return next;
  220. }
  221. void Pathfinding::resetGenerations() {
  222. std::fill(m_closedGeneration.begin(), m_closedGeneration.end(), 0);
  223. std::fill(m_gCostGeneration.begin(), m_gCostGeneration.end(), 0);
  224. std::fill(m_parentGeneration.begin(), m_parentGeneration.end(), 0);
  225. std::fill(m_gCostValues.begin(), m_gCostValues.end(),
  226. std::numeric_limits<int>::max());
  227. std::fill(m_parentValues.begin(), m_parentValues.end(), -1);
  228. m_generationCounter = 0;
  229. }
  230. auto Pathfinding::isClosed(int index, std::uint32_t generation) const -> bool {
  231. return index >= 0 &&
  232. static_cast<std::size_t>(index) < m_closedGeneration.size() &&
  233. m_closedGeneration[static_cast<std::size_t>(index)] == generation;
  234. }
  235. void Pathfinding::setClosed(int index, std::uint32_t generation) {
  236. if (index >= 0 &&
  237. static_cast<std::size_t>(index) < m_closedGeneration.size()) {
  238. m_closedGeneration[static_cast<std::size_t>(index)] = generation;
  239. }
  240. }
  241. auto Pathfinding::getGCost(int index, std::uint32_t generation) const -> int {
  242. if (index < 0 ||
  243. static_cast<std::size_t>(index) >= m_gCostGeneration.size()) {
  244. return std::numeric_limits<int>::max();
  245. }
  246. if (m_gCostGeneration[static_cast<std::size_t>(index)] == generation) {
  247. return m_gCostValues[static_cast<std::size_t>(index)];
  248. }
  249. return std::numeric_limits<int>::max();
  250. }
  251. void Pathfinding::setGCost(int index, std::uint32_t generation, int cost) {
  252. if (index >= 0 &&
  253. static_cast<std::size_t>(index) < m_gCostGeneration.size()) {
  254. const auto idx = static_cast<std::size_t>(index);
  255. m_gCostGeneration[idx] = generation;
  256. m_gCostValues[idx] = cost;
  257. }
  258. }
  259. auto Pathfinding::hasParent(int index, std::uint32_t generation) const -> bool {
  260. return index >= 0 &&
  261. static_cast<std::size_t>(index) < m_parentGeneration.size() &&
  262. m_parentGeneration[static_cast<std::size_t>(index)] == generation;
  263. }
  264. auto Pathfinding::getParent(int index, std::uint32_t generation) const -> int {
  265. if (hasParent(index, generation)) {
  266. return m_parentValues[static_cast<std::size_t>(index)];
  267. }
  268. return -1;
  269. }
  270. void Pathfinding::setParent(int index, std::uint32_t generation,
  271. int parentIndex) {
  272. if (index >= 0 &&
  273. static_cast<std::size_t>(index) < m_parentGeneration.size()) {
  274. const auto idx = static_cast<std::size_t>(index);
  275. m_parentGeneration[idx] = generation;
  276. m_parentValues[idx] = parentIndex;
  277. }
  278. }
  279. auto Pathfinding::collectNeighbors(
  280. const Point &point, std::array<Point, 8> &buffer) const -> std::size_t {
  281. std::size_t count = 0;
  282. for (int dx = -1; dx <= 1; ++dx) {
  283. for (int dy = -1; dy <= 1; ++dy) {
  284. if (dx == 0 && dy == 0) {
  285. continue;
  286. }
  287. const int x = point.x + dx;
  288. const int y = point.y + dy;
  289. if (x < 0 || x >= m_width || y < 0 || y >= m_height) {
  290. continue;
  291. }
  292. if (dx != 0 && dy != 0) {
  293. if (!isWalkable(point.x + dx, point.y) ||
  294. !isWalkable(point.x, point.y + dy)) {
  295. continue;
  296. }
  297. }
  298. buffer[count++] = Point{x, y};
  299. }
  300. }
  301. return count;
  302. }
  303. void Pathfinding::buildPath(int startIndex, int endIndex,
  304. std::uint32_t generation, int expectedLength,
  305. std::vector<Point> &outPath) const {
  306. outPath.clear();
  307. if (expectedLength > 0) {
  308. outPath.reserve(static_cast<std::size_t>(expectedLength));
  309. }
  310. int current = endIndex;
  311. while (current >= 0) {
  312. outPath.push_back(toPoint(current));
  313. if (current == startIndex) {
  314. std::reverse(outPath.begin(), outPath.end());
  315. return;
  316. }
  317. if (!hasParent(current, generation)) {
  318. outPath.clear();
  319. return;
  320. }
  321. const int parent = getParent(current, generation);
  322. if (parent == current || parent < 0) {
  323. outPath.clear();
  324. return;
  325. }
  326. current = parent;
  327. }
  328. outPath.clear();
  329. }
  330. auto Pathfinding::heapLess(const QueueNode &lhs, const QueueNode &rhs) -> bool {
  331. if (lhs.fCost != rhs.fCost) {
  332. return lhs.fCost < rhs.fCost;
  333. }
  334. return lhs.gCost < rhs.gCost;
  335. }
  336. void Pathfinding::pushOpenNode(const QueueNode &node) {
  337. m_openHeap.push_back(node);
  338. std::size_t index = m_openHeap.size() - 1;
  339. while (index > 0) {
  340. std::size_t const parent = (index - 1) / 2;
  341. if (heapLess(m_openHeap[parent], m_openHeap[index])) {
  342. break;
  343. }
  344. std::swap(m_openHeap[parent], m_openHeap[index]);
  345. index = parent;
  346. }
  347. }
  348. auto Pathfinding::popOpenNode() -> Pathfinding::QueueNode {
  349. QueueNode top = m_openHeap.front();
  350. QueueNode const last = m_openHeap.back();
  351. m_openHeap.pop_back();
  352. if (!m_openHeap.empty()) {
  353. m_openHeap[0] = last;
  354. std::size_t index = 0;
  355. const std::size_t size = m_openHeap.size();
  356. while (true) {
  357. std::size_t const left = index * 2 + 1;
  358. std::size_t const right = left + 1;
  359. std::size_t smallest = index;
  360. if (left < size && !heapLess(m_openHeap[smallest], m_openHeap[left])) {
  361. smallest = left;
  362. }
  363. if (right < size && !heapLess(m_openHeap[smallest], m_openHeap[right])) {
  364. smallest = right;
  365. }
  366. if (smallest == index) {
  367. break;
  368. }
  369. std::swap(m_openHeap[index], m_openHeap[smallest]);
  370. index = smallest;
  371. }
  372. }
  373. return top;
  374. }
  375. void Pathfinding::workerLoop() {
  376. while (true) {
  377. PathRequest request;
  378. {
  379. std::unique_lock<std::mutex> lock(m_requestMutex);
  380. m_requestCondition.wait(lock, [this]() {
  381. return m_stopWorker.load(std::memory_order_acquire) ||
  382. !m_requestQueue.empty();
  383. });
  384. if (m_stopWorker.load(std::memory_order_acquire) &&
  385. m_requestQueue.empty()) {
  386. break;
  387. }
  388. request = m_requestQueue.front();
  389. m_requestQueue.pop();
  390. }
  391. auto path = findPath(request.start, request.end);
  392. {
  393. std::lock_guard<std::mutex> const lock(m_resultMutex);
  394. m_resultQueue.push({request.request_id, std::move(path)});
  395. }
  396. }
  397. }
  398. } // namespace Game::Systems