Merge branch 'master' into jgrpp
Bump savegame for water regions for ship pathfinder Use ring_buffer for ShipPathCache
This commit is contained in:
@@ -16,5 +16,7 @@ add_files(
|
||||
yapf_rail.cpp
|
||||
yapf_road.cpp
|
||||
yapf_ship.cpp
|
||||
yapf_ship_regions.h
|
||||
yapf_ship_regions.cpp
|
||||
yapf_type.hpp
|
||||
)
|
||||
|
||||
@@ -14,23 +14,34 @@
|
||||
|
||||
#include "yapf.hpp"
|
||||
#include "yapf_node_ship.hpp"
|
||||
#include "yapf_ship_regions.h"
|
||||
#include "../water_regions.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
constexpr int NUMBER_OR_WATER_REGIONS_LOOKAHEAD = 4;
|
||||
constexpr int MAX_SHIP_PF_NODES = (NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1) * WATER_REGION_NUMBER_OF_TILES * 4; // 4 possible exit dirs per tile.
|
||||
|
||||
constexpr int SHIP_LOST_PATH_LENGTH = 8; // The length of the (aimless) path assigned when a ship is lost.
|
||||
|
||||
template <class Types>
|
||||
class CYapfDestinationTileWaterT
|
||||
{
|
||||
public:
|
||||
typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
|
||||
typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class).
|
||||
typedef typename Types::TrackFollower TrackFollower;
|
||||
typedef typename Types::NodeList::Titem Node; ///< this will be our node type
|
||||
typedef typename Node::Key Key; ///< key to hash tables
|
||||
typedef typename Types::NodeList::Titem Node; ///< this will be our node type.
|
||||
typedef typename Node::Key Key; ///< key to hash tables.
|
||||
|
||||
protected:
|
||||
TileIndex m_destTile;
|
||||
TrackdirBits m_destTrackdirs;
|
||||
StationID m_destStation;
|
||||
|
||||
bool m_has_intermediate_dest = false;
|
||||
TileIndex m_intermediate_dest_tile;
|
||||
WaterRegionPatchDesc m_intermediate_dest_region_patch;
|
||||
|
||||
public:
|
||||
void SetDestination(const Ship *v)
|
||||
{
|
||||
@@ -45,15 +56,22 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void SetIntermediateDestination(const WaterRegionPatchDesc &water_region_patch)
|
||||
{
|
||||
m_has_intermediate_dest = true;
|
||||
m_intermediate_dest_tile = GetWaterRegionCenterTile(water_region_patch);
|
||||
m_intermediate_dest_region_patch = water_region_patch;
|
||||
}
|
||||
|
||||
protected:
|
||||
/** to access inherited path finder */
|
||||
inline Tpf &Yapf()
|
||||
/** To access inherited path finder. */
|
||||
inline Tpf& Yapf()
|
||||
{
|
||||
return *static_cast<Tpf*>(this);
|
||||
}
|
||||
|
||||
public:
|
||||
/** Called by YAPF to detect if node ends in the desired destination */
|
||||
/** Called by YAPF to detect if node ends in the desired destination. */
|
||||
inline bool PfDetectDestination(Node &n)
|
||||
{
|
||||
return PfDetectDestinationTile(n.m_segment_last_tile, n.m_segment_last_td);
|
||||
@@ -61,21 +79,27 @@ public:
|
||||
|
||||
inline bool PfDetectDestinationTile(TileIndex tile, Trackdir trackdir)
|
||||
{
|
||||
if (m_destStation != INVALID_STATION) {
|
||||
return IsDockingTile(tile) && IsShipDestinationTile(tile, m_destStation);
|
||||
if (m_has_intermediate_dest) {
|
||||
/* GetWaterRegionInfo is much faster than GetWaterRegionPatchInfo so we try that first. */
|
||||
if (GetWaterRegionInfo(tile) != m_intermediate_dest_region_patch) return false;
|
||||
return GetWaterRegionPatchInfo(tile) == m_intermediate_dest_region_patch;
|
||||
}
|
||||
|
||||
if (m_destStation != INVALID_STATION) return IsDockingTile(tile) && IsShipDestinationTile(tile, m_destStation);
|
||||
|
||||
return tile == m_destTile && ((m_destTrackdirs & TrackdirToTrackdirBits(trackdir)) != TRACKDIR_BIT_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by YAPF to calculate cost estimate. Calculates distance to the destination
|
||||
* adds it to the actual cost from origin and stores the sum to the Node::m_estimate
|
||||
* adds it to the actual cost from origin and stores the sum to the Node::m_estimate.
|
||||
*/
|
||||
inline bool PfCalcEstimate(Node &n)
|
||||
{
|
||||
static const int dg_dir_to_x_offs[] = {-1, 0, 1, 0};
|
||||
static const int dg_dir_to_y_offs[] = {0, 1, 0, -1};
|
||||
const TileIndex destination_tile = m_has_intermediate_dest ? m_intermediate_dest_tile : m_destTile;
|
||||
|
||||
static const int dg_dir_to_x_offs[] = { -1, 0, 1, 0 };
|
||||
static const int dg_dir_to_y_offs[] = { 0, 1, 0, -1 };
|
||||
if (PfDetectDestination(n)) {
|
||||
n.m_estimate = n.m_cost;
|
||||
return true;
|
||||
@@ -85,8 +109,8 @@ public:
|
||||
DiagDirection exitdir = TrackdirToExitdir(n.m_segment_last_td);
|
||||
int x1 = 2 * TileX(tile) + dg_dir_to_x_offs[(int)exitdir];
|
||||
int y1 = 2 * TileY(tile) + dg_dir_to_y_offs[(int)exitdir];
|
||||
int x2 = 2 * TileX(m_destTile);
|
||||
int y2 = 2 * TileY(m_destTile);
|
||||
int x2 = 2 * TileX(destination_tile);
|
||||
int y2 = 2 * TileY(destination_tile);
|
||||
int dx = abs(x1 - x2);
|
||||
int dy = abs(y1 - y2);
|
||||
int dmin = std::min(dx, dy);
|
||||
@@ -98,24 +122,25 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Node Follower module of YAPF for ships */
|
||||
template <class Types>
|
||||
class CYapfFollowShipT
|
||||
{
|
||||
public:
|
||||
typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
|
||||
typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class).
|
||||
typedef typename Types::TrackFollower TrackFollower;
|
||||
typedef typename Types::NodeList::Titem Node; ///< this will be our node type
|
||||
typedef typename Node::Key Key; ///< key to hash tables
|
||||
typedef typename Types::NodeList::Titem Node; ///< this will be our node type.
|
||||
typedef typename Node::Key Key; ///< key to hash tables.
|
||||
|
||||
protected:
|
||||
/** to access inherited path finder */
|
||||
inline Tpf &Yapf()
|
||||
{
|
||||
return *static_cast<Tpf *>(this);
|
||||
return *static_cast<Tpf*>(this);
|
||||
}
|
||||
|
||||
std::vector<WaterRegionDesc> m_water_region_corridor;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Called by YAPF to move from the given node to the next tile. For each
|
||||
@@ -126,23 +151,57 @@ public:
|
||||
{
|
||||
TrackFollower F(Yapf().GetVehicle());
|
||||
if (F.Follow(old_node.m_key.m_tile, old_node.m_key.m_td)) {
|
||||
Yapf().AddMultipleNodes(&old_node, F);
|
||||
if (m_water_region_corridor.empty()
|
||||
|| std::find(m_water_region_corridor.begin(), m_water_region_corridor.end(),
|
||||
GetWaterRegionInfo(F.m_new_tile)) != m_water_region_corridor.end()) {
|
||||
Yapf().AddMultipleNodes(&old_node, F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** return debug report character to identify the transportation type */
|
||||
/** Restricts the search by creating corridor or water regions through which the ship is allowed to travel. */
|
||||
inline void RestrictSearch(const std::vector<WaterRegionPatchDesc> &path)
|
||||
{
|
||||
m_water_region_corridor.clear();
|
||||
for (const WaterRegionPatchDesc &path_entry : path) m_water_region_corridor.push_back(path_entry);
|
||||
}
|
||||
|
||||
/** Return debug report character to identify the transportation type. */
|
||||
inline char TransportTypeChar() const
|
||||
{
|
||||
return 'w';
|
||||
}
|
||||
|
||||
/** Creates a random path, avoids 90 degree turns. */
|
||||
static Trackdir CreateRandomPath(const Ship *v, TileIndex tile, Trackdir dir, ShipPathCache &path_cache, int path_length)
|
||||
{
|
||||
for (int i = 0; i < path_length; ++i) {
|
||||
TrackFollower F(v);
|
||||
if (F.Follow(tile, dir)) {
|
||||
tile = F.m_new_tile;
|
||||
TrackdirBits dirs = F.m_new_td_bits & ~TrackdirCrossesTrackdirs(dir);
|
||||
const int strip_amount = _random.Next(CountBits(dirs));
|
||||
for (int s = 0; s < strip_amount; ++s) RemoveFirstTrackdir(&dirs);
|
||||
dir = FindFirstTrackdir(dirs);
|
||||
if (dir == INVALID_TRACKDIR) break;
|
||||
path_cache.push_back(dir);
|
||||
}
|
||||
}
|
||||
|
||||
if (path_cache.empty()) return INVALID_TRACKDIR;
|
||||
|
||||
const Trackdir result = path_cache.front();
|
||||
path_cache.pop_front();
|
||||
return result;
|
||||
}
|
||||
|
||||
static Trackdir ChooseShipTrack(const Ship *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool &path_found, ShipPathCache &path_cache)
|
||||
{
|
||||
/* handle special case - when next tile is destination tile */
|
||||
/* Handle special case - when next tile is destination tile. */
|
||||
if (tile == v->dest_tile) {
|
||||
/* convert tracks to trackdirs */
|
||||
/* Convert tracks to trackdirs */
|
||||
TrackdirBits trackdirs = TrackBitsToTrackdirBits(tracks);
|
||||
/* limit to trackdirs reachable from enterdir */
|
||||
/* Limit to trackdirs reachable from enterdir. */
|
||||
trackdirs &= DiagdirReachesTrackdirs(enterdir);
|
||||
|
||||
/* use vehicle's current direction if that's possible, otherwise use first usable one. */
|
||||
@@ -150,68 +209,91 @@ public:
|
||||
return (HasTrackdir(trackdirs, veh_dir)) ? veh_dir : (Trackdir)FindFirstBit2x64(trackdirs);
|
||||
}
|
||||
|
||||
/* move back to the old tile/trackdir (where ship is coming from) */
|
||||
/* Move back to the old tile/trackdir (where ship is coming from). */
|
||||
TileIndex src_tile = TileAddByDiagDir(tile, ReverseDiagDir(enterdir));
|
||||
Trackdir trackdir = v->GetVehicleTrackdir();
|
||||
dbg_assert(IsValidTrackdir(trackdir));
|
||||
|
||||
/* convert origin trackdir to TrackdirBits */
|
||||
/* Convert origin trackdir to TrackdirBits. */
|
||||
TrackdirBits trackdirs = TrackdirToTrackdirBits(trackdir);
|
||||
|
||||
/* create pathfinder instance */
|
||||
Tpf pf;
|
||||
/* set origin and destination nodes */
|
||||
pf.SetOrigin(src_tile, trackdirs);
|
||||
pf.SetDestination(v);
|
||||
/* find best path */
|
||||
path_found = pf.FindPath(v);
|
||||
|
||||
Trackdir next_trackdir = INVALID_TRACKDIR; // this would mean "path not found"
|
||||
|
||||
Node *pNode = pf.GetBestNode();
|
||||
if (pNode != nullptr) {
|
||||
uint steps = 0;
|
||||
for (Node *n = pNode; n->m_parent != nullptr; n = n->m_parent) steps++;
|
||||
uint skip = 0;
|
||||
if (path_found) skip = SHIP_PATH_CACHE_LENGTH / 2;
|
||||
|
||||
/* walk through the path back to the origin */
|
||||
Node *pPrevNode = nullptr;
|
||||
while (pNode->m_parent != nullptr) {
|
||||
steps--;
|
||||
/* Skip tiles at end of path near destination. */
|
||||
if (skip > 0) skip--;
|
||||
if (skip == 0 && steps > 0 && steps < SHIP_PATH_CACHE_LENGTH) {
|
||||
path_cache.push_front(pNode->GetTrackdir());
|
||||
}
|
||||
pPrevNode = pNode;
|
||||
pNode = pNode->m_parent;
|
||||
}
|
||||
/* return trackdir from the best next node (direct child of origin) */
|
||||
Node &best_next_node = *pPrevNode;
|
||||
assert(best_next_node.GetTile() == tile);
|
||||
next_trackdir = best_next_node.GetTrackdir();
|
||||
/* remove last element for the special case when tile == dest_tile */
|
||||
if (path_found && !path_cache.empty()) path_cache.pop_back();
|
||||
const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
|
||||
if (high_level_path.empty()) {
|
||||
path_found = false;
|
||||
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
|
||||
return CreateRandomPath(v, src_tile, trackdir, path_cache, SHIP_LOST_PATH_LENGTH);
|
||||
}
|
||||
return next_trackdir;
|
||||
|
||||
/* Try one time without restricting the search area, which generally results in better and more natural looking paths.
|
||||
* However the pathfinder can hit the node limit in certain situations such as long aqueducts or maze-like terrain.
|
||||
* If that happens we run the pathfinder again, but restricted only to the regions provided by the region pathfinder. */
|
||||
for (int attempt = 0; attempt < 2; ++attempt) {
|
||||
Tpf pf(MAX_SHIP_PF_NODES);
|
||||
|
||||
/* Set origin and destination nodes */
|
||||
pf.SetOrigin(src_tile, trackdirs);
|
||||
pf.SetDestination(v);
|
||||
const bool is_intermediate_destination = static_cast<int>(high_level_path.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1;
|
||||
if (is_intermediate_destination) pf.SetIntermediateDestination(high_level_path.back());
|
||||
|
||||
/* Restrict the search area to prevent the low level pathfinder from expanding too many nodes. This can happen
|
||||
* when the terrain is very "maze-like" or when the high level path "teleports" via a very long aqueduct. */
|
||||
if (attempt > 0) pf.RestrictSearch(high_level_path);
|
||||
|
||||
/* Find best path. */
|
||||
path_found = pf.FindPath(v);
|
||||
Node *node = pf.GetBestNode();
|
||||
if (attempt == 0 && !path_found) continue; // Try again with restricted search area.
|
||||
if (!path_found || !node) return INVALID_TRACKDIR;
|
||||
|
||||
/* Return only the path within the current water region if an intermediate destination was returned. If not, cache the entire path
|
||||
* to the final destination tile. The low-level pathfinder might actually prefer a different docking tile in a nearby region. Without
|
||||
* caching the full path the ship can get stuck in a loop. */
|
||||
const WaterRegionPatchDesc end_water_patch = GetWaterRegionPatchInfo(node->GetTile());
|
||||
const WaterRegionPatchDesc start_water_patch = GetWaterRegionPatchInfo(tile);
|
||||
while (node->m_parent) {
|
||||
const WaterRegionPatchDesc node_water_patch = GetWaterRegionPatchInfo(node->GetTile());
|
||||
if (node_water_patch == start_water_patch || (!is_intermediate_destination && node_water_patch != end_water_patch)) {
|
||||
path_cache.push_front(node->GetTrackdir());
|
||||
}
|
||||
node = node->m_parent;
|
||||
}
|
||||
assert(!path_cache.empty());
|
||||
|
||||
/* Take out the last trackdir as the result. */
|
||||
const Trackdir result = path_cache.front();
|
||||
path_cache.pop_front();
|
||||
|
||||
/* Clear path cache when in final water region patch. This is to allow ships to spread over different docking tiles dynamically. */
|
||||
if (start_water_patch == end_water_patch) path_cache.clear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return INVALID_TRACKDIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a ship should reverse to reach its destination.
|
||||
* Called when leaving depot.
|
||||
* @param v Ship
|
||||
* @param tile Current position
|
||||
* @param td1 Forward direction
|
||||
* @param td2 Reverse direction
|
||||
* @param trackdir [out] the best of all possible reversed trackdirs
|
||||
* @return true if the reverse direction is better
|
||||
* @param v Ship.
|
||||
* @param tile Current position.
|
||||
* @param td1 Forward direction.
|
||||
* @param td2 Reverse direction.
|
||||
* @param trackdir [out] the best of all possible reversed trackdirs.
|
||||
* @return true if the reverse direction is better.
|
||||
*/
|
||||
static bool CheckShipReverse(const Ship *v, TileIndex tile, Trackdir td1, Trackdir td2, Trackdir *trackdir)
|
||||
{
|
||||
/* create pathfinder instance */
|
||||
Tpf pf;
|
||||
/* set origin and destination nodes */
|
||||
const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
|
||||
if (high_level_path.empty()) {
|
||||
if (trackdir) *trackdir = INVALID_TRACKDIR;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Create pathfinder instance. */
|
||||
Tpf pf(MAX_SHIP_PF_NODES);
|
||||
/* Set origin and destination nodes. */
|
||||
if (trackdir == nullptr) {
|
||||
pf.SetOrigin(tile, TrackdirToTrackdirBits(td1) | TrackdirToTrackdirBits(td2));
|
||||
} else {
|
||||
@@ -220,14 +302,16 @@ public:
|
||||
pf.SetOrigin(tile, rtds);
|
||||
}
|
||||
pf.SetDestination(v);
|
||||
/* find best path */
|
||||
if (high_level_path.size() > 1) pf.SetIntermediateDestination(high_level_path.back());
|
||||
pf.RestrictSearch(high_level_path);
|
||||
|
||||
/* Find best path. */
|
||||
if (!pf.FindPath(v)) return false;
|
||||
|
||||
Node *pNode = pf.GetBestNode();
|
||||
if (pNode == nullptr) return false;
|
||||
|
||||
/* path was found
|
||||
* walk through the path back to the origin */
|
||||
/* Path was found, walk through the path back to the origin. */
|
||||
while (pNode->m_parent != nullptr) {
|
||||
pNode = pNode->m_parent;
|
||||
}
|
||||
@@ -242,21 +326,20 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/** Cost Provider module of YAPF for ships */
|
||||
/** Cost Provider module of YAPF for ships. */
|
||||
template <class Types>
|
||||
class CYapfCostShipT
|
||||
{
|
||||
public:
|
||||
typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class)
|
||||
typedef typename Types::Tpf Tpf; ///< the pathfinder class (derived from THIS class).
|
||||
typedef typename Types::TrackFollower TrackFollower;
|
||||
typedef typename Types::NodeList::Titem Node; ///< this will be our node type
|
||||
typedef typename Node::Key Key; ///< key to hash tables
|
||||
typedef typename Types::NodeList::Titem Node; ///< this will be our node type.
|
||||
typedef typename Node::Key Key; ///< key to hash tables.
|
||||
|
||||
protected:
|
||||
/** to access inherited path finder */
|
||||
Tpf &Yapf()
|
||||
{
|
||||
return *static_cast<Tpf *>(this);
|
||||
return *static_cast<Tpf*>(this);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -266,10 +349,10 @@ public:
|
||||
dbg_assert(IsValidTrackdir(td2));
|
||||
|
||||
if (HasTrackdir(TrackdirCrossesTrackdirs(td1), td2)) {
|
||||
/* 90-deg curve penalty */
|
||||
/* 90-deg curve penalty. */
|
||||
return Yapf().PfGetSettings().ship_curve90_penalty;
|
||||
} else if (td2 != NextTrackdir(td1)) {
|
||||
/* 45-deg curve penalty */
|
||||
/* 45-deg curve penalty. */
|
||||
return Yapf().PfGetSettings().ship_curve45_penalty;
|
||||
}
|
||||
return 0;
|
||||
@@ -277,7 +360,7 @@ public:
|
||||
|
||||
static Vehicle *CountShipProc(Vehicle *v, void *data)
|
||||
{
|
||||
uint *count = (uint *)data;
|
||||
uint *count = (uint*)data;
|
||||
/* Ignore other vehicles (aircraft) and ships inside depot. */
|
||||
if ((v->vehstatus & VS_HIDDEN) == 0) (*count)++;
|
||||
|
||||
@@ -286,18 +369,18 @@ public:
|
||||
|
||||
/**
|
||||
* Called by YAPF to calculate the cost from the origin to the given node.
|
||||
* Calculates only the cost of given node, adds it to the parent node cost
|
||||
* and stores the result into Node::m_cost member
|
||||
* Calculates only the cost of given node, adds it to the parent node cost
|
||||
* and stores the result into Node::m_cost member.
|
||||
*/
|
||||
inline bool PfCalcCost(Node &n, const TrackFollower *tf)
|
||||
{
|
||||
/* base tile cost depending on distance */
|
||||
/* Base tile cost depending on distance. */
|
||||
int c = IsDiagonalTrackdir(n.GetTrackdir()) ? YAPF_TILE_LENGTH : YAPF_TILE_CORNER_LENGTH;
|
||||
/* additional penalty for curves */
|
||||
/* Additional penalty for curves. */
|
||||
c += CurveCost(n.m_parent->GetTrackdir(), n.GetTrackdir());
|
||||
|
||||
if (IsDockingTile(n.GetTile())) {
|
||||
/* Check docking tile for occupancy */
|
||||
/* Check docking tile for occupancy. */
|
||||
uint count = 0;
|
||||
HasVehicleOnPos(n.GetTile(), VEH_SHIP, &count, &CountShipProc);
|
||||
c += count * 3 * YAPF_TILE_LENGTH;
|
||||
@@ -311,7 +394,7 @@ public:
|
||||
byte speed_frac = (GetEffectiveWaterClass(n.GetTile()) == WATER_CLASS_SEA) ? svi->ocean_speed_frac : svi->canal_speed_frac;
|
||||
if (speed_frac > 0) c += YAPF_TILE_LENGTH * (1 + tf->m_tiles_skipped) * speed_frac / (256 - speed_frac);
|
||||
|
||||
/* apply it */
|
||||
/* Apply it. */
|
||||
n.m_cost = n.m_parent->m_cost + c;
|
||||
return true;
|
||||
}
|
||||
@@ -319,48 +402,35 @@ public:
|
||||
|
||||
/**
|
||||
* Config struct of YAPF for ships.
|
||||
* Defines all 6 base YAPF modules as classes providing services for CYapfBaseT.
|
||||
* Defines all 6 base YAPF modules as classes providing services for CYapfBaseT.
|
||||
*/
|
||||
template <class Tpf_, class Ttrack_follower, class Tnode_list>
|
||||
struct CYapfShip_TypesT
|
||||
{
|
||||
/** Types - shortcut for this struct type */
|
||||
typedef CYapfShip_TypesT<Tpf_, Ttrack_follower, Tnode_list> Types;
|
||||
typedef CYapfShip_TypesT<Tpf_, Ttrack_follower, Tnode_list> Types; ///< Shortcut for this struct type.
|
||||
typedef Tpf_ Tpf; ///< Pathfinder type.
|
||||
typedef Ttrack_follower TrackFollower; ///< Track follower helper class.
|
||||
typedef Tnode_list NodeList;
|
||||
typedef Ship VehicleType;
|
||||
|
||||
/** Tpf - pathfinder type */
|
||||
typedef Tpf_ Tpf;
|
||||
/** track follower helper class */
|
||||
typedef Ttrack_follower TrackFollower;
|
||||
/** node list type */
|
||||
typedef Tnode_list NodeList;
|
||||
typedef Ship VehicleType;
|
||||
/** pathfinder components (modules) */
|
||||
typedef CYapfBaseT<Types> PfBase; // base pathfinder class
|
||||
typedef CYapfFollowShipT<Types> PfFollow; // node follower
|
||||
typedef CYapfOriginTileT<Types> PfOrigin; // origin provider
|
||||
typedef CYapfDestinationTileWaterT<Types> PfDestination; // destination/distance provider
|
||||
typedef CYapfSegmentCostCacheNoneT<Types> PfCache; // segment cost cache provider
|
||||
typedef CYapfCostShipT<Types> PfCost; // cost provider
|
||||
/** Pathfinder components (modules). */
|
||||
typedef CYapfBaseT<Types> PfBase; ///< Base pathfinder class.
|
||||
typedef CYapfFollowShipT<Types> PfFollow; ///< Node follower.
|
||||
typedef CYapfOriginTileT<Types> PfOrigin; ///< Origin provider.
|
||||
typedef CYapfDestinationTileWaterT<Types> PfDestination; ///< Destination/distance provider.
|
||||
typedef CYapfSegmentCostCacheNoneT<Types> PfCache; ///< Segment cost cache provider.
|
||||
typedef CYapfCostShipT<Types> PfCost; ///< Cost provider.
|
||||
};
|
||||
|
||||
/* YAPF type 1 - uses TileIndex/Trackdir as Node key */
|
||||
struct CYapfShip1 : CYapfT<CYapfShip_TypesT<CYapfShip1, CFollowTrackWater , CShipNodeListTrackDir> > {};
|
||||
/* YAPF type 2 - uses TileIndex/DiagDirection as Node key */
|
||||
struct CYapfShip2 : CYapfT<CYapfShip_TypesT<CYapfShip2, CFollowTrackWater , CShipNodeListExitDir > > {};
|
||||
struct CYapfShip : CYapfT<CYapfShip_TypesT<CYapfShip, CFollowTrackWater, CShipNodeListExitDir > >
|
||||
{
|
||||
explicit CYapfShip(int max_nodes) { m_max_search_nodes = max_nodes; }
|
||||
};
|
||||
|
||||
/** Ship controller helper - path finder invoker */
|
||||
/** Ship controller helper - path finder invoker. */
|
||||
Track YapfShipChooseTrack(const Ship *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks, bool &path_found, ShipPathCache &path_cache)
|
||||
{
|
||||
/* default is YAPF type 2 */
|
||||
typedef Trackdir (*PfnChooseShipTrack)(const Ship*, TileIndex, DiagDirection, TrackBits, bool &path_found, ShipPathCache &path_cache);
|
||||
PfnChooseShipTrack pfnChooseShipTrack = CYapfShip2::ChooseShipTrack; // default: ExitDir
|
||||
|
||||
/* check if non-default YAPF type needed */
|
||||
if (_settings_game.pf.yapf.disable_node_optimization) {
|
||||
pfnChooseShipTrack = &CYapfShip1::ChooseShipTrack; // Trackdir
|
||||
}
|
||||
|
||||
Trackdir td_ret = pfnChooseShipTrack(v, tile, enterdir, tracks, path_found, path_cache);
|
||||
Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, enterdir, tracks, path_found, path_cache);
|
||||
return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK;
|
||||
}
|
||||
|
||||
@@ -369,16 +439,5 @@ bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir)
|
||||
Trackdir td = v->GetVehicleTrackdir();
|
||||
Trackdir td_rev = ReverseTrackdir(td);
|
||||
TileIndex tile = v->tile;
|
||||
|
||||
typedef bool (*PfnCheckReverseShip)(const Ship*, TileIndex, Trackdir, Trackdir, Trackdir*);
|
||||
PfnCheckReverseShip pfnCheckReverseShip = CYapfShip2::CheckShipReverse; // default: ExitDir
|
||||
|
||||
/* check if non-default YAPF type needed */
|
||||
if (_settings_game.pf.yapf.disable_node_optimization) {
|
||||
pfnCheckReverseShip = &CYapfShip1::CheckShipReverse; // Trackdir
|
||||
}
|
||||
|
||||
bool reverse = pfnCheckReverseShip(v, tile, td, td_rev, trackdir);
|
||||
|
||||
return reverse;
|
||||
return CYapfShip::CheckShipReverse(v, tile, td, td_rev, trackdir);
|
||||
}
|
||||
|
||||
314
src/pathfinder/yapf/yapf_ship_regions.cpp
Normal file
314
src/pathfinder/yapf/yapf_ship_regions.cpp
Normal file
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* 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 yapf_ship_regions.cpp Implementation of YAPF for water regions, which are used for finding intermediate ship destinations. */
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../ship.h"
|
||||
|
||||
#include "yapf.hpp"
|
||||
#include "yapf_ship_regions.h"
|
||||
#include "../water_regions.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
constexpr int DIRECT_NEIGHBOR_COST = 100;
|
||||
constexpr int NODES_PER_REGION = 4;
|
||||
constexpr int MAX_NUMBER_OF_NODES = 65536;
|
||||
|
||||
/** Yapf Node Key that represents a single patch of interconnected water within a water region. */
|
||||
struct CYapfRegionPatchNodeKey {
|
||||
WaterRegionPatchDesc m_water_region_patch;
|
||||
|
||||
static_assert(sizeof(TWaterRegionPatchLabel) == sizeof(byte)); // Important for the hash calculation.
|
||||
|
||||
inline void Set(const WaterRegionPatchDesc &water_region_patch)
|
||||
{
|
||||
m_water_region_patch = water_region_patch;
|
||||
}
|
||||
|
||||
inline int CalcHash() const { return m_water_region_patch.label | GetWaterRegionIndex(m_water_region_patch) << 8; }
|
||||
inline bool operator==(const CYapfRegionPatchNodeKey &other) const { return CalcHash() == other.CalcHash(); }
|
||||
};
|
||||
|
||||
inline uint ManhattanDistance(const CYapfRegionPatchNodeKey &a, const CYapfRegionPatchNodeKey &b)
|
||||
{
|
||||
return (std::abs(a.m_water_region_patch.x - b.m_water_region_patch.x) + std::abs(a.m_water_region_patch.y - b.m_water_region_patch.y)) * DIRECT_NEIGHBOR_COST;
|
||||
}
|
||||
|
||||
/** Yapf Node for water regions. */
|
||||
template <class Tkey_>
|
||||
struct CYapfRegionNodeT {
|
||||
typedef Tkey_ Key;
|
||||
typedef CYapfRegionNodeT<Tkey_> Node;
|
||||
|
||||
Tkey_ m_key;
|
||||
Node *m_hash_next;
|
||||
Node *m_parent;
|
||||
int m_cost;
|
||||
int m_estimate;
|
||||
|
||||
inline void Set(Node *parent, const WaterRegionPatchDesc &water_region_patch)
|
||||
{
|
||||
m_key.Set(water_region_patch);
|
||||
m_hash_next = nullptr;
|
||||
m_parent = parent;
|
||||
m_cost = 0;
|
||||
m_estimate = 0;
|
||||
}
|
||||
|
||||
inline void Set(Node *parent, const Key &key)
|
||||
{
|
||||
Set(parent, key.m_water_region_patch);
|
||||
}
|
||||
|
||||
DiagDirection GetDiagDirFromParent() const
|
||||
{
|
||||
if (!m_parent) return INVALID_DIAGDIR;
|
||||
const int dx = m_key.m_water_region_patch.x - m_parent->m_key.m_water_region_patch.x;
|
||||
const int dy = m_key.m_water_region_patch.y - m_parent->m_key.m_water_region_patch.y;
|
||||
if (dx > 0 && dy == 0) return DIAGDIR_SW;
|
||||
if (dx < 0 && dy == 0) return DIAGDIR_NE;
|
||||
if (dx == 0 && dy > 0) return DIAGDIR_SE;
|
||||
if (dx == 0 && dy < 0) return DIAGDIR_NW;
|
||||
return INVALID_DIAGDIR;
|
||||
}
|
||||
|
||||
inline Node *GetHashNext() { return m_hash_next; }
|
||||
inline void SetHashNext(Node *pNext) { m_hash_next = pNext; }
|
||||
inline const Tkey_ &GetKey() const { return m_key; }
|
||||
inline int GetCost() { return m_cost; }
|
||||
inline int GetCostEstimate() { return m_estimate; }
|
||||
inline bool operator<(const Node &other) const { return m_estimate < other.m_estimate; }
|
||||
};
|
||||
|
||||
/** YAPF origin for water regions. */
|
||||
template <class Types>
|
||||
class CYapfOriginRegionT
|
||||
{
|
||||
public:
|
||||
typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class).
|
||||
typedef typename Types::NodeList::Titem Node; ///< This will be our node type.
|
||||
typedef typename Node::Key Key; ///< Key to hash tables.
|
||||
|
||||
protected:
|
||||
inline Tpf &Yapf() { return *static_cast<Tpf*>(this); }
|
||||
|
||||
private:
|
||||
std::vector<CYapfRegionPatchNodeKey> m_origin_keys;
|
||||
|
||||
public:
|
||||
void AddOrigin(const WaterRegionPatchDesc &water_region_patch)
|
||||
{
|
||||
if (!HasOrigin(water_region_patch)) m_origin_keys.push_back(CYapfRegionPatchNodeKey{ water_region_patch });
|
||||
}
|
||||
|
||||
bool HasOrigin(const WaterRegionPatchDesc &water_region_patch)
|
||||
{
|
||||
return std::find(m_origin_keys.begin(), m_origin_keys.end(), CYapfRegionPatchNodeKey{ water_region_patch }) != m_origin_keys.end();
|
||||
}
|
||||
|
||||
void PfSetStartupNodes()
|
||||
{
|
||||
for (const CYapfRegionPatchNodeKey &origin_key : m_origin_keys) {
|
||||
Node &node = Yapf().CreateNewNode();
|
||||
node.Set(nullptr, origin_key);
|
||||
Yapf().AddStartupNode(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** YAPF destination provider for water regions. */
|
||||
template <class Types>
|
||||
class CYapfDestinationRegionT
|
||||
{
|
||||
public:
|
||||
typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class).
|
||||
typedef typename Types::NodeList::Titem Node; ///< This will be our node type.
|
||||
typedef typename Node::Key Key; ///< Key to hash tables.
|
||||
|
||||
protected:
|
||||
Key m_dest;
|
||||
|
||||
public:
|
||||
void SetDestination(const WaterRegionPatchDesc &water_region_patch)
|
||||
{
|
||||
m_dest.Set(water_region_patch);
|
||||
}
|
||||
|
||||
protected:
|
||||
Tpf &Yapf() { return *static_cast<Tpf*>(this); }
|
||||
|
||||
public:
|
||||
inline bool PfDetectDestination(Node &n) const
|
||||
{
|
||||
return n.m_key == m_dest;
|
||||
}
|
||||
|
||||
inline bool PfCalcEstimate(Node &n)
|
||||
{
|
||||
if (PfDetectDestination(n)) {
|
||||
n.m_estimate = n.m_cost;
|
||||
return true;
|
||||
}
|
||||
|
||||
n.m_estimate = n.m_cost + ManhattanDistance(n.m_key, m_dest);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** YAPF node following for water region pathfinding. */
|
||||
template <class Types>
|
||||
class CYapfFollowRegionT
|
||||
{
|
||||
public:
|
||||
typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class).
|
||||
typedef typename Types::TrackFollower TrackFollower;
|
||||
typedef typename Types::NodeList::Titem Node; ///< This will be our node type.
|
||||
typedef typename Node::Key Key; ///< Key to hash tables.
|
||||
|
||||
protected:
|
||||
inline Tpf &Yapf() { return *static_cast<Tpf*>(this); }
|
||||
|
||||
public:
|
||||
inline void PfFollowNode(Node &old_node)
|
||||
{
|
||||
TVisitWaterRegionPatchCallBack visitFunc = [&](const WaterRegionPatchDesc &water_region_patch)
|
||||
{
|
||||
Node &node = Yapf().CreateNewNode();
|
||||
node.Set(&old_node, water_region_patch);
|
||||
Yapf().AddNewNode(node, TrackFollower{});
|
||||
};
|
||||
VisitWaterRegionPatchNeighbors(old_node.m_key.m_water_region_patch, visitFunc);
|
||||
}
|
||||
|
||||
inline char TransportTypeChar() const { return '^'; }
|
||||
|
||||
static std::vector<WaterRegionPatchDesc> FindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
|
||||
{
|
||||
const WaterRegionPatchDesc start_water_region_patch = GetWaterRegionPatchInfo(start_tile);
|
||||
|
||||
/* 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
|
||||
* 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. */
|
||||
Tpf pf(std::min(static_cast<int>(MapSize() * NODES_PER_REGION) / WATER_REGION_NUMBER_OF_TILES, MAX_NUMBER_OF_NODES));
|
||||
pf.SetDestination(start_water_region_patch);
|
||||
|
||||
if (v->current_order.IsType(OT_GOTO_STATION)) {
|
||||
DestinationID station_id = v->current_order.GetDestination();
|
||||
const BaseStation *station = BaseStation::Get(station_id);
|
||||
TileArea tile_area;
|
||||
station->GetTileArea(&tile_area, STATION_DOCK);
|
||||
for (const auto &tile : tile_area) {
|
||||
if (IsDockingTile(tile) && IsShipDestinationTile(tile, station_id)) {
|
||||
pf.AddOrigin(GetWaterRegionPatchInfo(tile));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TileIndex tile = v->dest_tile;
|
||||
pf.AddOrigin(GetWaterRegionPatchInfo(tile));
|
||||
}
|
||||
|
||||
/* If origin and destination are the same we simply return that water patch. */
|
||||
std::vector<WaterRegionPatchDesc> path = { start_water_region_patch };
|
||||
path.reserve(max_returned_path_length);
|
||||
if (pf.HasOrigin(start_water_region_patch)) return path;
|
||||
|
||||
/* Find best path. */
|
||||
if (!pf.FindPath(v)) return {}; // Path not found.
|
||||
|
||||
Node *node = pf.GetBestNode();
|
||||
for (int i = 0; i < max_returned_path_length - 1; ++i) {
|
||||
if (node != nullptr) {
|
||||
node = node->m_parent;
|
||||
if (node != nullptr) path.push_back(node->m_key.m_water_region_patch);
|
||||
}
|
||||
}
|
||||
|
||||
assert(!path.empty());
|
||||
return path;
|
||||
}
|
||||
};
|
||||
|
||||
/** Cost Provider of YAPF for water regions. */
|
||||
template <class Types>
|
||||
class CYapfCostRegionT
|
||||
{
|
||||
public:
|
||||
typedef typename Types::Tpf Tpf; ///< The pathfinder class (derived from THIS class).
|
||||
typedef typename Types::TrackFollower TrackFollower;
|
||||
typedef typename Types::NodeList::Titem Node; ///< This will be our node type.
|
||||
typedef typename Node::Key Key; ///< Key to hash tables.
|
||||
|
||||
protected:
|
||||
/** To access inherited path finder. */
|
||||
Tpf &Yapf() { return *static_cast<Tpf*>(this); }
|
||||
|
||||
public:
|
||||
/**
|
||||
* Called by YAPF to calculate the cost from the origin to the given node.
|
||||
* Calculates only the cost of given node, adds it to the parent node cost
|
||||
* and stores the result into Node::m_cost member.
|
||||
*/
|
||||
inline bool PfCalcCost(Node &n, const TrackFollower *)
|
||||
{
|
||||
n.m_cost = n.m_parent->m_cost + ManhattanDistance(n.m_key, n.m_parent->m_key);
|
||||
|
||||
/* Incentivise zigzagging by adding a slight penalty when the search continues in the same direction. */
|
||||
Node *grandparent = n.m_parent->m_parent;
|
||||
if (grandparent != nullptr) {
|
||||
const DiagDirDiff dir_diff = DiagDirDifference(n.m_parent->GetDiagDirFromParent(), n.GetDiagDirFromParent());
|
||||
if (dir_diff != DIAGDIRDIFF_90LEFT && dir_diff != DIAGDIRDIFF_90RIGHT) n.m_cost += 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/* We don't need a follower but YAPF requires one. */
|
||||
struct DummyFollower : public CFollowTrackWater {};
|
||||
|
||||
/**
|
||||
* Config struct of YAPF for route planning.
|
||||
* Defines all 6 base YAPF modules as classes providing services for CYapfBaseT.
|
||||
*/
|
||||
template <class Tpf_, class Tnode_list>
|
||||
struct CYapfRegion_TypesT
|
||||
{
|
||||
typedef CYapfRegion_TypesT<Tpf_, Tnode_list> Types; ///< Shortcut for this struct type.
|
||||
typedef Tpf_ Tpf; ///< Pathfinder type.
|
||||
typedef DummyFollower TrackFollower; ///< Track follower helper class
|
||||
typedef Tnode_list NodeList;
|
||||
typedef Ship VehicleType;
|
||||
|
||||
/** Pathfinder components (modules). */
|
||||
typedef CYapfBaseT<Types> PfBase; ///< Base pathfinder class.
|
||||
typedef CYapfFollowRegionT<Types> PfFollow; ///< Node follower.
|
||||
typedef CYapfOriginRegionT<Types> PfOrigin; ///< Origin provider.
|
||||
typedef CYapfDestinationRegionT<Types> PfDestination; ///< Destination/distance provider.
|
||||
typedef CYapfSegmentCostCacheNoneT<Types> PfCache; ///< Segment cost cache provider.
|
||||
typedef CYapfCostRegionT<Types> PfCost; ///< Cost provider.
|
||||
};
|
||||
|
||||
typedef CNodeList_HashTableT<CYapfRegionNodeT<CYapfRegionPatchNodeKey>, 12, 12> CRegionNodeListWater;
|
||||
|
||||
struct CYapfRegionWater : CYapfT<CYapfRegion_TypesT<CYapfRegionWater, CRegionNodeListWater>>
|
||||
{
|
||||
explicit CYapfRegionWater(int max_nodes) { m_max_search_nodes = max_nodes; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a path at the water region level. Note that the starting region is always included if the path was found.
|
||||
* @param v The ship to find a path for.
|
||||
* @param start_tile The tile to start searching from.
|
||||
* @param max_returned_path_length The maximum length of the path that will be returned.
|
||||
* @returns A path of water region patches, or an empty vector if no path was found.
|
||||
*/
|
||||
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
|
||||
{
|
||||
return CYapfRegionWater::FindWaterRegionPath(v, start_tile, max_returned_path_length);
|
||||
}
|
||||
21
src/pathfinder/yapf/yapf_ship_regions.h
Normal file
21
src/pathfinder/yapf/yapf_ship_regions.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 yapf_ship_regions.h Implementation of YAPF for water regions, which are used for finding intermediate ship destinations. */
|
||||
|
||||
#ifndef YAPF_SHIP_REGIONS_H
|
||||
#define YAPF_SHIP_REGIONS_H
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../tile_type.h"
|
||||
#include "../water_regions.h"
|
||||
|
||||
struct Ship;
|
||||
|
||||
std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length);
|
||||
|
||||
#endif /* YAPF_SHIP_REGIONS_H */
|
||||
Reference in New Issue
Block a user