Codechange: [CMake] Hide errors when breakpad is not found (#13371)
[openttd-github.git] / src / pathfinder / water_regions.cpp
blob666a1d27343ac3bdf803738d2bfeba4f9119059e
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file water_regions.cpp Handles dividing the water in the map into square regions to assist pathfinding. */
10 #include "stdafx.h"
11 #include "map_func.h"
12 #include "water_regions.h"
13 #include "tilearea_type.h"
14 #include "track_func.h"
15 #include "transport_type.h"
16 #include "landscape.h"
17 #include "tunnelbridge_map.h"
18 #include "follow_track.hpp"
19 #include "ship.h"
20 #include "debug.h"
22 using TWaterRegionTraversabilityBits = uint16_t;
23 constexpr TWaterRegionPatchLabel FIRST_REGION_LABEL = 1;
25 static_assert(sizeof(TWaterRegionTraversabilityBits) * 8 == WATER_REGION_EDGE_LENGTH);
26 static_assert(sizeof(TWaterRegionPatchLabel) == sizeof(uint8_t)); // Important for the hash calculation.
28 static inline TrackBits GetWaterTracks(TileIndex tile) { return TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_WATER, 0)); }
29 static inline bool IsAqueductTile(TileIndex tile) { return IsBridgeTile(tile) && GetTunnelBridgeTransportType(tile) == TRANSPORT_WATER; }
31 static inline int GetWaterRegionX(TileIndex tile) { return TileX(tile) / WATER_REGION_EDGE_LENGTH; }
32 static inline int GetWaterRegionY(TileIndex tile) { return TileY(tile) / WATER_REGION_EDGE_LENGTH; }
34 static inline int GetWaterRegionMapSizeX() { return Map::SizeX() / WATER_REGION_EDGE_LENGTH; }
35 static inline int GetWaterRegionMapSizeY() { return Map::SizeY() / WATER_REGION_EDGE_LENGTH; }
37 static inline TWaterRegionIndex GetWaterRegionIndex(int region_x, int region_y) { return GetWaterRegionMapSizeX() * region_y + region_x; }
38 static inline TWaterRegionIndex GetWaterRegionIndex(TileIndex tile) { return GetWaterRegionIndex(GetWaterRegionX(tile), GetWaterRegionY(tile)); }
40 using TWaterRegionPatchLabelArray = std::array<TWaterRegionPatchLabel, WATER_REGION_NUMBER_OF_TILES>;
42 /**
43 * The data stored for each water region.
45 class WaterRegionData {
46 friend class WaterRegion;
48 std::array<TWaterRegionTraversabilityBits, DIAGDIR_END> edge_traversability_bits{};
49 std::unique_ptr<TWaterRegionPatchLabelArray> tile_patch_labels; // Tile patch labels, this may be nullptr in the following trivial cases: region is invalid, region is only land (0 patches), region is only water (1 patch)
50 bool has_cross_region_aqueducts = false;
51 TWaterRegionPatchLabel number_of_patches = 0; // 0 = no water, 1 = one single patch of water, etc...
54 /**
55 * Represents a square section of the map of a fixed size. Within this square individual unconnected patches of water are
56 * identified using a Connected Component Labeling (CCL) algorithm. Note that all information stored in this class applies
57 * only to tiles within the square section, there is no knowledge about the rest of the map. This makes it easy to invalidate
58 * and update a water region if any changes are made to it, such as construction or terraforming.
60 class WaterRegion
62 private:
63 WaterRegionData &data;
64 const OrthogonalTileArea tile_area;
66 /**
67 * Returns the local index of the tile within the region. The N corner represents 0,
68 * the x direction is positive in the SW direction, and Y is positive in the SE direction.
69 * @param tile Tile within the water region.
70 * @returns The local index.
72 inline int GetLocalIndex(TileIndex tile) const
74 assert(this->tile_area.Contains(tile));
75 return (TileX(tile) - TileX(this->tile_area.tile)) + WATER_REGION_EDGE_LENGTH * (TileY(tile) - TileY(this->tile_area.tile));
78 public:
79 WaterRegion(int region_x, int region_y, WaterRegionData &water_region_data)
80 : data(water_region_data)
81 , tile_area(TileXY(region_x * WATER_REGION_EDGE_LENGTH, region_y * WATER_REGION_EDGE_LENGTH), WATER_REGION_EDGE_LENGTH, WATER_REGION_EDGE_LENGTH)
84 OrthogonalTileIterator begin() const { return this->tile_area.begin(); }
85 OrthogonalTileIterator end() const { return this->tile_area.end(); }
87 /**
88 * Returns a set of bits indicating whether an edge tile on a particular side is traversable or not. These
89 * values can be used to determine whether a ship can enter/leave the region through a particular edge tile.
90 * @see GetLocalIndex() for a description of the coordinate system used.
91 * @param side Which side of the region we want to know the edge traversability of.
92 * @returns A value holding the edge traversability bits.
94 TWaterRegionTraversabilityBits GetEdgeTraversabilityBits(DiagDirection side) const { return this->data.edge_traversability_bits[side]; }
96 /**
97 * @returns The amount of individual water patches present within the water region. A value of
98 * 0 means there is no water present in the water region at all.
100 int NumberOfPatches() const { return static_cast<int>(this->data.number_of_patches); }
103 * @returns Whether the water region contains aqueducts that cross the region boundaries.
105 bool HasCrossRegionAqueducts() const { return this->data.has_cross_region_aqueducts; }
108 * Returns the patch label that was assigned to the tile.
109 * @param tile The tile of which we want to retrieve the label.
110 * @returns The label assigned to the tile.
112 TWaterRegionPatchLabel GetLabel(TileIndex tile) const
114 assert(this->tile_area.Contains(tile));
115 if (this->data.tile_patch_labels == nullptr) {
116 return this->NumberOfPatches() == 0 ? INVALID_WATER_REGION_PATCH : FIRST_REGION_LABEL;
118 return (*this->data.tile_patch_labels)[this->GetLocalIndex(tile)];
122 * Performs the connected component labeling and other data gathering.
123 * @see WaterRegion
125 void ForceUpdate()
127 Debug(map, 3, "Updating water region ({},{})", GetWaterRegionX(this->tile_area.tile), GetWaterRegionY(this->tile_area.tile));
128 this->data.has_cross_region_aqueducts = false;
130 /* Acquire a tile patch label array if this region does not already have one */
131 if (this->data.tile_patch_labels == nullptr) {
132 this->data.tile_patch_labels = std::make_unique<TWaterRegionPatchLabelArray>();
135 this->data.tile_patch_labels->fill(INVALID_WATER_REGION_PATCH);
136 this->data.edge_traversability_bits.fill(0);
138 TWaterRegionPatchLabel current_label = FIRST_REGION_LABEL;
139 TWaterRegionPatchLabel highest_assigned_label = INVALID_WATER_REGION_PATCH;
141 /* Perform connected component labeling. This uses a flooding algorithm that expands until no
142 * additional tiles can be added. Only tiles inside the water region are considered. */
143 for (const TileIndex start_tile : this->tile_area) {
144 static std::vector<TileIndex> tiles_to_check;
145 tiles_to_check.clear();
146 tiles_to_check.push_back(start_tile);
148 bool increase_label = false;
149 while (!tiles_to_check.empty()) {
150 const TileIndex tile = tiles_to_check.back();
151 tiles_to_check.pop_back();
153 const TrackdirBits valid_dirs = TrackBitsToTrackdirBits(GetWaterTracks(tile));
154 if (valid_dirs == TRACKDIR_BIT_NONE) continue;
156 TWaterRegionPatchLabel &tile_patch = (*this->data.tile_patch_labels)[this->GetLocalIndex(tile)];
157 if (tile_patch != INVALID_WATER_REGION_PATCH) continue;
159 tile_patch = current_label;
160 highest_assigned_label = current_label;
161 increase_label = true;
163 for (const Trackdir dir : SetTrackdirBitIterator(valid_dirs)) {
164 /* By using a TrackFollower we "play by the same rules" as the actual ship pathfinder */
165 CFollowTrackWater ft;
166 if (ft.Follow(tile, dir)) {
167 if (this->tile_area.Contains(ft.new_tile)) {
168 tiles_to_check.push_back(ft.new_tile);
169 } else if (!ft.is_bridge) {
170 assert(DistanceManhattan(ft.new_tile, tile) == 1);
171 const auto side = DiagdirBetweenTiles(tile, ft.new_tile);
172 const int local_x_or_y = DiagDirToAxis(side) == AXIS_X ? TileY(tile) - TileY(this->tile_area.tile) : TileX(tile) - TileX(this->tile_area.tile);
173 SetBit(this->data.edge_traversability_bits[side], local_x_or_y);
174 } else {
175 this->data.has_cross_region_aqueducts = true;
181 if (increase_label) current_label++;
184 this->data.number_of_patches = highest_assigned_label;
186 if (this->NumberOfPatches() == 0 || (this->NumberOfPatches() == 1 &&
187 std::all_of(this->data.tile_patch_labels->begin(), this->data.tile_patch_labels->end(), [](TWaterRegionPatchLabel label) { return label == FIRST_REGION_LABEL; }))) {
188 /* No need for patch storage: trivial cases */
189 this->data.tile_patch_labels.reset();
193 void PrintDebugInfo()
195 Debug(map, 9, "Water region {},{} labels and edge traversability = ...", GetWaterRegionX(this->tile_area.tile), GetWaterRegionY(this->tile_area.tile));
197 const size_t max_element_width = std::to_string(this->NumberOfPatches()).size();
199 std::string traversability = fmt::format("{:0{}b}", this->GetEdgeTraversabilityBits(DIAGDIR_NW), WATER_REGION_EDGE_LENGTH);
200 Debug(map, 9, " {:{}}", fmt::join(traversability, " "), max_element_width);
201 Debug(map, 9, " +{:->{}}+", "", WATER_REGION_EDGE_LENGTH * (max_element_width + 1) + 1);
203 for (int y = 0; y < WATER_REGION_EDGE_LENGTH; ++y) {
204 std::string line{};
205 for (int x = 0; x < WATER_REGION_EDGE_LENGTH; ++x) {
206 const auto label = this->GetLabel(TileAddXY(this->tile_area.tile, x, y));
207 const std::string label_str = label == INVALID_WATER_REGION_PATCH ? "." : std::to_string(label);
208 line = fmt::format("{:{}}", label_str, max_element_width) + " " + line;
210 Debug(map, 9, "{} | {}| {}", GB(this->GetEdgeTraversabilityBits(DIAGDIR_SW), y, 1), line, GB(this->GetEdgeTraversabilityBits(DIAGDIR_NE), y, 1));
213 Debug(map, 9, " +{:->{}}+", "", WATER_REGION_EDGE_LENGTH * (max_element_width + 1) + 1);
214 traversability = fmt::format("{:0{}b}", this->GetEdgeTraversabilityBits(DIAGDIR_SE), WATER_REGION_EDGE_LENGTH);
215 Debug(map, 9, " {:{}}", fmt::join(traversability, " "), max_element_width);
219 std::vector<WaterRegionData> _water_region_data;
220 std::vector<bool> _is_water_region_valid;
222 static TileIndex GetTileIndexFromLocalCoordinate(int region_x, int region_y, int local_x, int local_y)
224 assert(local_x >= 0 && local_x < WATER_REGION_EDGE_LENGTH);
225 assert(local_y >= 0 && local_y < WATER_REGION_EDGE_LENGTH);
226 return TileXY(WATER_REGION_EDGE_LENGTH * region_x + local_x, WATER_REGION_EDGE_LENGTH * region_y + local_y);
229 static TileIndex GetEdgeTileCoordinate(int region_x, int region_y, DiagDirection side, int x_or_y)
231 assert(x_or_y >= 0 && x_or_y < WATER_REGION_EDGE_LENGTH);
232 switch (side) {
233 case DIAGDIR_NE: return GetTileIndexFromLocalCoordinate(region_x, region_y, 0, x_or_y);
234 case DIAGDIR_SW: return GetTileIndexFromLocalCoordinate(region_x, region_y, WATER_REGION_EDGE_LENGTH - 1, x_or_y);
235 case DIAGDIR_NW: return GetTileIndexFromLocalCoordinate(region_x, region_y, x_or_y, 0);
236 case DIAGDIR_SE: return GetTileIndexFromLocalCoordinate(region_x, region_y, x_or_y, WATER_REGION_EDGE_LENGTH - 1);
237 default: NOT_REACHED();
241 static WaterRegion GetUpdatedWaterRegion(uint16_t region_x, uint16_t region_y)
243 const TWaterRegionIndex index = GetWaterRegionIndex(region_x, region_y);
244 WaterRegion water_region(region_x, region_y, _water_region_data[index]);
245 if (!_is_water_region_valid[index]) {
246 water_region.ForceUpdate();
247 _is_water_region_valid[index] = true;
249 return water_region;
252 static WaterRegion GetUpdatedWaterRegion(TileIndex tile)
254 return GetUpdatedWaterRegion(GetWaterRegionX(tile), GetWaterRegionY(tile));
258 * Returns the index of the water region.
259 * @param water_region The water region to return the index for.
261 static TWaterRegionIndex GetWaterRegionIndex(const WaterRegionDesc &water_region)
263 return GetWaterRegionIndex(water_region.x, water_region.y);
267 * Calculates a number that uniquely identifies the provided water region patch.
268 * @param water_region_patch The Water region to calculate the hash for.
270 int CalculateWaterRegionPatchHash(const WaterRegionPatchDesc &water_region_patch)
272 return water_region_patch.label | GetWaterRegionIndex(water_region_patch) << 8;
276 * Returns the center tile of a particular water region.
277 * @param water_region The water region to find the center tile for.
278 * @returns The center tile of the water region.
280 TileIndex GetWaterRegionCenterTile(const WaterRegionDesc &water_region)
282 return TileXY(water_region.x * WATER_REGION_EDGE_LENGTH + (WATER_REGION_EDGE_LENGTH / 2), water_region.y * WATER_REGION_EDGE_LENGTH + (WATER_REGION_EDGE_LENGTH / 2));
286 * Returns basic water region information for the provided tile.
287 * @param tile The tile for which the information will be calculated.
289 WaterRegionDesc GetWaterRegionInfo(TileIndex tile)
291 return WaterRegionDesc{ GetWaterRegionX(tile), GetWaterRegionY(tile) };
295 * Returns basic water region patch information for the provided tile.
296 * @param tile The tile for which the information will be calculated.
298 WaterRegionPatchDesc GetWaterRegionPatchInfo(TileIndex tile)
300 const WaterRegion region = GetUpdatedWaterRegion(tile);
301 return WaterRegionPatchDesc{ GetWaterRegionX(tile), GetWaterRegionY(tile), region.GetLabel(tile) };
305 * Marks the water region that tile is part of as invalid.
306 * @param tile Tile within the water region that we wish to invalidate.
308 void InvalidateWaterRegion(TileIndex tile)
310 if (!IsValidTile(tile)) return;
312 auto invalidate_region = [](TileIndex tile) {
313 const TWaterRegionIndex water_region_index = GetWaterRegionIndex(tile);
314 if (!_is_water_region_valid[water_region_index]) Debug(map, 3, "Invalidated water region ({},{})", GetWaterRegionX(tile), GetWaterRegionY(tile));
315 _is_water_region_valid[water_region_index] = false;
318 invalidate_region(tile);
320 /* When updating the water region we look into the first tile of adjacent water regions to determine edge
321 * traversability. This means that if we invalidate any region edge tiles we might also change the traversability
322 * of the adjacent region. This code ensures the adjacent regions also get invalidated in such a case. */
323 for (DiagDirection side = DIAGDIR_BEGIN; side < DIAGDIR_END; side++) {
324 const TileIndex adjacent_tile = AddTileIndexDiffCWrap(tile, TileIndexDiffCByDiagDir(side));
325 if (adjacent_tile == INVALID_TILE) continue;
326 if (GetWaterRegionIndex(adjacent_tile) != GetWaterRegionIndex(tile)) invalidate_region(adjacent_tile);
331 * Calls the provided callback function for all water region patches
332 * accessible from one particular side of the starting patch.
333 * @param water_region_patch Water patch within the water region to start searching from
334 * @param side Side of the water region to look for neigboring patches of water
335 * @param callback The function that will be called for each neighbor that is found
337 static inline void VisitAdjacentWaterRegionPatchNeighbors(const WaterRegionPatchDesc &water_region_patch, DiagDirection side, TVisitWaterRegionPatchCallBack &func)
339 if (water_region_patch.label == INVALID_WATER_REGION_PATCH) return;
341 const WaterRegion current_region = GetUpdatedWaterRegion(water_region_patch.x, water_region_patch.y);
343 const TileIndexDiffC offset = TileIndexDiffCByDiagDir(side);
344 const int nx = water_region_patch.x + offset.x;
345 const int ny = water_region_patch.y + offset.y;
347 if (nx < 0 || ny < 0 || nx >= GetWaterRegionMapSizeX() || ny >= GetWaterRegionMapSizeY()) return;
349 const WaterRegion neighboring_region = GetUpdatedWaterRegion(nx, ny);
350 const DiagDirection opposite_side = ReverseDiagDir(side);
352 /* Indicates via which local x or y coordinates (depends on the "side" parameter) we can cross over into the adjacent region. */
353 const TWaterRegionTraversabilityBits traversability_bits = current_region.GetEdgeTraversabilityBits(side)
354 & neighboring_region.GetEdgeTraversabilityBits(opposite_side);
355 if (traversability_bits == 0) return;
357 if (current_region.NumberOfPatches() == 1 && neighboring_region.NumberOfPatches() == 1) {
358 func(WaterRegionPatchDesc{ nx, ny, FIRST_REGION_LABEL }); // No further checks needed because we know there is just one patch for both adjacent regions
359 return;
362 /* Multiple water patches can be reached from the current patch. Check each edge tile individually. */
363 static std::vector<TWaterRegionPatchLabel> unique_labels; // static and vector-instead-of-map for performance reasons
364 unique_labels.clear();
365 for (int x_or_y = 0; x_or_y < WATER_REGION_EDGE_LENGTH; ++x_or_y) {
366 if (!HasBit(traversability_bits, x_or_y)) continue;
368 const TileIndex current_edge_tile = GetEdgeTileCoordinate(water_region_patch.x, water_region_patch.y, side, x_or_y);
369 const TWaterRegionPatchLabel current_label = current_region.GetLabel(current_edge_tile);
370 if (current_label != water_region_patch.label) continue;
372 const TileIndex neighbor_edge_tile = GetEdgeTileCoordinate(nx, ny, opposite_side, x_or_y);
373 const TWaterRegionPatchLabel neighbor_label = neighboring_region.GetLabel(neighbor_edge_tile);
374 assert(neighbor_label != INVALID_WATER_REGION_PATCH);
375 if (std::ranges::find(unique_labels, neighbor_label) == unique_labels.end()) unique_labels.push_back(neighbor_label);
377 for (TWaterRegionPatchLabel unique_label : unique_labels) func(WaterRegionPatchDesc{ nx, ny, unique_label });
381 * Calls the provided callback function on all accessible water region patches in
382 * each cardinal direction, plus any others that are reachable via aqueducts.
383 * @param water_region_patch Water patch within the water region to start searching from
384 * @param callback The function that will be called for each accessible water patch that is found
386 void VisitWaterRegionPatchNeighbors(const WaterRegionPatchDesc &water_region_patch, TVisitWaterRegionPatchCallBack &callback)
388 if (water_region_patch.label == INVALID_WATER_REGION_PATCH) return;
390 const WaterRegion current_region = GetUpdatedWaterRegion(water_region_patch.x, water_region_patch.y);
392 /* Visit adjacent water region patches in each cardinal direction */
393 for (DiagDirection side = DIAGDIR_BEGIN; side < DIAGDIR_END; side++) VisitAdjacentWaterRegionPatchNeighbors(water_region_patch, side, callback);
395 /* Visit neigboring water patches accessible via cross-region aqueducts */
396 if (current_region.HasCrossRegionAqueducts()) {
397 for (const TileIndex tile : current_region) {
398 if (GetWaterRegionPatchInfo(tile) == water_region_patch && IsAqueductTile(tile)) {
399 const TileIndex other_end_tile = GetOtherBridgeEnd(tile);
400 if (GetWaterRegionIndex(tile) != GetWaterRegionIndex(other_end_tile)) callback(GetWaterRegionPatchInfo(other_end_tile));
407 * Allocates the appropriate amount of water regions for the current map size
409 void AllocateWaterRegions()
411 const int number_of_regions = GetWaterRegionMapSizeX() * GetWaterRegionMapSizeY();
413 _water_region_data.clear();
414 _water_region_data.resize(number_of_regions);
416 _is_water_region_valid.clear();
417 _is_water_region_valid.resize(number_of_regions, false);
419 Debug(map, 2, "Allocating {} x {} water regions", GetWaterRegionMapSizeX(), GetWaterRegionMapSizeY());
420 assert(_is_water_region_valid.size() == _water_region_data.size());
423 void PrintWaterRegionDebugInfo(TileIndex tile)
425 GetUpdatedWaterRegion(tile).PrintDebugInfo();