pathfinding.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  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. ensure_working_buffers();
  21. m_obstaclesDirty.store(true, std::memory_order_release);
  22. m_fullUpdateRequired = true;
  23. m_workerThread = std::thread(&Pathfinding::worker_loop, this);
  24. }
  25. Pathfinding::~Pathfinding() {
  26. m_stopWorker.store(true, std::memory_order_release);
  27. m_requestCondition.notify_all();
  28. if (m_workerThread.joinable()) {
  29. m_workerThread.join();
  30. }
  31. }
  32. void Pathfinding::set_grid_offset(float offset_x, float offset_z) {
  33. m_gridOffsetX = offset_x;
  34. m_gridOffsetZ = offset_z;
  35. }
  36. void Pathfinding::set_obstacle(int x, int y, bool isObstacle) {
  37. if (x >= 0 && x < m_width && y >= 0 && y < m_height) {
  38. m_obstacles[y][x] = static_cast<std::uint8_t>(isObstacle);
  39. }
  40. }
  41. auto Pathfinding::is_walkable(int x, int y) const -> bool {
  42. if (x < 0 || x >= m_width || y < 0 || y >= m_height) {
  43. return false;
  44. }
  45. return m_obstacles[y][x] == 0;
  46. }
  47. auto Pathfinding::is_walkable_with_radius(int x, int y,
  48. float unit_radius) const -> bool {
  49. (void)unit_radius;
  50. return is_walkable(x, y);
  51. }
  52. void Pathfinding::mark_obstacles_dirty() {
  53. std::lock_guard<std::mutex> const lock(m_dirtyMutex);
  54. m_fullUpdateRequired = true;
  55. m_obstaclesDirty.store(true, std::memory_order_release);
  56. }
  57. void Pathfinding::mark_region_dirty(int min_x, int max_x, int min_z,
  58. int max_z) {
  59. min_x = std::max(0, min_x);
  60. max_x = std::min(m_width - 1, max_x);
  61. min_z = std::max(0, min_z);
  62. max_z = std::min(m_height - 1, max_z);
  63. if (min_x > max_x || min_z > max_z) {
  64. return;
  65. }
  66. std::lock_guard<std::mutex> const lock(m_dirtyMutex);
  67. m_dirtyRegions.emplace_back(min_x, max_x, min_z, max_z);
  68. m_obstaclesDirty.store(true, std::memory_order_release);
  69. }
  70. void Pathfinding::mark_building_region_dirty(float center_x, float center_z,
  71. float width, float depth) {
  72. float const padding = BuildingCollisionRegistry::get_grid_padding();
  73. float const half_width = width / 2.0F + padding;
  74. float const half_depth = depth / 2.0F + padding;
  75. int const min_x =
  76. static_cast<int>(std::floor(center_x - half_width - m_gridOffsetX));
  77. int const max_x =
  78. static_cast<int>(std::ceil(center_x + half_width - m_gridOffsetX));
  79. int const min_z =
  80. static_cast<int>(std::floor(center_z - half_depth - m_gridOffsetZ));
  81. int const max_z =
  82. static_cast<int>(std::ceil(center_z + half_depth - m_gridOffsetZ));
  83. mark_region_dirty(min_x, max_x, min_z, max_z);
  84. }
  85. void Pathfinding::process_dirty_regions() {
  86. std::vector<DirtyRegion> regions_to_process;
  87. {
  88. std::lock_guard<std::mutex> const lock(m_dirtyMutex);
  89. if (m_fullUpdateRequired) {
  90. m_dirtyRegions.clear();
  91. m_fullUpdateRequired = false;
  92. for (auto &row : m_obstacles) {
  93. std::fill(row.begin(), row.end(), static_cast<std::uint8_t>(0));
  94. }
  95. auto &terrain_service = Game::Map::TerrainService::instance();
  96. if (terrain_service.is_initialized()) {
  97. const Game::Map::TerrainHeightMap *height_map =
  98. terrain_service.get_height_map();
  99. const int terrain_width =
  100. (height_map != nullptr) ? height_map->getWidth() : 0;
  101. const int terrain_height =
  102. (height_map != nullptr) ? height_map->getHeight() : 0;
  103. for (int z = 0; z < m_height; ++z) {
  104. for (int x = 0; x < m_width; ++x) {
  105. bool blocked = false;
  106. if (x < terrain_width && z < terrain_height) {
  107. blocked = !terrain_service.is_walkable(x, z);
  108. } else {
  109. blocked = true;
  110. }
  111. if (blocked) {
  112. m_obstacles[z][x] = static_cast<std::uint8_t>(1);
  113. }
  114. }
  115. }
  116. }
  117. auto &registry = BuildingCollisionRegistry::instance();
  118. const auto &buildings = registry.get_all_buildings();
  119. for (const auto &building : buildings) {
  120. auto cells =
  121. Game::Systems::BuildingCollisionRegistry::get_occupied_grid_cells(
  122. building, m_gridCellSize);
  123. for (const auto &cell : cells) {
  124. int const grid_x =
  125. static_cast<int>(std::round(cell.first - m_gridOffsetX));
  126. int const grid_z =
  127. static_cast<int>(std::round(cell.second - m_gridOffsetZ));
  128. if (grid_x >= 0 && grid_x < m_width && grid_z >= 0 &&
  129. grid_z < m_height) {
  130. m_obstacles[grid_z][grid_x] = static_cast<std::uint8_t>(1);
  131. }
  132. }
  133. }
  134. return;
  135. }
  136. regions_to_process = std::move(m_dirtyRegions);
  137. m_dirtyRegions.clear();
  138. }
  139. if (regions_to_process.empty()) {
  140. return;
  141. }
  142. for (const auto &region : regions_to_process) {
  143. update_region(region.min_x, region.max_x, region.min_z, region.max_z);
  144. }
  145. }
  146. void Pathfinding::update_region(int min_x, int max_x, int min_z, int max_z) {
  147. auto &terrain_service = Game::Map::TerrainService::instance();
  148. const Game::Map::TerrainHeightMap *height_map = nullptr;
  149. int terrain_width = 0;
  150. int terrain_height = 0;
  151. if (terrain_service.is_initialized()) {
  152. height_map = terrain_service.get_height_map();
  153. terrain_width = (height_map != nullptr) ? height_map->getWidth() : 0;
  154. terrain_height = (height_map != nullptr) ? height_map->getHeight() : 0;
  155. }
  156. for (int z = min_z; z <= max_z; ++z) {
  157. for (int x = min_x; x <= max_x; ++x) {
  158. bool blocked = false;
  159. if (x >= 0 && x < terrain_width && z >= 0 && z < terrain_height) {
  160. blocked = !terrain_service.is_walkable(x, z);
  161. } else if (terrain_service.is_initialized()) {
  162. blocked = true;
  163. }
  164. m_obstacles[z][x] = static_cast<std::uint8_t>(blocked ? 1 : 0);
  165. }
  166. }
  167. auto &registry = BuildingCollisionRegistry::instance();
  168. const auto &buildings = registry.get_all_buildings();
  169. for (const auto &building : buildings) {
  170. auto cells =
  171. Game::Systems::BuildingCollisionRegistry::get_occupied_grid_cells(
  172. building, m_gridCellSize);
  173. for (const auto &cell : cells) {
  174. int const grid_x =
  175. static_cast<int>(std::round(cell.first - m_gridOffsetX));
  176. int const grid_z =
  177. static_cast<int>(std::round(cell.second - m_gridOffsetZ));
  178. if (grid_x >= min_x && grid_x <= max_x && grid_z >= min_z &&
  179. grid_z <= max_z && grid_x >= 0 && grid_x < m_width && grid_z >= 0 &&
  180. grid_z < m_height) {
  181. m_obstacles[grid_z][grid_x] = static_cast<std::uint8_t>(1);
  182. }
  183. }
  184. }
  185. }
  186. void Pathfinding::update_building_obstacles() {
  187. if (!m_obstaclesDirty.load(std::memory_order_acquire)) {
  188. return;
  189. }
  190. std::lock_guard<std::mutex> const lock(m_mutex);
  191. if (!m_obstaclesDirty.load(std::memory_order_acquire)) {
  192. return;
  193. }
  194. process_dirty_regions();
  195. m_obstaclesDirty.store(false, std::memory_order_release);
  196. }
  197. auto Pathfinding::find_path(const Point &start,
  198. const Point &end) -> std::vector<Point> {
  199. if (m_obstaclesDirty.load(std::memory_order_acquire)) {
  200. update_building_obstacles();
  201. }
  202. std::lock_guard<std::mutex> const lock(m_mutex);
  203. return find_path_internal(start, end);
  204. }
  205. auto Pathfinding::find_path(const Point &start, const Point &end,
  206. float unit_radius) -> std::vector<Point> {
  207. if (m_obstaclesDirty.load(std::memory_order_acquire)) {
  208. update_building_obstacles();
  209. }
  210. std::lock_guard<std::mutex> const lock(m_mutex);
  211. return find_path_internal(start, end, unit_radius);
  212. }
  213. auto Pathfinding::find_path_async(const Point &start, const Point &end)
  214. -> std::future<std::vector<Point>> {
  215. return std::async(std::launch::async,
  216. [this, start, end]() { return find_path(start, end); });
  217. }
  218. void Pathfinding::submit_path_request(std::uint64_t request_id,
  219. const Point &start, const Point &end) {
  220. {
  221. std::lock_guard<std::mutex> const lock(m_requestMutex);
  222. m_requestQueue.push({request_id, start, end, 0.0F});
  223. }
  224. m_requestCondition.notify_one();
  225. }
  226. void Pathfinding::submit_path_request(std::uint64_t request_id,
  227. const Point &start, const Point &end,
  228. float unit_radius) {
  229. {
  230. std::lock_guard<std::mutex> const lock(m_requestMutex);
  231. m_requestQueue.push({request_id, start, end, unit_radius});
  232. }
  233. m_requestCondition.notify_one();
  234. }
  235. auto Pathfinding::fetch_completed_paths()
  236. -> std::vector<Pathfinding::PathResult> {
  237. std::vector<PathResult> results;
  238. std::lock_guard<std::mutex> const lock(m_resultMutex);
  239. while (!m_resultQueue.empty()) {
  240. results.push_back(std::move(m_resultQueue.front()));
  241. m_resultQueue.pop();
  242. }
  243. return results;
  244. }
  245. auto Pathfinding::find_path_internal(const Point &start,
  246. const Point &end) -> std::vector<Point> {
  247. return find_path_internal(start, end, 0.0F);
  248. }
  249. auto Pathfinding::find_path_internal(const Point &start, const Point &end,
  250. float unit_radius) -> std::vector<Point> {
  251. ensure_working_buffers();
  252. auto const is_walkableFunc = [this, unit_radius](int x, int y) -> bool {
  253. if (unit_radius <= 0.5F) {
  254. return is_walkable(x, y);
  255. }
  256. return is_walkable_with_radius(x, y, unit_radius);
  257. };
  258. if (!is_walkableFunc(start.x, start.y) || !is_walkableFunc(end.x, end.y)) {
  259. return {};
  260. }
  261. const int start_idx = toIndex(start);
  262. const int end_idx = toIndex(end);
  263. if (start_idx == end_idx) {
  264. return {start};
  265. }
  266. const std::uint32_t generation = next_generation();
  267. m_openHeap.clear();
  268. set_g_cost(start_idx, generation, 0);
  269. set_parent(start_idx, generation, start_idx);
  270. push_open_node({start_idx, calculate_heuristic(start, end), 0});
  271. const int max_iterations = std::max(m_width * m_height, 1);
  272. int iterations = 0;
  273. int final_cost = -1;
  274. while (!m_openHeap.empty() && iterations < max_iterations) {
  275. ++iterations;
  276. QueueNode const current = pop_open_node();
  277. if (current.g_cost > get_g_cost(current.index, generation)) {
  278. continue;
  279. }
  280. if (is_closed(current.index, generation)) {
  281. continue;
  282. }
  283. set_closed(current.index, generation);
  284. if (current.index == end_idx) {
  285. final_cost = current.g_cost;
  286. break;
  287. }
  288. const Point current_point = toPoint(current.index);
  289. std::array<Point, 8> neighbors{};
  290. const std::size_t neighbor_count =
  291. collect_neighbors(current_point, neighbors);
  292. for (std::size_t i = 0; i < neighbor_count; ++i) {
  293. const Point &neighbor = neighbors[i];
  294. if (!is_walkableFunc(neighbor.x, neighbor.y)) {
  295. continue;
  296. }
  297. const int neighbor_idx = toIndex(neighbor);
  298. if (is_closed(neighbor_idx, generation)) {
  299. continue;
  300. }
  301. const int tentative_gcost = current.g_cost + 1;
  302. if (tentative_gcost >= get_g_cost(neighbor_idx, generation)) {
  303. continue;
  304. }
  305. set_g_cost(neighbor_idx, generation, tentative_gcost);
  306. set_parent(neighbor_idx, generation, current.index);
  307. const int h_cost = calculate_heuristic(neighbor, end);
  308. push_open_node({neighbor_idx, tentative_gcost + h_cost, tentative_gcost});
  309. }
  310. }
  311. if (final_cost < 0) {
  312. return {};
  313. }
  314. std::vector<Point> path;
  315. path.reserve(final_cost + 1);
  316. build_path(start_idx, end_idx, generation, final_cost + 1, path);
  317. return path;
  318. }
  319. auto Pathfinding::calculate_heuristic(const Point &a, const Point &b) -> int {
  320. return std::abs(a.x - b.x) + std::abs(a.y - b.y);
  321. }
  322. void Pathfinding::ensure_working_buffers() {
  323. const std::size_t total_cells =
  324. static_cast<std::size_t>(m_width) * static_cast<std::size_t>(m_height);
  325. if (m_closedGeneration.size() != total_cells) {
  326. m_closedGeneration.assign(total_cells, 0);
  327. m_gCostGeneration.assign(total_cells, 0);
  328. m_gCostValues.assign(total_cells, std::numeric_limits<int>::max());
  329. m_parentGeneration.assign(total_cells, 0);
  330. m_parentValues.assign(total_cells, -1);
  331. }
  332. const std::size_t min_open_capacity =
  333. std::max<std::size_t>(total_cells / 8, 64);
  334. if (m_openHeap.capacity() < min_open_capacity) {
  335. m_openHeap.reserve(min_open_capacity);
  336. }
  337. }
  338. auto Pathfinding::next_generation() -> std::uint32_t {
  339. auto next = ++m_generationCounter;
  340. if (next == 0) {
  341. reset_generations();
  342. next = ++m_generationCounter;
  343. }
  344. return next;
  345. }
  346. void Pathfinding::reset_generations() {
  347. std::fill(m_closedGeneration.begin(), m_closedGeneration.end(), 0);
  348. std::fill(m_gCostGeneration.begin(), m_gCostGeneration.end(), 0);
  349. std::fill(m_parentGeneration.begin(), m_parentGeneration.end(), 0);
  350. std::fill(m_gCostValues.begin(), m_gCostValues.end(),
  351. std::numeric_limits<int>::max());
  352. std::fill(m_parentValues.begin(), m_parentValues.end(), -1);
  353. m_generationCounter = 0;
  354. }
  355. auto Pathfinding::is_closed(int index, std::uint32_t generation) const -> bool {
  356. return index >= 0 &&
  357. static_cast<std::size_t>(index) < m_closedGeneration.size() &&
  358. m_closedGeneration[static_cast<std::size_t>(index)] == generation;
  359. }
  360. void Pathfinding::set_closed(int index, std::uint32_t generation) {
  361. if (index >= 0 &&
  362. static_cast<std::size_t>(index) < m_closedGeneration.size()) {
  363. m_closedGeneration[static_cast<std::size_t>(index)] = generation;
  364. }
  365. }
  366. auto Pathfinding::get_g_cost(int index, std::uint32_t generation) const -> int {
  367. if (index < 0 ||
  368. static_cast<std::size_t>(index) >= m_gCostGeneration.size()) {
  369. return std::numeric_limits<int>::max();
  370. }
  371. if (m_gCostGeneration[static_cast<std::size_t>(index)] == generation) {
  372. return m_gCostValues[static_cast<std::size_t>(index)];
  373. }
  374. return std::numeric_limits<int>::max();
  375. }
  376. void Pathfinding::set_g_cost(int index, std::uint32_t generation, int cost) {
  377. if (index >= 0 &&
  378. static_cast<std::size_t>(index) < m_gCostGeneration.size()) {
  379. const auto idx = static_cast<std::size_t>(index);
  380. m_gCostGeneration[idx] = generation;
  381. m_gCostValues[idx] = cost;
  382. }
  383. }
  384. auto Pathfinding::has_parent(int index,
  385. std::uint32_t generation) const -> bool {
  386. return index >= 0 &&
  387. static_cast<std::size_t>(index) < m_parentGeneration.size() &&
  388. m_parentGeneration[static_cast<std::size_t>(index)] == generation;
  389. }
  390. auto Pathfinding::get_parent(int index, std::uint32_t generation) const -> int {
  391. if (has_parent(index, generation)) {
  392. return m_parentValues[static_cast<std::size_t>(index)];
  393. }
  394. return -1;
  395. }
  396. void Pathfinding::set_parent(int index, std::uint32_t generation,
  397. int parentIndex) {
  398. if (index >= 0 &&
  399. static_cast<std::size_t>(index) < m_parentGeneration.size()) {
  400. const auto idx = static_cast<std::size_t>(index);
  401. m_parentGeneration[idx] = generation;
  402. m_parentValues[idx] = parentIndex;
  403. }
  404. }
  405. auto Pathfinding::collect_neighbors(
  406. const Point &point, std::array<Point, 8> &buffer) const -> std::size_t {
  407. std::size_t count = 0;
  408. for (int dx = -1; dx <= 1; ++dx) {
  409. for (int dy = -1; dy <= 1; ++dy) {
  410. if (dx == 0 && dy == 0) {
  411. continue;
  412. }
  413. const int x = point.x + dx;
  414. const int y = point.y + dy;
  415. if (x < 0 || x >= m_width || y < 0 || y >= m_height) {
  416. continue;
  417. }
  418. if (dx != 0 && dy != 0) {
  419. if (!is_walkable(point.x + dx, point.y) ||
  420. !is_walkable(point.x, point.y + dy)) {
  421. continue;
  422. }
  423. }
  424. buffer[count++] = Point{x, y};
  425. }
  426. }
  427. return count;
  428. }
  429. void Pathfinding::build_path(int start_index, int end_index,
  430. std::uint32_t generation, int expected_length,
  431. std::vector<Point> &out_path) const {
  432. out_path.clear();
  433. if (expected_length > 0) {
  434. out_path.reserve(static_cast<std::size_t>(expected_length));
  435. }
  436. int current = end_index;
  437. while (current >= 0) {
  438. out_path.push_back(toPoint(current));
  439. if (current == start_index) {
  440. std::reverse(out_path.begin(), out_path.end());
  441. return;
  442. }
  443. if (!has_parent(current, generation)) {
  444. out_path.clear();
  445. return;
  446. }
  447. const int parent = get_parent(current, generation);
  448. if (parent == current || parent < 0) {
  449. out_path.clear();
  450. return;
  451. }
  452. current = parent;
  453. }
  454. out_path.clear();
  455. }
  456. auto Pathfinding::heap_less(const QueueNode &lhs,
  457. const QueueNode &rhs) -> bool {
  458. if (lhs.f_cost != rhs.f_cost) {
  459. return lhs.f_cost < rhs.f_cost;
  460. }
  461. return lhs.g_cost < rhs.g_cost;
  462. }
  463. void Pathfinding::push_open_node(const QueueNode &node) {
  464. m_openHeap.push_back(node);
  465. std::size_t index = m_openHeap.size() - 1;
  466. while (index > 0) {
  467. std::size_t const parent = (index - 1) / 2;
  468. if (heap_less(m_openHeap[parent], m_openHeap[index])) {
  469. break;
  470. }
  471. std::swap(m_openHeap[parent], m_openHeap[index]);
  472. index = parent;
  473. }
  474. }
  475. auto Pathfinding::pop_open_node() -> Pathfinding::QueueNode {
  476. QueueNode top = m_openHeap.front();
  477. QueueNode const last = m_openHeap.back();
  478. m_openHeap.pop_back();
  479. if (!m_openHeap.empty()) {
  480. m_openHeap[0] = last;
  481. std::size_t index = 0;
  482. const std::size_t size = m_openHeap.size();
  483. while (true) {
  484. std::size_t const left = index * 2 + 1;
  485. std::size_t const right = left + 1;
  486. std::size_t smallest = index;
  487. if (left < size && !heap_less(m_openHeap[smallest], m_openHeap[left])) {
  488. smallest = left;
  489. }
  490. if (right < size && !heap_less(m_openHeap[smallest], m_openHeap[right])) {
  491. smallest = right;
  492. }
  493. if (smallest == index) {
  494. break;
  495. }
  496. std::swap(m_openHeap[index], m_openHeap[smallest]);
  497. index = smallest;
  498. }
  499. }
  500. return top;
  501. }
  502. void Pathfinding::worker_loop() {
  503. while (true) {
  504. PathRequest request;
  505. {
  506. std::unique_lock<std::mutex> lock(m_requestMutex);
  507. m_requestCondition.wait(lock, [this]() {
  508. return m_stopWorker.load(std::memory_order_acquire) ||
  509. !m_requestQueue.empty();
  510. });
  511. if (m_stopWorker.load(std::memory_order_acquire) &&
  512. m_requestQueue.empty()) {
  513. break;
  514. }
  515. request = m_requestQueue.front();
  516. m_requestQueue.pop();
  517. }
  518. auto path = (request.unit_radius > 0.0F)
  519. ? find_path(request.start, request.end, request.unit_radius)
  520. : find_path(request.start, request.end);
  521. {
  522. std::lock_guard<std::mutex> const lock(m_resultMutex);
  523. m_resultQueue.push({request.request_id, std::move(path)});
  524. }
  525. }
  526. }
  527. auto Pathfinding::find_nearest_walkable_point(const Point &point,
  528. int max_search_radius,
  529. const Pathfinding &pathfinder,
  530. float unit_radius) -> Point {
  531. auto const is_walkableFunc = [&pathfinder, unit_radius](int x,
  532. int y) -> bool {
  533. if (unit_radius <= 0.5F) {
  534. return pathfinder.is_walkable(x, y);
  535. }
  536. return pathfinder.is_walkable_with_radius(x, y, unit_radius);
  537. };
  538. if (is_walkableFunc(point.x, point.y)) {
  539. return point;
  540. }
  541. for (int radius = 1; radius <= max_search_radius; ++radius) {
  542. for (int dy = -radius; dy <= radius; ++dy) {
  543. for (int dx = -radius; dx <= radius; ++dx) {
  544. if (std::abs(dx) != radius && std::abs(dy) != radius) {
  545. continue;
  546. }
  547. int const check_x = point.x + dx;
  548. int const check_y = point.y + dy;
  549. if (is_walkableFunc(check_x, check_y)) {
  550. return {check_x, check_y};
  551. }
  552. }
  553. }
  554. }
  555. return point;
  556. }
  557. } // namespace Game::Systems