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/>.
8 /** @file water_regions.cpp Handles dividing the water in the map into square regions to assist pathfinding. */
12 #include "water_regions.h"
14 #include "tilearea_type.h"
15 #include "track_func.h"
16 #include "transport_type.h"
17 #include "landscape.h"
18 #include "tunnelbridge_map.h"
19 #include "follow_track.hpp"
23 using TWaterRegionTraversabilityBits
= uint16_t;
24 constexpr TWaterRegionPatchLabel FIRST_REGION_LABEL
= 1;
26 static_assert(sizeof(TWaterRegionTraversabilityBits
) * 8 == WATER_REGION_EDGE_LENGTH
);
27 static_assert(sizeof(TWaterRegionPatchLabel
) == sizeof(uint8_t)); // Important for the hash calculation.
29 static inline TrackBits
GetWaterTracks(TileIndex tile
) { return TrackStatusToTrackBits(GetTileTrackStatus(tile
, TRANSPORT_WATER
, 0)); }
30 static inline bool IsAqueductTile(TileIndex tile
) { return IsBridgeTile(tile
) && GetTunnelBridgeTransportType(tile
) == TRANSPORT_WATER
; }
32 static inline int GetWaterRegionX(TileIndex tile
) { return TileX(tile
) / WATER_REGION_EDGE_LENGTH
; }
33 static inline int GetWaterRegionY(TileIndex tile
) { return TileY(tile
) / WATER_REGION_EDGE_LENGTH
; }
35 static inline int GetWaterRegionMapSizeX() { return Map::SizeX() / WATER_REGION_EDGE_LENGTH
; }
36 static inline int GetWaterRegionMapSizeY() { return Map::SizeY() / WATER_REGION_EDGE_LENGTH
; }
38 static inline TWaterRegionIndex
GetWaterRegionIndex(int region_x
, int region_y
) { return GetWaterRegionMapSizeX() * region_y
+ region_x
; }
39 static inline TWaterRegionIndex
GetWaterRegionIndex(TileIndex tile
) { return GetWaterRegionIndex(GetWaterRegionX(tile
), GetWaterRegionY(tile
)); }
41 using TWaterRegionPatchLabelArray
= std::array
<TWaterRegionPatchLabel
, WATER_REGION_NUMBER_OF_TILES
>;
44 * The data stored for each water region.
46 class WaterRegionData
{
47 friend class WaterRegion
;
49 std::array
<TWaterRegionTraversabilityBits
, DIAGDIR_END
> edge_traversability_bits
{};
50 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)
51 bool has_cross_region_aqueducts
= false;
52 TWaterRegionPatchLabel number_of_patches
= 0; // 0 = no water, 1 = one single patch of water, etc...
56 * Represents a square section of the map of a fixed size. Within this square individual unconnected patches of water are
57 * identified using a Connected Component Labeling (CCL) algorithm. Note that all information stored in this class applies
58 * only to tiles within the square section, there is no knowledge about the rest of the map. This makes it easy to invalidate
59 * and update a water region if any changes are made to it, such as construction or terraforming.
64 WaterRegionData
&data
;
65 const OrthogonalTileArea tile_area
;
68 * Returns the local index of the tile within the region. The N corner represents 0,
69 * the x direction is positive in the SW direction, and Y is positive in the SE direction.
70 * @param tile Tile within the water region.
71 * @returns The local index.
73 inline int GetLocalIndex(TileIndex tile
) const
75 assert(this->tile_area
.Contains(tile
));
76 return (TileX(tile
) - TileX(this->tile_area
.tile
)) + WATER_REGION_EDGE_LENGTH
* (TileY(tile
) - TileY(this->tile_area
.tile
));
80 WaterRegion(int region_x
, int region_y
, WaterRegionData
&water_region_data
)
81 : data(water_region_data
)
82 , tile_area(TileXY(region_x
* WATER_REGION_EDGE_LENGTH
, region_y
* WATER_REGION_EDGE_LENGTH
), WATER_REGION_EDGE_LENGTH
, WATER_REGION_EDGE_LENGTH
)
85 OrthogonalTileIterator
begin() const { return this->tile_area
.begin(); }
86 OrthogonalTileIterator
end() const { return this->tile_area
.end(); }
89 * Returns a set of bits indicating whether an edge tile on a particular side is traversable or not. These
90 * values can be used to determine whether a ship can enter/leave the region through a particular edge tile.
91 * @see GetLocalIndex() for a description of the coordinate system used.
92 * @param side Which side of the region we want to know the edge traversability of.
93 * @returns A value holding the edge traversability bits.
95 TWaterRegionTraversabilityBits
GetEdgeTraversabilityBits(DiagDirection side
) const { return this->data
.edge_traversability_bits
[side
]; }
98 * @returns The amount of individual water patches present within the water region. A value of
99 * 0 means there is no water present in the water region at all.
101 int NumberOfPatches() const { return static_cast<int>(this->data
.number_of_patches
); }
104 * @returns Whether the water region contains aqueducts that cross the region boundaries.
106 bool HasCrossRegionAqueducts() const { return this->data
.has_cross_region_aqueducts
; }
109 * Returns the patch label that was assigned to the tile.
110 * @param tile The tile of which we want to retrieve the label.
111 * @returns The label assigned to the tile.
113 TWaterRegionPatchLabel
GetLabel(TileIndex tile
) const
115 assert(this->tile_area
.Contains(tile
));
116 if (this->data
.tile_patch_labels
== nullptr) {
117 return this->NumberOfPatches() == 0 ? INVALID_WATER_REGION_PATCH
: FIRST_REGION_LABEL
;
119 return (*this->data
.tile_patch_labels
)[this->GetLocalIndex(tile
)];
123 * Performs the connected component labeling and other data gathering.
128 Debug(map
, 3, "Updating water region ({},{})", GetWaterRegionX(this->tile_area
.tile
), GetWaterRegionY(this->tile_area
.tile
));
129 this->data
.has_cross_region_aqueducts
= false;
131 /* Acquire a tile patch label array if this region does not already have one */
132 if (this->data
.tile_patch_labels
== nullptr) {
133 this->data
.tile_patch_labels
= std::make_unique
<TWaterRegionPatchLabelArray
>();
136 this->data
.tile_patch_labels
->fill(INVALID_WATER_REGION_PATCH
);
137 this->data
.edge_traversability_bits
.fill(0);
139 TWaterRegionPatchLabel current_label
= FIRST_REGION_LABEL
;
140 TWaterRegionPatchLabel highest_assigned_label
= INVALID_WATER_REGION_PATCH
;
142 /* Perform connected component labeling. This uses a flooding algorithm that expands until no
143 * additional tiles can be added. Only tiles inside the water region are considered. */
144 for (const TileIndex start_tile
: this->tile_area
) {
145 static std::vector
<TileIndex
> tiles_to_check
;
146 tiles_to_check
.clear();
147 tiles_to_check
.push_back(start_tile
);
149 bool increase_label
= false;
150 while (!tiles_to_check
.empty()) {
151 const TileIndex tile
= tiles_to_check
.back();
152 tiles_to_check
.pop_back();
154 const TrackdirBits valid_dirs
= TrackBitsToTrackdirBits(GetWaterTracks(tile
));
155 if (valid_dirs
== TRACKDIR_BIT_NONE
) continue;
157 TWaterRegionPatchLabel
&tile_patch
= (*this->data
.tile_patch_labels
)[this->GetLocalIndex(tile
)];
158 if (tile_patch
!= INVALID_WATER_REGION_PATCH
) continue;
160 tile_patch
= current_label
;
161 highest_assigned_label
= current_label
;
162 increase_label
= true;
164 for (const Trackdir dir
: SetTrackdirBitIterator(valid_dirs
)) {
165 /* By using a TrackFollower we "play by the same rules" as the actual ship pathfinder */
166 CFollowTrackWater ft
;
167 if (ft
.Follow(tile
, dir
)) {
168 if (this->tile_area
.Contains(ft
.new_tile
)) {
169 tiles_to_check
.push_back(ft
.new_tile
);
170 } else if (!ft
.is_bridge
) {
171 assert(DistanceManhattan(ft
.new_tile
, tile
) == 1);
172 const auto side
= DiagdirBetweenTiles(tile
, ft
.new_tile
);
173 const int local_x_or_y
= DiagDirToAxis(side
) == AXIS_X
? TileY(tile
) - TileY(this->tile_area
.tile
) : TileX(tile
) - TileX(this->tile_area
.tile
);
174 SetBit(this->data
.edge_traversability_bits
[side
], local_x_or_y
);
176 this->data
.has_cross_region_aqueducts
= true;
182 if (increase_label
) current_label
++;
185 this->data
.number_of_patches
= highest_assigned_label
;
187 if (this->NumberOfPatches() == 0 || (this->NumberOfPatches() == 1 &&
188 std::all_of(this->data
.tile_patch_labels
->begin(), this->data
.tile_patch_labels
->end(), [](TWaterRegionPatchLabel label
) { return label
== FIRST_REGION_LABEL
; }))) {
189 /* No need for patch storage: trivial cases */
190 this->data
.tile_patch_labels
.reset();
194 void PrintDebugInfo()
196 Debug(map
, 9, "Water region {},{} labels and edge traversability = ...", GetWaterRegionX(this->tile_area
.tile
), GetWaterRegionY(this->tile_area
.tile
));
198 const size_t max_element_width
= std::to_string(this->NumberOfPatches()).size();
200 std::string traversability
= fmt::format("{:0{}b}", this->GetEdgeTraversabilityBits(DIAGDIR_NW
), WATER_REGION_EDGE_LENGTH
);
201 Debug(map
, 9, " {:{}}", fmt::join(traversability
, " "), max_element_width
);
202 Debug(map
, 9, " +{:->{}}+", "", WATER_REGION_EDGE_LENGTH
* (max_element_width
+ 1) + 1);
204 for (int y
= 0; y
< WATER_REGION_EDGE_LENGTH
; ++y
) {
206 for (int x
= 0; x
< WATER_REGION_EDGE_LENGTH
; ++x
) {
207 const auto label
= this->GetLabel(TileAddXY(this->tile_area
.tile
, x
, y
));
208 const std::string label_str
= label
== INVALID_WATER_REGION_PATCH
? "." : std::to_string(label
);
209 line
= fmt::format("{:{}}", label_str
, max_element_width
) + " " + line
;
211 Debug(map
, 9, "{} | {}| {}", GB(this->GetEdgeTraversabilityBits(DIAGDIR_SW
), y
, 1), line
, GB(this->GetEdgeTraversabilityBits(DIAGDIR_NE
), y
, 1));
214 Debug(map
, 9, " +{:->{}}+", "", WATER_REGION_EDGE_LENGTH
* (max_element_width
+ 1) + 1);
215 traversability
= fmt::format("{:0{}b}", this->GetEdgeTraversabilityBits(DIAGDIR_SE
), WATER_REGION_EDGE_LENGTH
);
216 Debug(map
, 9, " {:{}}", fmt::join(traversability
, " "), max_element_width
);
220 std::vector
<WaterRegionData
> _water_region_data
;
221 std::vector
<bool> _is_water_region_valid
;
223 static TileIndex
GetTileIndexFromLocalCoordinate(int region_x
, int region_y
, int local_x
, int local_y
)
225 assert(local_x
>= 0 && local_x
< WATER_REGION_EDGE_LENGTH
);
226 assert(local_y
>= 0 && local_y
< WATER_REGION_EDGE_LENGTH
);
227 return TileXY(WATER_REGION_EDGE_LENGTH
* region_x
+ local_x
, WATER_REGION_EDGE_LENGTH
* region_y
+ local_y
);
230 static TileIndex
GetEdgeTileCoordinate(int region_x
, int region_y
, DiagDirection side
, int x_or_y
)
232 assert(x_or_y
>= 0 && x_or_y
< WATER_REGION_EDGE_LENGTH
);
234 case DIAGDIR_NE
: return GetTileIndexFromLocalCoordinate(region_x
, region_y
, 0, x_or_y
);
235 case DIAGDIR_SW
: return GetTileIndexFromLocalCoordinate(region_x
, region_y
, WATER_REGION_EDGE_LENGTH
- 1, x_or_y
);
236 case DIAGDIR_NW
: return GetTileIndexFromLocalCoordinate(region_x
, region_y
, x_or_y
, 0);
237 case DIAGDIR_SE
: return GetTileIndexFromLocalCoordinate(region_x
, region_y
, x_or_y
, WATER_REGION_EDGE_LENGTH
- 1);
238 default: NOT_REACHED();
242 static WaterRegion
GetUpdatedWaterRegion(uint16_t region_x
, uint16_t region_y
)
244 const TWaterRegionIndex index
= GetWaterRegionIndex(region_x
, region_y
);
245 WaterRegion
water_region(region_x
, region_y
, _water_region_data
[index
]);
246 if (!_is_water_region_valid
[index
]) {
247 water_region
.ForceUpdate();
248 _is_water_region_valid
[index
] = true;
253 static WaterRegion
GetUpdatedWaterRegion(TileIndex tile
)
255 return GetUpdatedWaterRegion(GetWaterRegionX(tile
), GetWaterRegionY(tile
));
259 * Returns the index of the water region.
260 * @param water_region The water region to return the index for.
262 static TWaterRegionIndex
GetWaterRegionIndex(const WaterRegionDesc
&water_region
)
264 return GetWaterRegionIndex(water_region
.x
, water_region
.y
);
268 * Calculates a number that uniquely identifies the provided water region patch.
269 * @param water_region_patch The Water region to calculate the hash for.
271 int CalculateWaterRegionPatchHash(const WaterRegionPatchDesc
&water_region_patch
)
273 return water_region_patch
.label
| GetWaterRegionIndex(water_region_patch
) << 8;
277 * Returns the center tile of a particular water region.
278 * @param water_region The water region to find the center tile for.
279 * @returns The center tile of the water region.
281 TileIndex
GetWaterRegionCenterTile(const WaterRegionDesc
&water_region
)
283 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));
287 * Returns basic water region information for the provided tile.
288 * @param tile The tile for which the information will be calculated.
290 WaterRegionDesc
GetWaterRegionInfo(TileIndex tile
)
292 return WaterRegionDesc
{ GetWaterRegionX(tile
), GetWaterRegionY(tile
) };
296 * Returns basic water region patch information for the provided tile.
297 * @param tile The tile for which the information will be calculated.
299 WaterRegionPatchDesc
GetWaterRegionPatchInfo(TileIndex tile
)
301 const WaterRegion region
= GetUpdatedWaterRegion(tile
);
302 return WaterRegionPatchDesc
{ GetWaterRegionX(tile
), GetWaterRegionY(tile
), region
.GetLabel(tile
) };
306 * Marks the water region that tile is part of as invalid.
307 * @param tile Tile within the water region that we wish to invalidate.
309 void InvalidateWaterRegion(TileIndex tile
)
311 if (!IsValidTile(tile
)) return;
313 auto invalidate_region
= [](TileIndex tile
) {
314 const TWaterRegionIndex water_region_index
= GetWaterRegionIndex(tile
);
315 if (!_is_water_region_valid
[water_region_index
]) Debug(map
, 3, "Invalidated water region ({},{})", GetWaterRegionX(tile
), GetWaterRegionY(tile
));
316 _is_water_region_valid
[water_region_index
] = false;
319 invalidate_region(tile
);
321 /* When updating the water region we look into the first tile of adjacent water regions to determine edge
322 * traversability. This means that if we invalidate any region edge tiles we might also change the traversability
323 * of the adjacent region. This code ensures the adjacent regions also get invalidated in such a case. */
324 for (DiagDirection side
= DIAGDIR_BEGIN
; side
< DIAGDIR_END
; side
++) {
325 const TileIndex adjacent_tile
= AddTileIndexDiffCWrap(tile
, TileIndexDiffCByDiagDir(side
));
326 if (adjacent_tile
== INVALID_TILE
) continue;
327 if (GetWaterRegionIndex(adjacent_tile
) != GetWaterRegionIndex(tile
)) invalidate_region(adjacent_tile
);
332 * Calls the provided callback function for all water region patches
333 * accessible from one particular side of the starting patch.
334 * @param water_region_patch Water patch within the water region to start searching from
335 * @param side Side of the water region to look for neigboring patches of water
336 * @param callback The function that will be called for each neighbor that is found
338 static inline void VisitAdjacentWaterRegionPatchNeighbors(const WaterRegionPatchDesc
&water_region_patch
, DiagDirection side
, TVisitWaterRegionPatchCallBack
&func
)
340 if (water_region_patch
.label
== INVALID_WATER_REGION_PATCH
) return;
342 const WaterRegion current_region
= GetUpdatedWaterRegion(water_region_patch
.x
, water_region_patch
.y
);
344 const TileIndexDiffC offset
= TileIndexDiffCByDiagDir(side
);
345 const int nx
= water_region_patch
.x
+ offset
.x
;
346 const int ny
= water_region_patch
.y
+ offset
.y
;
348 if (nx
< 0 || ny
< 0 || nx
>= GetWaterRegionMapSizeX() || ny
>= GetWaterRegionMapSizeY()) return;
350 const WaterRegion neighboring_region
= GetUpdatedWaterRegion(nx
, ny
);
351 const DiagDirection opposite_side
= ReverseDiagDir(side
);
353 /* Indicates via which local x or y coordinates (depends on the "side" parameter) we can cross over into the adjacent region. */
354 const TWaterRegionTraversabilityBits traversability_bits
= current_region
.GetEdgeTraversabilityBits(side
)
355 & neighboring_region
.GetEdgeTraversabilityBits(opposite_side
);
356 if (traversability_bits
== 0) return;
358 if (current_region
.NumberOfPatches() == 1 && neighboring_region
.NumberOfPatches() == 1) {
359 func(WaterRegionPatchDesc
{ nx
, ny
, FIRST_REGION_LABEL
}); // No further checks needed because we know there is just one patch for both adjacent regions
363 /* Multiple water patches can be reached from the current patch. Check each edge tile individually. */
364 static std::vector
<TWaterRegionPatchLabel
> unique_labels
; // static and vector-instead-of-map for performance reasons
365 unique_labels
.clear();
366 for (int x_or_y
= 0; x_or_y
< WATER_REGION_EDGE_LENGTH
; ++x_or_y
) {
367 if (!HasBit(traversability_bits
, x_or_y
)) continue;
369 const TileIndex current_edge_tile
= GetEdgeTileCoordinate(water_region_patch
.x
, water_region_patch
.y
, side
, x_or_y
);
370 const TWaterRegionPatchLabel current_label
= current_region
.GetLabel(current_edge_tile
);
371 if (current_label
!= water_region_patch
.label
) continue;
373 const TileIndex neighbor_edge_tile
= GetEdgeTileCoordinate(nx
, ny
, opposite_side
, x_or_y
);
374 const TWaterRegionPatchLabel neighbor_label
= neighboring_region
.GetLabel(neighbor_edge_tile
);
375 assert(neighbor_label
!= INVALID_WATER_REGION_PATCH
);
376 if (std::find(unique_labels
.begin(), unique_labels
.end(), neighbor_label
) == unique_labels
.end()) unique_labels
.push_back(neighbor_label
);
378 for (TWaterRegionPatchLabel unique_label
: unique_labels
) func(WaterRegionPatchDesc
{ nx
, ny
, unique_label
});
382 * Calls the provided callback function on all accessible water region patches in
383 * each cardinal direction, plus any others that are reachable via aqueducts.
384 * @param water_region_patch Water patch within the water region to start searching from
385 * @param callback The function that will be called for each accessible water patch that is found
387 void VisitWaterRegionPatchNeighbors(const WaterRegionPatchDesc
&water_region_patch
, TVisitWaterRegionPatchCallBack
&callback
)
389 if (water_region_patch
.label
== INVALID_WATER_REGION_PATCH
) return;
391 const WaterRegion current_region
= GetUpdatedWaterRegion(water_region_patch
.x
, water_region_patch
.y
);
393 /* Visit adjacent water region patches in each cardinal direction */
394 for (DiagDirection side
= DIAGDIR_BEGIN
; side
< DIAGDIR_END
; side
++) VisitAdjacentWaterRegionPatchNeighbors(water_region_patch
, side
, callback
);
396 /* Visit neigboring water patches accessible via cross-region aqueducts */
397 if (current_region
.HasCrossRegionAqueducts()) {
398 for (const TileIndex tile
: current_region
) {
399 if (GetWaterRegionPatchInfo(tile
) == water_region_patch
&& IsAqueductTile(tile
)) {
400 const TileIndex other_end_tile
= GetOtherBridgeEnd(tile
);
401 if (GetWaterRegionIndex(tile
) != GetWaterRegionIndex(other_end_tile
)) callback(GetWaterRegionPatchInfo(other_end_tile
));
408 * Allocates the appropriate amount of water regions for the current map size
410 void AllocateWaterRegions()
412 const int number_of_regions
= GetWaterRegionMapSizeX() * GetWaterRegionMapSizeY();
414 _water_region_data
.clear();
415 _water_region_data
.resize(number_of_regions
);
417 _is_water_region_valid
.clear();
418 _is_water_region_valid
.resize(number_of_regions
, false);
420 Debug(map
, 2, "Allocating {} x {} water regions", GetWaterRegionMapSizeX(), GetWaterRegionMapSizeY());
421 assert(_is_water_region_valid
.size() == _water_region_data
.size());
424 void PrintWaterRegionDebugInfo(TileIndex tile
)
426 GetUpdatedWaterRegion(tile
).PrintDebugInfo();