OpenTTD Source 20251019-master-g9f7f314f81
yapf_ship_regions.cpp
Go to the documentation of this file.
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 */
7
10#include "../../stdafx.h"
11#include "../../ship.h"
12
13#include "yapf.hpp"
14#include "yapf_ship_regions.h"
15#include "../water_regions.h"
16
17#include "../../safeguards.h"
18
19static constexpr int DIRECT_NEIGHBOUR_COST = 100;
20static constexpr int NODES_PER_REGION = 4;
21static constexpr int MAX_NUMBER_OF_NODES = 65536;
22
23static constexpr int NODE_LIST_HASH_BITS_OPEN = 12;
24static constexpr int NODE_LIST_HASH_BITS_CLOSED = 12;
25
28 WaterRegionPatchDesc water_region_patch;
29
30 inline void Set(const WaterRegionPatchDesc &water_region_patch)
31 {
32 this->water_region_patch = water_region_patch;
33 }
34
35 inline int CalcHash() const { return CalculateWaterRegionPatchHash(this->water_region_patch); }
36 inline bool operator==(const WaterRegionPatchKey &other) const { return this->CalcHash() == other.CalcHash(); }
37};
38
39inline uint ManhattanDistance(const WaterRegionPatchKey &a, const WaterRegionPatchKey &b)
40{
41 return (std::abs(a.water_region_patch.x - b.water_region_patch.x) + std::abs(a.water_region_patch.y - b.water_region_patch.y)) * DIRECT_NEIGHBOUR_COST;
42}
43
45struct WaterRegionNode : CYapfNodeT<WaterRegionPatchKey, WaterRegionNode> {
47 using Node = WaterRegionNode;
48
49 inline void Set(Node *parent, const WaterRegionPatchDesc &water_region_patch)
50 {
51 this->key.Set(water_region_patch);
52 this->hash_next = nullptr;
53 this->parent = parent;
54 this->cost = 0;
55 this->estimate = 0;
56 }
57
58 inline void Set(Node *parent, const Key &key)
59 {
60 this->Set(parent, key.water_region_patch);
61 }
62
63 DiagDirection GetDiagDirFromParent() const
64 {
65 if (this->parent == nullptr) return INVALID_DIAGDIR;
66 const int dx = this->key.water_region_patch.x - this->parent->key.water_region_patch.x;
67 const int dy = this->key.water_region_patch.y - this->parent->key.water_region_patch.y;
68 if (dx > 0 && dy == 0) return DIAGDIR_SW;
69 if (dx < 0 && dy == 0) return DIAGDIR_NE;
70 if (dx == 0 && dy > 0) return DIAGDIR_SE;
71 if (dx == 0 && dy < 0) return DIAGDIR_NW;
72 return INVALID_DIAGDIR;
73 }
74};
75
77
78/* We don't need a follower but YAPF requires one. */
80
81class YapfShipRegions;
82
83/* Types struct required for YAPF internals. */
90
93 : public CYapfBaseT<WaterRegionTypes>
94 , public CYapfSegmentCostCacheNoneT<WaterRegionTypes>
95{
96private:
97 using Node = typename WaterRegionTypes::NodeList::Item;
98
99 std::vector<WaterRegionPatchKey> origin_keys;
101
102 inline YapfShipRegions &Yapf()
103 {
104 return *static_cast<YapfShipRegions *>(this);
105 }
106
107public:
108 explicit YapfShipRegions(int max_nodes)
109 {
110 this->max_search_nodes = max_nodes;
111 }
112
113 void AddOrigin(const WaterRegionPatchDesc &water_region_patch)
114 {
115 if (water_region_patch.label == INVALID_WATER_REGION_PATCH) return;
116 if (!HasOrigin(water_region_patch)) {
117 this->origin_keys.emplace_back(water_region_patch);
118 Node &node = Yapf().CreateNewNode();
119 node.Set(nullptr, water_region_patch);
120 Yapf().AddStartupNode(node);
121 }
122 }
123
124 bool HasOrigin(const WaterRegionPatchDesc &water_region_patch)
125 {
126 return std::ranges::find(this->origin_keys, WaterRegionPatchKey{ water_region_patch }) != this->origin_keys.end();
127 }
128
129 void SetDestination(const WaterRegionPatchDesc &water_region_patch)
130 {
131 this->dest.Set(water_region_patch);
132 }
133
134 inline void PfFollowNode(Node &old_node)
135 {
136 VisitWaterRegionPatchCallback visit_func = [&](const WaterRegionPatchDesc &water_region_patch) {
137 Node &node = Yapf().CreateNewNode();
138 node.Set(&old_node, water_region_patch);
139 Yapf().AddNewNode(node, TrackFollower{});
140 };
141 VisitWaterRegionPatchNeighbours(old_node.key.water_region_patch, visit_func);
142 }
143
144 inline bool PfDetectDestination(Node &n) const
145 {
146 return n.key == this->dest;
147 }
148
149 inline bool PfCalcCost(Node &n, const TrackFollower *)
150 {
151 n.cost = n.parent->cost + ManhattanDistance(n.key, n.parent->key);
152
153 /* Incentivise zigzagging by adding a slight penalty when the search continues in the same direction. */
154 Node *grandparent = n.parent->parent;
155 if (grandparent != nullptr) {
156 const DiagDirDiff dir_diff = DiagDirDifference(n.parent->GetDiagDirFromParent(), n.GetDiagDirFromParent());
157 if (dir_diff != DIAGDIRDIFF_90LEFT && dir_diff != DIAGDIRDIFF_90RIGHT) n.cost += 1;
158 }
159
160 return true;
161 }
162
163 inline bool PfCalcEstimate(Node &n)
164 {
165 if (this->PfDetectDestination(n)) {
166 n.estimate = n.cost;
167 return true;
168 }
169
170 n.estimate = n.cost + ManhattanDistance(n.key, this->dest);
171
172 return true;
173 }
174
175 inline char TransportTypeChar() const { return '^'; }
176
177 static std::vector<WaterRegionPatchDesc> FindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
178 {
179 const WaterRegionPatchDesc start_water_region_patch = GetWaterRegionPatchInfo(start_tile);
180
181 /* We reserve 4 nodes (patches) per water region. The vast majority of water regions have 1 or 2 regions so this should be a pretty
182 * safe limit. We cap the limit at 65536 which is at a region size of 16x16 is equivalent to one node per region for a 4096x4096 map. */
183 const int node_limit = std::min(static_cast<int>(Map::Size() * NODES_PER_REGION) / WATER_REGION_NUMBER_OF_TILES, MAX_NUMBER_OF_NODES);
184 YapfShipRegions pf(node_limit);
185 pf.SetDestination(start_water_region_patch);
186
187 if (v->current_order.IsType(OT_GOTO_STATION)) {
188 StationID station_id = v->current_order.GetDestination().ToStationID();
189 const BaseStation *station = BaseStation::Get(station_id);
190 for (const auto &tile : station->GetTileArea(StationType::Dock)) {
191 if (IsDockingTile(tile) && IsShipDestinationTile(tile, station_id)) {
192 pf.AddOrigin(GetWaterRegionPatchInfo(tile));
193 }
194 }
195 } else {
196 TileIndex tile = v->dest_tile;
197 pf.AddOrigin(GetWaterRegionPatchInfo(tile));
198 }
199
200 /* If origin and destination are the same we simply return that water patch. */
201 std::vector<WaterRegionPatchDesc> path = { start_water_region_patch };
202 path.reserve(max_returned_path_length);
203 if (pf.HasOrigin(start_water_region_patch)) return path;
204
205 /* Find best path. */
206 if (!pf.FindPath(v)) return {}; // Path not found.
207
208 Node *node = pf.GetBestNode();
209 for (int i = 0; i < max_returned_path_length - 1; ++i) {
210 if (node != nullptr) {
211 node = node->parent;
212 if (node != nullptr) path.push_back(node->key.water_region_patch);
213 }
214 }
215
216 assert(!path.empty());
217 return path;
218 }
219};
220
228std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
229{
230 return YapfShipRegions::FindWaterRegionPath(v, start_tile, max_returned_path_length);
231}
CYapfBaseT - A-star type path finder base class.
Definition yapf_base.hpp:48
void AddNewNode(Node &n, const TrackFollower &follower)
AddNewNode() - called by Tderived::PfFollowNode() for each child node.
int max_search_nodes
maximum number of nodes we are allowed to visit before we give up
Definition yapf_base.hpp:63
Node * GetBestNode()
If path was found return the best node that has reached the destination.
bool FindPath(const VehicleType *v)
Main pathfinder routine:
Node & CreateNewNode()
Calls NodeList::CreateNewNode() - allocates new node that can be filled and used as argument for AddS...
void AddStartupNode(Node &n)
Add new node (created by CreateNewNode and filled with data) into open list.
CYapfSegmentCostCacheNoneT - the formal only yapf cost cache provider that implements PfNodeCacheFetc...
Hash table based node list multi-container class.
Definition nodelist.hpp:21
Water region based YAPF implementation for ships.
DiagDirDiff DiagDirDifference(DiagDirection d0, DiagDirection d1)
Calculate the difference between two DiagDirection values.
DiagDirDiff
Enumeration for the difference between to DiagDirection.
@ DIAGDIRDIFF_90RIGHT
90 degrees right
@ DIAGDIRDIFF_90LEFT
90 degrees left
DiagDirection
Enumeration for diagonal directions.
@ DIAGDIR_NE
Northeast, upper right on your monitor.
@ DIAGDIR_NW
Northwest.
@ DIAGDIR_SE
Southeast.
@ INVALID_DIAGDIR
Flag for an invalid DiagDirection.
@ DIAGDIR_SW
Southwest.
bool IsShipDestinationTile(TileIndex tile, StationID station)
Test if a tile is a docking tile for the given station.
Definition ship_cmd.cpp:617
Base class for all station-ish types.
virtual TileArea GetTileArea(StationType type) const =0
Get the tile area for a given station type.
Track follower helper template class (can serve pathfinders and vehicle controllers).
Yapf Node base.
Definition yapf_node.hpp:61
static debug_inline uint Size()
Get the size of the map.
Definition map_func.h:287
DestinationID GetDestination() const
Gets the destination of this order.
Definition order_base.h:99
bool IsType(OrderType type) const
Check whether this order is of the given type.
Definition order_base.h:66
static Titem * Get(auto index)
Returns Titem with given index.
All ships have this type.
Definition ship.h:32
Order current_order
The current order (+ status, like: loading)
TileIndex dest_tile
Heading for this tile.
Yapf Node for water regions.
Describes a single interconnected patch of water within a particular water region.
int y
The Y coordinate of the water region, i.e. Y=2 is the 3rd water region along the Y-axis.
int x
The X coordinate of the water region, i.e. X=2 is the 3rd water region along the X-axis.
WaterRegionPatchLabel label
Unique label identifying the patch within the region.
Yapf Node Key that represents a single patch of interconnected water within a water region.
bool IsDockingTile(Tile t)
Checks whether the tile is marked as a dockling tile.
Definition water_map.h:373
WaterRegionPatchDesc GetWaterRegionPatchInfo(TileIndex tile)
Returns basic water region patch information for the provided tile.
int CalculateWaterRegionPatchHash(const WaterRegionPatchDesc &water_region_patch)
Calculates a number that uniquely identifies the provided water region patch.
void VisitWaterRegionPatchNeighbours(const WaterRegionPatchDesc &water_region_patch, VisitWaterRegionPatchCallback &callback)
Calls the provided callback function on all accessible water region patches in each cardinal directio...
Base includes/functions for YAPF.
std::vector< WaterRegionPatchDesc > YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
Finds a path at the water region level.
Implementation of YAPF for water regions, which are used for finding intermediate ship destinations.