2024-01-08 19:29:05 +00:00
/*
* This file is part of OpenTTD .
* 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.
* 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 .
* 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/>.
*/
/** @file water_regions.cpp Handles dividing the water in the map into square regions to assist pathfinding. */
# include "stdafx.h"
# include "map_func.h"
# include "water_regions.h"
# include "map_func.h"
# include "tilearea_type.h"
# include "track_func.h"
# include "transport_type.h"
# include "landscape.h"
# include "tunnelbridge_map.h"
# include "follow_track.hpp"
# include "ship.h"
2024-01-09 20:55:27 +00:00
# include <array>
2024-01-10 19:19:28 +00:00
# include <vector>
2024-01-09 20:55:27 +00:00
2024-01-08 19:29:05 +00:00
using TWaterRegionTraversabilityBits = uint16_t ;
constexpr TWaterRegionPatchLabel FIRST_REGION_LABEL = 1 ;
constexpr TWaterRegionPatchLabel INVALID_WATER_REGION_PATCH = 0 ;
static_assert ( sizeof ( TWaterRegionTraversabilityBits ) * 8 = = WATER_REGION_EDGE_LENGTH ) ;
2024-01-27 14:06:14 +00:00
static_assert ( sizeof ( TWaterRegionPatchLabel ) = = sizeof ( byte ) ) ; // Important for the hash calculation.
2024-01-08 19:29:05 +00:00
static inline TrackBits GetWaterTracks ( TileIndex tile ) { return TrackStatusToTrackBits ( GetTileTrackStatus ( tile , TRANSPORT_WATER , 0 ) ) ; }
static inline bool IsAqueductTile ( TileIndex tile ) { return IsBridgeTile ( tile ) & & GetTunnelBridgeTransportType ( tile ) = = TRANSPORT_WATER ; }
2024-01-09 20:55:27 +00:00
static inline uint32_t GetWaterRegionX ( TileIndex tile ) { return TileX ( tile ) / WATER_REGION_EDGE_LENGTH ; }
static inline uint32_t GetWaterRegionY ( TileIndex tile ) { return TileY ( tile ) / WATER_REGION_EDGE_LENGTH ; }
2024-01-08 19:29:05 +00:00
2024-01-09 20:55:27 +00:00
static inline uint32_t GetWaterRegionMapSizeX ( ) { return MapSizeX ( ) / WATER_REGION_EDGE_LENGTH ; }
static inline uint32_t GetWaterRegionMapSizeY ( ) { return MapSizeY ( ) / WATER_REGION_EDGE_LENGTH ; }
2024-01-08 19:29:05 +00:00
2024-01-09 23:31:50 +00:00
static inline uint32_t GetWaterRegionYShift ( ) { return MapLogX ( ) - WATER_REGION_EDGE_LENGTH_LOG ; }
static inline TWaterRegionIndex GetWaterRegionIndex ( uint32_t region_x , uint32_t region_y ) { return ( region_y < < GetWaterRegionYShift ( ) ) + region_x ; }
2024-01-08 19:29:05 +00:00
static inline TWaterRegionIndex GetWaterRegionIndex ( TileIndex tile ) { return GetWaterRegionIndex ( GetWaterRegionX ( tile ) , GetWaterRegionY ( tile ) ) ; }
2024-01-09 22:48:04 +00:00
struct WaterRegionTileIterator {
uint32_t x ;
uint32_t y ;
inline operator TileIndex ( ) const
{
return TileXY ( this - > x , this - > y ) ;
}
inline TileIndex operator * ( ) const
{
return TileXY ( this - > x , this - > y ) ;
}
WaterRegionTileIterator & operator + + ( )
{
this - > x + + ;
if ( ( this - > x & WATER_REGION_EDGE_MASK ) = = 0 ) {
/* reached end of row */
this - > x - = WATER_REGION_EDGE_LENGTH ;
this - > y + + ;
}
return * this ;
}
} ;
2024-01-10 01:47:24 +00:00
using TWaterRegionPatchLabelArray = std : : array < TWaterRegionPatchLabel , WATER_REGION_NUMBER_OF_TILES > ;
2024-01-08 19:29:05 +00:00
/**
* Represents a square section of the map of a fixed size . Within this square individual unconnected patches of water are
* identified using a Connected Component Labeling ( CCL ) algorithm . Note that all information stored in this class applies
* only to tiles within the square section , there is no knowledge about the rest of the map . This makes it easy to invalidate
* and update a water region if any changes are made to it , such as construction or terraforming .
*/
class WaterRegion
{
2024-01-10 01:27:16 +00:00
friend class WaterRegionReference ;
2024-01-08 19:29:05 +00:00
std : : array < TWaterRegionTraversabilityBits , DIAGDIR_END > edge_traversability_bits { } ;
2024-01-10 01:47:24 +00:00
bool initialized = false ;
2024-01-08 19:29:05 +00:00
bool has_cross_region_aqueducts = false ;
TWaterRegionPatchLabel number_of_patches = 0 ; // 0 = no water, 1 = one single patch of water, etc...
2024-01-10 01:47:24 +00:00
std : : unique_ptr < TWaterRegionPatchLabelArray > tile_patch_labels ;
2024-01-10 01:27:16 +00:00
} ;
2024-01-10 01:47:24 +00:00
static std : : unique_ptr < TWaterRegionPatchLabelArray > _spare_labels ;
2024-01-10 01:27:16 +00:00
class WaterRegionReference {
const uint32_t tile_x ;
const uint32_t tile_y ;
WaterRegion & wr ;
2024-01-08 19:29:05 +00:00
2024-01-09 22:48:04 +00:00
inline bool ContainsTile ( TileIndex tile ) const
{
const uint32_t x = TileX ( tile ) ;
const uint32_t y = TileY ( tile ) ;
return x > = this - > tile_x & & x < this - > tile_x + WATER_REGION_EDGE_LENGTH
& & y > = this - > tile_y & & y < this - > tile_y + WATER_REGION_EDGE_LENGTH ;
}
2024-01-08 19:29:05 +00:00
/**
* Returns the local index of the tile within the region . The N corner represents 0 ,
* the x direction is positive in the SW direction , and Y is positive in the SE direction .
* @ param tile Tile within the water region .
* @ returns The local index .
*/
inline int GetLocalIndex ( TileIndex tile ) const
{
2024-01-09 22:48:04 +00:00
assert ( this - > ContainsTile ( tile ) ) ;
return ( TileX ( tile ) - this - > tile_x ) + WATER_REGION_EDGE_LENGTH * ( TileY ( tile ) - this - > tile_y ) ;
2024-01-08 19:29:05 +00:00
}
2024-01-10 01:47:24 +00:00
inline bool HasNonMatchingPatchLabel ( TWaterRegionPatchLabel expected_label ) const
{
for ( TWaterRegionPatchLabel label : * this - > wr . tile_patch_labels ) {
if ( label ! = expected_label ) return true ;
}
return false ;
}
2024-01-08 19:29:05 +00:00
public :
2024-01-10 01:27:16 +00:00
WaterRegionReference ( uint32_t region_x , uint32_t region_y , WaterRegion & wr )
: tile_x ( region_x * WATER_REGION_EDGE_LENGTH ) , tile_y ( region_y * WATER_REGION_EDGE_LENGTH ) , wr ( wr )
2024-01-08 19:29:05 +00:00
{ }
2024-01-09 22:48:04 +00:00
WaterRegionTileIterator begin ( ) const { return { this - > tile_x , this - > tile_y } ; }
WaterRegionTileIterator end ( ) const { return { this - > tile_x , this - > tile_y + WATER_REGION_EDGE_LENGTH } ; }
2024-01-08 19:29:05 +00:00
2024-01-10 01:27:16 +00:00
bool IsInitialized ( ) const { return this - > wr . initialized ; }
2024-01-08 19:29:05 +00:00
2024-01-10 01:27:16 +00:00
void Invalidate ( ) { this - > wr . initialized = false ; }
2024-01-08 19:29:05 +00:00
/**
* Returns a set of bits indicating whether an edge tile on a particular side is traversable or not . These
* values can be used to determine whether a ship can enter / leave the region through a particular edge tile .
* @ see GetLocalIndex ( ) for a description of the coordinate system used .
* @ param side Which side of the region we want to know the edge traversability of .
* @ returns A value holding the edge traversability bits .
*/
2024-01-10 01:27:16 +00:00
TWaterRegionTraversabilityBits GetEdgeTraversabilityBits ( DiagDirection side ) const { return this - > wr . edge_traversability_bits [ side ] ; }
2024-01-08 19:29:05 +00:00
/**
* @ returns The amount of individual water patches present within the water region . A value of
* 0 means there is no water present in the water region at all .
*/
2024-01-10 01:27:16 +00:00
int NumberOfPatches ( ) const { return this - > wr . number_of_patches ; }
2024-01-08 19:29:05 +00:00
/**
* @ returns Whether the water region contains aqueducts that cross the region boundaries .
*/
2024-01-10 01:27:16 +00:00
bool HasCrossRegionAqueducts ( ) const { return this - > wr . has_cross_region_aqueducts ; }
2024-01-08 19:29:05 +00:00
/**
* Returns the patch label that was assigned to the tile .
* @ param tile The tile of which we want to retrieve the label .
* @ returns The label assigned to the tile .
*/
TWaterRegionPatchLabel GetLabel ( TileIndex tile ) const
{
2024-01-09 22:48:04 +00:00
assert ( this - > ContainsTile ( tile ) ) ;
2024-01-10 01:47:24 +00:00
if ( this - > wr . tile_patch_labels = = nullptr ) {
return this - > NumberOfPatches ( ) = = 0 ? INVALID_WATER_REGION_PATCH : 1 ;
}
return ( * this - > wr . tile_patch_labels ) [ this - > GetLocalIndex ( tile ) ] ;
2024-01-08 19:29:05 +00:00
}
/**
* Performs the connected component labeling and other data gathering .
* @ see WaterRegion
*/
void ForceUpdate ( )
{
2024-01-10 01:27:16 +00:00
this - > wr . has_cross_region_aqueducts = false ;
2024-01-08 19:29:05 +00:00
2024-01-10 01:47:24 +00:00
if ( this - > wr . tile_patch_labels = = nullptr ) {
if ( _spare_labels ! = nullptr ) {
this - > wr . tile_patch_labels = std : : move ( _spare_labels ) ;
} else {
this - > wr . tile_patch_labels = std : : make_unique < TWaterRegionPatchLabelArray > ( ) ;
}
}
this - > wr . tile_patch_labels - > fill ( INVALID_WATER_REGION_PATCH ) ;
2024-01-08 19:29:05 +00:00
TWaterRegionPatchLabel current_label = 1 ;
TWaterRegionPatchLabel highest_assigned_label = 0 ;
/* Perform connected component labeling. This uses a flooding algorithm that expands until no
* additional tiles can be added . Only tiles inside the water region are considered . */
2024-01-09 22:48:04 +00:00
for ( const TileIndex start_tile : * this ) {
2024-01-08 19:29:05 +00:00
static std : : vector < TileIndex > tiles_to_check ;
tiles_to_check . clear ( ) ;
tiles_to_check . push_back ( start_tile ) ;
2024-01-10 01:27:16 +00:00
if ( ! this - > wr . has_cross_region_aqueducts & & IsAqueductTile ( start_tile ) ) {
2024-01-09 22:48:04 +00:00
const TileIndex other_aqueduct_end = GetOtherBridgeEnd ( start_tile ) ;
if ( ! this - > ContainsTile ( other_aqueduct_end ) ) {
2024-01-10 01:27:16 +00:00
this - > wr . has_cross_region_aqueducts = true ;
2024-01-09 22:48:04 +00:00
}
}
2024-01-08 19:29:05 +00:00
bool increase_label = false ;
while ( ! tiles_to_check . empty ( ) ) {
const TileIndex tile = tiles_to_check . back ( ) ;
tiles_to_check . pop_back ( ) ;
const TrackdirBits valid_dirs = TrackBitsToTrackdirBits ( GetWaterTracks ( tile ) ) ;
if ( valid_dirs = = TRACKDIR_BIT_NONE ) continue ;
2024-01-10 01:47:24 +00:00
TWaterRegionPatchLabel & tile_patch = ( * this - > wr . tile_patch_labels ) [ GetLocalIndex ( tile ) ] ;
if ( tile_patch ! = INVALID_WATER_REGION_PATCH ) continue ;
2024-01-08 19:29:05 +00:00
2024-01-10 01:47:24 +00:00
tile_patch = current_label ;
2024-01-08 19:29:05 +00:00
highest_assigned_label = current_label ;
increase_label = true ;
for ( const Trackdir dir : SetTrackdirBitIterator ( valid_dirs ) ) {
/* By using a TrackFollower we "play by the same rules" as the actual ship pathfinder */
CFollowTrackWater ft ;
2024-01-09 22:48:04 +00:00
if ( ft . Follow ( tile , dir ) & & this - > ContainsTile ( ft . m_new_tile ) ) tiles_to_check . push_back ( ft . m_new_tile ) ;
2024-01-08 19:29:05 +00:00
}
}
if ( increase_label ) current_label + + ;
}
2024-01-10 01:27:16 +00:00
this - > wr . number_of_patches = highest_assigned_label ;
this - > wr . initialized = true ;
2024-01-08 19:29:05 +00:00
/* Calculate the traversability (whether the tile can be entered / exited) for all edges. Note that
* we always follow the same X and Y scanning direction , this is important for comparisons later on ! */
2024-01-10 01:27:16 +00:00
this - > wr . edge_traversability_bits . fill ( 0 ) ;
2024-01-09 22:48:04 +00:00
const uint32_t top_x = this - > tile_x ;
const uint32_t top_y = this - > tile_y ;
2024-01-09 20:55:27 +00:00
for ( uint32_t i = 0 ; i < WATER_REGION_EDGE_LENGTH ; + + i ) {
2024-01-10 01:27:16 +00:00
if ( GetWaterTracks ( TileXY ( top_x + i , top_y ) ) & TRACK_BIT_3WAY_NW ) SetBit ( this - > wr . edge_traversability_bits [ DIAGDIR_NW ] , i ) ; // NW edge
if ( GetWaterTracks ( TileXY ( top_x + i , top_y + WATER_REGION_EDGE_LENGTH - 1 ) ) & TRACK_BIT_3WAY_SE ) SetBit ( this - > wr . edge_traversability_bits [ DIAGDIR_SE ] , i ) ; // SE edge
if ( GetWaterTracks ( TileXY ( top_x , top_y + i ) ) & TRACK_BIT_3WAY_NE ) SetBit ( this - > wr . edge_traversability_bits [ DIAGDIR_NE ] , i ) ; // NE edge
if ( GetWaterTracks ( TileXY ( top_x + WATER_REGION_EDGE_LENGTH - 1 , top_y + i ) ) & TRACK_BIT_3WAY_SW ) SetBit ( this - > wr . edge_traversability_bits [ DIAGDIR_SW ] , i ) ; // SW edge
2024-01-08 19:29:05 +00:00
}
2024-01-10 01:47:24 +00:00
if ( this - > wr . number_of_patches = = 0 | | ( this - > wr . number_of_patches = = 1 & & ! this - > HasNonMatchingPatchLabel ( 1 ) ) ) {
/* No need for patch storage: trivial cases */
_spare_labels = std : : move ( this - > wr . tile_patch_labels ) ;
}
2024-01-08 19:29:05 +00:00
}
/**
* Updates the patch labels and other data , but only if the region is not yet initialized .
*/
inline void UpdateIfNotInitialized ( )
{
2024-01-10 01:27:16 +00:00
if ( ! this - > wr . initialized ) this - > ForceUpdate ( ) ;
2024-01-08 19:29:05 +00:00
}
2024-01-10 00:51:44 +00:00
2024-01-10 01:47:24 +00:00
inline bool HasPatchStorage ( ) const
2024-01-10 00:51:44 +00:00
{
2024-01-10 01:47:24 +00:00
return this - > wr . tile_patch_labels ! = nullptr ;
2024-01-10 00:51:44 +00:00
}
2024-01-10 17:40:51 +00:00
TWaterRegionPatchLabelArray CopyPatchLabelArray ( ) const
{
TWaterRegionPatchLabelArray out ;
if ( this - > HasPatchStorage ( ) ) {
out = * this - > wr . tile_patch_labels ;
} else {
out . fill ( this - > NumberOfPatches ( ) = = 0 ? INVALID_WATER_REGION_PATCH : 1 ) ;
}
return out ;
}
2024-01-08 19:29:05 +00:00
} ;
2024-01-10 19:19:28 +00:00
std : : unique_ptr < WaterRegion [ ] > _water_regions ;
2024-01-08 19:29:05 +00:00
2024-01-09 20:55:27 +00:00
TileIndex GetTileIndexFromLocalCoordinate ( uint32_t region_x , uint32_t region_y , uint32_t local_x , uint32_t local_y )
2024-01-08 19:29:05 +00:00
{
2024-01-09 20:55:27 +00:00
assert ( local_x < WATER_REGION_EDGE_LENGTH ) ;
assert ( local_y < WATER_REGION_EDGE_LENGTH ) ;
2024-01-08 19:29:05 +00:00
return TileXY ( WATER_REGION_EDGE_LENGTH * region_x + local_x , WATER_REGION_EDGE_LENGTH * region_y + local_y ) ;
}
2024-01-09 20:55:27 +00:00
TileIndex GetEdgeTileCoordinate ( uint32_t region_x , uint32_t region_y , DiagDirection side , uint32_t x_or_y )
2024-01-08 19:29:05 +00:00
{
2024-01-09 20:55:27 +00:00
assert ( x_or_y < WATER_REGION_EDGE_LENGTH ) ;
2024-01-08 19:29:05 +00:00
switch ( side ) {
case DIAGDIR_NE : return GetTileIndexFromLocalCoordinate ( region_x , region_y , 0 , x_or_y ) ;
case DIAGDIR_SW : return GetTileIndexFromLocalCoordinate ( region_x , region_y , WATER_REGION_EDGE_LENGTH - 1 , x_or_y ) ;
case DIAGDIR_NW : return GetTileIndexFromLocalCoordinate ( region_x , region_y , x_or_y , 0 ) ;
case DIAGDIR_SE : return GetTileIndexFromLocalCoordinate ( region_x , region_y , x_or_y , WATER_REGION_EDGE_LENGTH - 1 ) ;
default : NOT_REACHED ( ) ;
}
}
2024-01-10 01:27:16 +00:00
inline WaterRegionReference GetWaterRegionRef ( uint32_t region_x , uint32_t region_y )
2024-01-08 19:29:05 +00:00
{
2024-01-10 01:27:16 +00:00
return WaterRegionReference ( region_x , region_y , _water_regions [ GetWaterRegionIndex ( region_x , region_y ) ] ) ;
2024-01-08 19:29:05 +00:00
}
2024-01-10 01:27:16 +00:00
inline WaterRegionReference GetWaterRegionRef ( TileIndex tile )
2024-01-08 19:29:05 +00:00
{
2024-01-10 01:27:16 +00:00
return GetWaterRegionRef ( GetWaterRegionX ( tile ) , GetWaterRegionY ( tile ) ) ;
}
WaterRegionReference GetUpdatedWaterRegion ( uint32_t region_x , uint32_t region_y )
{
WaterRegionReference ref ( region_x , region_y , _water_regions [ GetWaterRegionIndex ( region_x , region_y ) ] ) ;
ref . UpdateIfNotInitialized ( ) ;
return ref ;
}
WaterRegionReference GetUpdatedWaterRegion ( TileIndex tile )
{
return GetUpdatedWaterRegion ( GetWaterRegionX ( tile ) , GetWaterRegionY ( tile ) ) ;
2024-01-08 19:29:05 +00:00
}
/**
2024-01-27 14:06:14 +00:00
* Returns the index of the water region .
* @ param water_region The water region to return the index for .
2024-01-08 19:29:05 +00:00
*/
TWaterRegionIndex GetWaterRegionIndex ( const WaterRegionDesc & water_region )
{
return GetWaterRegionIndex ( water_region . x , water_region . y ) ;
}
2024-01-27 14:06:14 +00:00
/**
* Calculates a number that uniquely identifies the provided water region patch .
* @ param water_region_patch The Water region to calculate the hash for .
*/
uint32_t CalculateWaterRegionPatchHash ( const WaterRegionPatchDesc & water_region_patch )
{
return water_region_patch . label | GetWaterRegionIndex ( water_region_patch ) < < 8 ;
}
2024-01-08 19:29:05 +00:00
/**
* Returns the center tile of a particular water region .
* @ param water_region The water region to find the center tile for .
* @ returns The center tile of the water region .
*/
TileIndex GetWaterRegionCenterTile ( const WaterRegionDesc & water_region )
{
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 ) ) ;
}
/**
* Returns basic water region information for the provided tile .
* @ param tile The tile for which the information will be calculated .
*/
WaterRegionDesc GetWaterRegionInfo ( TileIndex tile )
{
return WaterRegionDesc { GetWaterRegionX ( tile ) , GetWaterRegionY ( tile ) } ;
}
/**
* Returns basic water region patch information for the provided tile .
* @ param tile The tile for which the information will be calculated .
*/
WaterRegionPatchDesc GetWaterRegionPatchInfo ( TileIndex tile )
{
2024-01-10 01:27:16 +00:00
WaterRegionReference region = GetUpdatedWaterRegion ( tile ) ;
return WaterRegionPatchDesc { GetWaterRegionX ( tile ) , GetWaterRegionY ( tile ) , region . GetLabel ( tile ) } ;
2024-01-08 19:29:05 +00:00
}
/**
* Marks the water region that tile is part of as invalid .
* @ param tile Tile within the water region that we wish to invalidate .
*/
void InvalidateWaterRegion ( TileIndex tile )
{
2024-01-10 01:27:16 +00:00
if ( tile < MapSize ( ) ) {
GetWaterRegionRef ( tile ) . Invalidate ( ) ;
}
2024-01-08 19:29:05 +00:00
}
/**
* Calls the provided callback function for all water region patches
* accessible from one particular side of the starting patch .
* @ param water_region_patch Water patch within the water region to start searching from
* @ param side Side of the water region to look for neigboring patches of water
* @ param callback The function that will be called for each neighbor that is found
*/
static inline void VisitAdjacentWaterRegionPatchNeighbors ( const WaterRegionPatchDesc & water_region_patch , DiagDirection side , TVisitWaterRegionPatchCallBack & func )
{
2024-01-10 01:27:16 +00:00
const WaterRegionReference current_region = GetUpdatedWaterRegion ( water_region_patch . x , water_region_patch . y ) ;
2024-01-08 19:29:05 +00:00
const TileIndexDiffC offset = TileIndexDiffCByDiagDir ( side ) ;
2024-01-09 20:55:27 +00:00
/* Unsigned underflow is allowed here, not UB */
const uint32_t nx = water_region_patch . x + ( uint32_t ) offset . x ;
const uint32_t ny = water_region_patch . y + ( uint32_t ) offset . y ;
2024-01-08 19:29:05 +00:00
2024-01-09 20:55:27 +00:00
if ( nx > = GetWaterRegionMapSizeX ( ) | | ny > = GetWaterRegionMapSizeY ( ) ) return ;
2024-01-08 19:29:05 +00:00
2024-01-10 01:27:16 +00:00
const WaterRegionReference neighboring_region = GetUpdatedWaterRegion ( nx , ny ) ;
2024-01-08 19:29:05 +00:00
const DiagDirection opposite_side = ReverseDiagDir ( side ) ;
/* Indicates via which local x or y coordinates (depends on the "side" parameter) we can cross over into the adjacent region. */
const TWaterRegionTraversabilityBits traversability_bits = current_region . GetEdgeTraversabilityBits ( side )
& neighboring_region . GetEdgeTraversabilityBits ( opposite_side ) ;
if ( traversability_bits = = 0 ) return ;
if ( current_region . NumberOfPatches ( ) = = 1 & & neighboring_region . NumberOfPatches ( ) = = 1 ) {
func ( WaterRegionPatchDesc { nx , ny , FIRST_REGION_LABEL } ) ; // No further checks needed because we know there is just one patch for both adjacent regions
return ;
}
/* Multiple water patches can be reached from the current patch. Check each edge tile individually. */
static std : : vector < TWaterRegionPatchLabel > unique_labels ; // static and vector-instead-of-map for performance reasons
unique_labels . clear ( ) ;
2024-01-09 20:55:27 +00:00
for ( uint32_t x_or_y = 0 ; x_or_y < WATER_REGION_EDGE_LENGTH ; + + x_or_y ) {
2024-01-08 19:29:05 +00:00
if ( ! HasBit ( traversability_bits , x_or_y ) ) continue ;
const TileIndex current_edge_tile = GetEdgeTileCoordinate ( water_region_patch . x , water_region_patch . y , side , x_or_y ) ;
const TWaterRegionPatchLabel current_label = current_region . GetLabel ( current_edge_tile ) ;
if ( current_label ! = water_region_patch . label ) continue ;
const TileIndex neighbor_edge_tile = GetEdgeTileCoordinate ( nx , ny , opposite_side , x_or_y ) ;
const TWaterRegionPatchLabel neighbor_label = neighboring_region . GetLabel ( neighbor_edge_tile ) ;
if ( std : : find ( unique_labels . begin ( ) , unique_labels . end ( ) , neighbor_label ) = = unique_labels . end ( ) ) unique_labels . push_back ( neighbor_label ) ;
}
for ( TWaterRegionPatchLabel unique_label : unique_labels ) func ( WaterRegionPatchDesc { nx , ny , unique_label } ) ;
}
/**
* Calls the provided callback function on all accessible water region patches in
* each cardinal direction , plus any others that are reachable via aqueducts .
* @ param water_region_patch Water patch within the water region to start searching from
* @ param callback The function that will be called for each accessible water patch that is found
*/
void VisitWaterRegionPatchNeighbors ( const WaterRegionPatchDesc & water_region_patch , TVisitWaterRegionPatchCallBack & callback )
{
2024-01-10 01:27:16 +00:00
const WaterRegionReference current_region = GetUpdatedWaterRegion ( water_region_patch . x , water_region_patch . y ) ;
2024-01-08 19:29:05 +00:00
/* Visit adjacent water region patches in each cardinal direction */
for ( DiagDirection side = DIAGDIR_BEGIN ; side < DIAGDIR_END ; side + + ) VisitAdjacentWaterRegionPatchNeighbors ( water_region_patch , side , callback ) ;
/* Visit neigboring water patches accessible via cross-region aqueducts */
if ( current_region . HasCrossRegionAqueducts ( ) ) {
for ( const TileIndex tile : current_region ) {
if ( GetWaterRegionPatchInfo ( tile ) = = water_region_patch & & IsAqueductTile ( tile ) ) {
const TileIndex other_end_tile = GetOtherBridgeEnd ( tile ) ;
if ( GetWaterRegionIndex ( tile ) ! = GetWaterRegionIndex ( other_end_tile ) ) callback ( GetWaterRegionPatchInfo ( other_end_tile ) ) ;
}
}
}
}
/**
* Initializes all water regions . All water tiles will be scanned and interconnected water patches within regions will be identified .
*/
void InitializeWaterRegions ( )
{
2024-01-10 19:19:28 +00:00
_water_regions . reset ( new WaterRegion [ GetWaterRegionMapSizeX ( ) * GetWaterRegionMapSizeY ( ) ] ) ;
2024-01-08 19:29:05 +00:00
}
2024-01-10 01:14:15 +00:00
uint GetWaterRegionTileDebugColourIndex ( TileIndex tile )
{
const uint32_t sub_x = TileX ( tile ) & WATER_REGION_EDGE_MASK ;
const uint32_t sub_y = TileY ( tile ) & WATER_REGION_EDGE_MASK ;
auto get_edge_distance = [ & ] ( uint32_t sub ) - > uint32_t {
if ( sub > WATER_REGION_EDGE_LENGTH / 2 ) sub = WATER_REGION_EDGE_MASK - sub ;
return sub ;
} ;
uint32_t mode = std : : min ( get_edge_distance ( sub_x ) , get_edge_distance ( sub_y ) ) ;
switch ( mode ) {
case 0 : {
2024-01-10 01:27:16 +00:00
const WaterRegionReference wr = GetWaterRegionRef ( tile ) ;
2024-01-10 01:14:15 +00:00
if ( ! wr . IsInitialized ( ) ) return 1 ;
return 2 + wr . NumberOfPatches ( ) ;
}
case 1 : {
2024-01-10 01:27:16 +00:00
const WaterRegionReference wr = GetWaterRegionRef ( tile ) ;
2024-01-10 01:47:24 +00:00
if ( wr . HasPatchStorage ( ) ) return 2 ;
2024-01-10 01:14:15 +00:00
return 0 ;
}
case 2 : {
2024-01-10 01:27:16 +00:00
const WaterRegionReference wr = GetWaterRegionRef ( tile ) ;
2024-01-10 01:14:15 +00:00
if ( wr . IsInitialized ( ) & & wr . HasCrossRegionAqueducts ( ) ) return 9 ;
return 0 ;
}
default :
return 0 ;
}
}
2024-01-10 17:40:51 +00:00
2024-01-10 17:41:09 +00:00
void DebugInvalidateAllWaterRegions ( )
{
const uint32_t size_x = GetWaterRegionMapSizeX ( ) ;
const uint32_t size_y = GetWaterRegionMapSizeY ( ) ;
for ( uint32_t y = 0 ; y < size_y ; y + + ) {
for ( uint32_t x = 0 ; x < size_x ; x + + ) {
GetWaterRegionRef ( x , y ) . Invalidate ( ) ;
}
}
}
void DebugInitAllWaterRegions ( )
{
const uint32_t size_x = GetWaterRegionMapSizeX ( ) ;
const uint32_t size_y = GetWaterRegionMapSizeY ( ) ;
for ( uint32_t y = 0 ; y < size_y ; y + + ) {
for ( uint32_t x = 0 ; x < size_x ; x + + ) {
GetWaterRegionRef ( x , y ) . UpdateIfNotInitialized ( ) ;
}
}
}
2024-01-10 17:40:51 +00:00
void WaterRegionCheckCaches ( std : : function < void ( const char * ) > log )
{
char cclog_buffer [ 1024 ] ;
# define CCLOG(...) { \
char * cc_log_pos = cclog_buffer + seprintf ( cclog_buffer , lastof ( cclog_buffer ) , " Region: %u x %u to %u x %u: " , \
x * WATER_REGION_EDGE_LENGTH , y * WATER_REGION_EDGE_LENGTH , ( x * WATER_REGION_EDGE_LENGTH ) + WATER_REGION_EDGE_MASK , ( y * WATER_REGION_EDGE_LENGTH ) + WATER_REGION_EDGE_MASK ) ; \
seprintf ( cc_log_pos , lastof ( cclog_buffer ) , __VA_ARGS__ ) ; \
DEBUG ( desync , 0 , " %s " , cclog_buffer ) ; \
if ( log ) log ( cclog_buffer ) ; \
}
const uint32_t size_x = GetWaterRegionMapSizeX ( ) ;
const uint32_t size_y = GetWaterRegionMapSizeY ( ) ;
for ( uint32_t y = 0 ; y < size_y ; y + + ) {
for ( uint32_t x = 0 ; x < size_x ; x + + ) {
WaterRegionReference wr = GetWaterRegionRef ( x , y ) ;
if ( ! wr . IsInitialized ( ) ) continue ;
const bool old_has_cross_region_aqueducts = wr . HasCrossRegionAqueducts ( ) ;
const int old_number_of_patches = wr . NumberOfPatches ( ) ;
const TWaterRegionPatchLabelArray old_patch_labels = wr . CopyPatchLabelArray ( ) ;
wr . ForceUpdate ( ) ;
if ( old_has_cross_region_aqueducts ! = wr . HasCrossRegionAqueducts ( ) ) {
CCLOG ( " Has cross region aqueducts mismatch: %u -> %u " , old_has_cross_region_aqueducts , wr . HasCrossRegionAqueducts ( ) ) ;
}
if ( old_number_of_patches ! = wr . NumberOfPatches ( ) ) {
CCLOG ( " Number of patches mismatch: %u -> %u " , old_number_of_patches , wr . NumberOfPatches ( ) ) ;
}
if ( old_patch_labels ! = wr . CopyPatchLabelArray ( ) ) {
CCLOG ( " Patch label mismatch " ) ;
}
}
}
# undef CCLOG
}