qHexWalker 0.0.1
Hexagonal Grid Pathfinding & Maze Visualization on Interactive Maps
Loading...
Searching...
No Matches
h3MazeGenerator.cpp
Go to the documentation of this file.
1#include "h3MazeGenerator.h"
2
3#include <queue>
4
5H3MazeGenerator::H3MazeGenerator(QObject *parent) : QObject(parent), rng_(std::random_device{}()) {}
6
7std::array<H3Index, 6> H3MazeGenerator::getNeighbors(const H3Index cell) {
8 std::array<H3Index, 7> ring{}; // размер 7: центр + 6 соседей
9
10 if (gridDisk(cell, 1, ring.data()) != E_SUCCESS) {
11 return {};
12 }
13
14 neighbors_ = {};
15
16 int idx = 0;
17 for (const auto &neighbor : ring) {
18 if (neighbor != H3_NULL && neighbor != cell && !isPentagon(neighbor)) {
19 neighbors_[idx++] = neighbor;
20 }
21 }
22
23 // Если соседей меньше 6 (возле пентагона), остальные останутся 0
24 return neighbors_;
25}
26
27std::unordered_set<H3Index, H3MazeGenerator::H3IndexHash> H3MazeGenerator::getCellsInRadius(const H3Index center,
28 const int radius) {
29 std::unordered_set<H3Index, H3IndexHash> cells;
30
31 int64_t maxSize = 0;
32 H3Error err = maxGridDiskSize(radius, &maxSize);
33 if (err != E_SUCCESS) {
34 spdlog::warn(describeH3Error(err));
35 return cells;
36 }
37
38 std::vector<H3Index> disk(maxSize);
39
40 err = gridDisk(center, radius, disk.data());
41 if (err != E_SUCCESS) {
42 spdlog::warn(describeH3Error(err));
43 return cells;
44 }
45
46 // skip pentagons
47 cells.reserve(maxSize);
48 for (const auto &cell : disk) {
49 if (cell != H3_NULL && !isPentagon(cell) && isValidIndex(cell)) {
50 cells.insert(cell);
51 }
52 }
53
54 return cells;
55}
56
57// Создает сетку комнат с минимальным интервалом 2
58std::unordered_set<H3Index, H3MazeGenerator::H3IndexHash>
59H3MazeGenerator::createRoomGrid(const std::unordered_set<H3Index, H3IndexHash> &allCells) {
60
61 std::unordered_set<H3Index, H3IndexHash> rooms;
62 std::unordered_set<H3Index, H3IndexHash> occupied;
63
64 for (const auto &cell : allCells) {
65 if (occupied.contains(cell)) {
66 continue;
67 }
68
69 // Добавляем комнату
70 rooms.insert(cell);
71
72 // Резервируем соседей на расстоянии 1 (будущие потенциальные стены)
73 for (auto neighbors = getNeighbors(cell); const auto &neighbor : neighbors) {
74 occupied.insert(neighbor);
75 }
76 occupied.insert(cell);
77 }
78
79 return rooms;
80}
81
82// Находит комнаты-соседи на расстоянии 2
83std::vector<H3Index> H3MazeGenerator::getRoomNeighbors(const H3Index room,
84 const std::unordered_set<H3Index, H3IndexHash> &rooms) {
85
86 std::vector<H3Index> roomNeighbors;
87
88 // int64_t maxKRingSize = 0;
89 // maxGridDiskSize(kRingSize, &maxKRingSize) == 19,
90 // Thats why, ring size is 19.
91 std::array<H3Index, 19> ring = {};
92 // Получаем соседей на расстоянии 2
93 constexpr int kRingSize = 2;
94 if (E_SUCCESS != gridDisk(room, kRingSize, ring.data())) {
95 return roomNeighbors;
96 }
97 for (const auto &candidate : ring) {
98 if (candidate == H3_NULL || candidate == room) {
99 continue;
100 }
101
102 // Проверяем, что это комната и находится на расстоянии ровно 2
103 if (rooms.contains(candidate)) {
104 int64_t distance = 0;
105 if (gridDistance(room, candidate, &distance) == E_SUCCESS && distance == kRingSize) {
106 roomNeighbors.emplace_back(candidate);
107 }
108 }
109 }
110
111 return roomNeighbors;
112}
113
114// Находит стену между двумя комнатами на расстоянии 2
115std::optional<H3Index> H3MazeGenerator::findWallBetween(const H3Index room1, const H3Index room2) {
116 const auto neighbors1 = getNeighbors(room1);
117 const auto neighbors2 = getNeighbors(room2);
118
119 // Ищем общего соседа - это и будет стена между комнатами
120 for (const auto &n1 : neighbors1) {
121 if (std::ranges::any_of(neighbors2, [&](const auto &n2) { return n1 == n2; })) {
122 return n1;
123 }
124 }
125
126 return std::nullopt;
127}
128
129// Генерирует лабиринт методом Randomized Prim's
130std::unordered_set<H3Index, H3MazeGenerator::H3IndexHash>
131H3MazeGenerator::generateMazePrim(const std::unordered_set<H3Index, H3IndexHash> &rooms) {
132
133 if (rooms.empty()) {
134 return {};
135 }
136
137 // Проходы - изначально пустое множество, будем добавлять комнаты и проходы между ними
138 std::unordered_set<H3Index, H3IndexHash> passages;
139 std::unordered_set<H3Index, H3IndexHash> visitedRooms;
140
141 // Список стен: пара (стена, непосещенная комната за ней)
142 std::vector<std::pair<H3Index, H3Index>> wallList;
143
144 // Выбираем случайную стартовую комнату
145 const auto roomsVec = std::vector(rooms.begin(), rooms.end());
146 std::uniform_int_distribution<size_t> startDist(0, roomsVec.size() - 1);
147 const H3Index startRoom = roomsVec[startDist(rng_)];
148
149 // Отмечаем стартовую комнату как посещенную и добавляем в проходы
150 visitedRooms.insert(startRoom);
151 passages.insert(startRoom);
152
153 // Добавляем все стены стартовой комнаты
154 for (const auto neighborRooms = getRoomNeighbors(startRoom, rooms); const auto &neighborRoom : neighborRooms) {
155 if (!visitedRooms.contains(neighborRoom)) {
156 if (auto wall = findWallBetween(startRoom, neighborRoom); wall.has_value()) {
157 wallList.emplace_back(wall.value(), neighborRoom);
158 }
159 }
160 }
161
162 // Основной цикл алгоритма Prim
163 while (!wallList.empty()) {
164 // Выбираем случайную стену из списка
165 std::uniform_int_distribution<size_t> wallDist(0, wallList.size() - 1);
166 const size_t wallIdx = wallDist(rng_);
167 auto [wall, nextRoom] = wallList[wallIdx];
168
169 // Удаляем стену из списка
170 wallList.erase(wallList.begin() + static_cast<long>(wallIdx));
171
172 // Если комната за стеной уже посещена, пропускаем
173 if (visitedRooms.contains(nextRoom)) {
174 continue;
175 }
176
177 // Превращаем стену в проход
178 passages.insert(wall);
179 passages.insert(nextRoom);
180 visitedRooms.insert(nextRoom);
181
182 // Добавляем новые стены из новой комнаты
183 for (auto newNeighborRooms = getRoomNeighbors(nextRoom, rooms);
184 const auto &newNeighborRoom : newNeighborRooms) {
185 if (!visitedRooms.contains(newNeighborRoom)) {
186 if (auto newWall = findWallBetween(nextRoom, newNeighborRoom); newWall.has_value()) {
187 wallList.emplace_back(newWall.value(), newNeighborRoom);
188 }
189 }
190 }
191 }
192
193 spdlog::info("Prim's algorithm: visited {} rooms out of {}", visitedRooms.size(), rooms.size());
194
195 return passages;
196}
197
198// Находит самую удаленную комнату от стартовой через BFS
199H3Index H3MazeGenerator::findFarthestRoom(const H3Index start,
200 const std::unordered_set<H3Index, H3IndexHash> &passages) {
201
202 std::unordered_map<H3Index, int> distances;
203 std::queue<H3Index> queue;
204
205 queue.push(start);
206 distances[start] = 0;
207
208 H3Index farthest = start;
209 int maxDist = 0;
210
211 while (!queue.empty()) {
212 H3Index current = queue.front();
213 queue.pop();
214
215 for (auto neighbors = getNeighbors(current); const auto &neighbor : neighbors) {
216 if (passages.contains(neighbor) && !distances.contains(neighbor)) {
217 distances[neighbor] = distances[current] + 1;
218 queue.push(neighbor);
219
220 if (distances[neighbor] > maxDist) {
221 maxDist = distances[neighbor];
222 farthest = neighbor;
223 }
224 }
225 }
226 }
227
228 spdlog::info("Farthest room is at distance {}", maxDist);
229 return farthest;
230}
231
232// Проверяет, находится ли ячейка на границе области
233bool H3MazeGenerator::isOnBorder(const H3Index cell, const H3Index center, const int radius) {
234 int64_t distance = 0;
235 if (gridDistance(center, cell, &distance) != E_SUCCESS) {
236 return false;
237 }
238
239 // Ячейка на границе, если она находится на максимальном расстоянии от центра
240 return distance >= radius - 1;
241}
242
243// Новый метод с информацией о входе и выходе
245 spdlog::info("Generating H3 maze with entrances, center cell, radius={}", radius);
246
247 // Получаем все соты в радиусе
248 const auto allCells = getCellsInRadius(centerCell, radius);
249 if (allCells.empty()) {
250 spdlog::error("No cells in radius");
251 return {std::unordered_set<H3Index>(), H3_NULL, H3_NULL};
252 }
253
254 spdlog::info("Total cells in area: {}", allCells.size());
255
256 // Шаг 1: Создаем сетку комнат с интервалом 2
257 const auto rooms = createRoomGrid(allCells);
258 spdlog::info("Created {} rooms for maze", rooms.size());
259
260 if (rooms.empty()) {
261 return {std::unordered_set<H3Index>(), H3_NULL, H3_NULL};
262 }
263
264 // Шаг 2: Генерируем лабиринт методом Prim's
265 const auto passages = generateMazePrim(rooms);
266 spdlog::info("Generated {} passages", passages.size());
267
268 // Шаг 3: Находим вход (случайная комната на границе)
269 std::vector<H3Index> borderRooms;
270 for (const auto &room : rooms) {
271 if (passages.contains(room) && isOnBorder(room, centerCell, radius)) {
272 borderRooms.emplace_back(room);
273 }
274 }
275
276 H3Index entrance = H3_NULL;
277 if (!borderRooms.empty()) {
278 std::uniform_int_distribution<size_t> borderDist(0, borderRooms.size() - 1);
279 entrance = borderRooms[borderDist(rng_)];
280 } else if (!passages.empty()) {
281 entrance = *passages.begin();
282 }
283
284 // Шаг 4: Находим выход (самая удаленная комната от входа)
285 H3Index exit = findFarthestRoom(entrance, passages);
286
287 // Шаг 5: Создаем стены (все соты минус проходы)
288 std::unordered_set<H3Index> walls;
289 for (const auto &cell : allCells) {
290 if (!passages.contains(cell)) {
291 walls.insert(cell);
292 }
293 }
294
295 double wallPercent = walls.size() * 100.0 / allCells.size();
296 spdlog::info("Maze generated: {} walls ({:.1f}%), {} passages ({:.1f}%)", walls.size(), wallPercent,
297 passages.size(), 100.0 - wallPercent);
298 spdlog::info("Entrance: {}, Exit: {}", entrance, exit);
299
300 emit mazeGenerated(walls);
301 return {walls, entrance, exit};
302}
303
304// Обратная совместимость - старый метод без входа/выхода
305std::unordered_set<H3Index> H3MazeGenerator::generateMaze(const H3Index centerCell, const int radius) {
306 auto result = generateMazeWithEntrances(centerCell, radius);
307 return result.walls;
308}
std::array< H3Index, 6 > getNeighbors(H3Index cell)
Gets the 6 neighbors of a hexagonal cell.
static std::vector< H3Index > getRoomNeighbors(H3Index room, const std::unordered_set< H3Index, H3IndexHash > &rooms)
Gets rooms that are neighbors at distance 2.
MazeResult generateMazeWithEntrances(H3Index centerCell, int radius)
Generates a maze with entrance and exit points.
std::unordered_set< H3Index, H3IndexHash > generateMazePrim(const std::unordered_set< H3Index, H3IndexHash > &rooms)
Generates maze passages using randomized Prim's algorithm.
std::unordered_set< H3Index, H3IndexHash > createRoomGrid(const std::unordered_set< H3Index, H3IndexHash > &allCells)
Creates a sparse room grid with minimum 2-cell spacing.
std::unordered_set< H3Index > generateMaze(H3Index centerCell, int radius)
Generates a maze centered at the given cell.
std::array< H3Index, 6 > neighbors_
Temporary storage for neighbor cells.
void mazeGenerated(const std::unordered_set< H3Index > &walls)
Emitted when maze generation is complete.
std::optional< H3Index > findWallBetween(H3Index room1, H3Index room2)
Finds the wall cell between two rooms.
H3MazeGenerator(QObject *parent=nullptr)
Constructs an H3MazeGenerator.
static bool isOnBorder(H3Index cell, H3Index center, int radius)
Checks if a cell is on the border of the maze area.
static std::unordered_set< H3Index, H3IndexHash > getCellsInRadius(H3Index center, int radius)
Gets all cells within a radius of the center.
std::mt19937 rng_
Random number generator for maze randomization.
H3Index findFarthestRoom(H3Index start, const std::unordered_set< H3Index, H3IndexHash > &passages)
Finds the room farthest from the start using BFS.
Result structure containing generated maze data.
Procedural maze generation on H3 hexagonal grids using Prim's algorithm.