1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
|
/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. 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 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_PATHFINDING
#define INCLUDED_PATHFINDING
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "simulation2/system/ParamNode.h"
#include "graphics/Terrain.h"
#include "Grid.h"
#include "PathGoal.h"
typedef u16 pass_class_t;
struct Waypoint
{
entity_pos_t x, z;
};
/**
* Returned path.
* Waypoints are in *reverse* order (the earliest is at the back of the list)
*/
struct WaypointPath
{
std::vector<Waypoint> m_Waypoints;
};
/**
* Represents the cost of a path consisting of horizontal/vertical and
* diagonal movements over a uniform-cost grid.
* Maximum path length before overflow is about 45K steps.
*/
struct PathCost
{
PathCost() : data(0) { }
/// Construct from a number of horizontal/vertical and diagonal steps
PathCost(u16 hv, u16 d)
: data(hv * 65536 + d * 92682) // 2^16 * sqrt(2) == 92681.9
{
}
/// Construct for horizontal/vertical movement of given number of steps
static PathCost horizvert(u16 n)
{
return PathCost(n, 0);
}
/// Construct for diagonal movement of given number of steps
static PathCost diag(u16 n)
{
return PathCost(0, n);
}
PathCost operator+(const PathCost& a) const
{
PathCost c;
c.data = data + a.data;
return c;
}
PathCost& operator+=(const PathCost& a)
{
data += a.data;
return *this;
}
bool operator<=(const PathCost& b) const { return data <= b.data; }
bool operator< (const PathCost& b) const { return data < b.data; }
bool operator>=(const PathCost& b) const { return data >= b.data; }
bool operator>(const PathCost& b) const { return data > b.data; }
u32 ToInt()
{
return data;
}
private:
u32 data;
};
static const int PASS_CLASS_BITS = 16;
typedef u16 NavcellData; // 1 bit per passability class (up to PASS_CLASS_BITS)
#define IS_PASSABLE(item, classmask) (((item) & (classmask)) == 0)
#define PASS_CLASS_MASK_FROM_INDEX(id) ((pass_class_t)(1u << id))
#define SPECIAL_PASS_CLASS PASS_CLASS_MASK_FROM_INDEX((PASS_CLASS_BITS-1)) // 16th bit, used for special in-place computations
namespace Pathfinding
{
/**
* The long-range pathfinder operates primarily over a navigation grid (a uniform-cost
* 2D passability grid, with horizontal/vertical (not diagonal) connectivity).
* This is based on the terrain tile passability, plus the rasterized shapes of
* obstructions, all expanded outwards by the radius of the units.
* Since units are much smaller than terrain tiles, the nav grid should be
* higher resolution than the tiles.
* We therefore split each terrain tile into NxN "nav cells" (for some integer N,
* preferably a power of two).
*/
const int NAVCELLS_PER_TILE = 4;
/**
* Size of a navcell in metres ( = TERRAIN_TILE_SIZE / NAVCELLS_PER_TILE)
*/
const fixed NAVCELL_SIZE = fixed::FromInt((int)TERRAIN_TILE_SIZE) / Pathfinding::NAVCELLS_PER_TILE;
const int NAVCELL_SIZE_INT = 1;
const int NAVCELL_SIZE_LOG2 = 0;
/**
* For extending the goal outwards/inwards a little bit
* NOTE: keep next to the definition of NAVCELL_SIZE to avoid init order problems
* between translation units.
* TODO: figure out whether this is actually needed. It was added back in r8751 (in 2010) for unclear reasons
* and it does not seem to really improve behavior today
*/
const entity_pos_t GOAL_DELTA = NAVCELL_SIZE/8;
/**
* To make sure the long-range pathfinder is more strict than the short-range one,
* we need to slightly over-rasterize. So we extend the clearance radius by 1.
*/
const entity_pos_t CLEARANCE_EXTENSION_RADIUS = fixed::FromInt(1);
/**
* Compute the navcell indexes on the grid nearest to a given point
* w, h are the grid dimensions, i.e. the number of navcells per side
*/
inline void NearestNavcell(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
{
// Use NAVCELL_SIZE_INT to save the cost of dividing by a fixed
i = (u16)clamp((x / NAVCELL_SIZE_INT).ToInt_RoundToNegInfinity(), 0, w - 1);
j = (u16)clamp((z / NAVCELL_SIZE_INT).ToInt_RoundToNegInfinity(), 0, h - 1);
}
/**
* Returns the position of the center of the given tile
*/
inline void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
{
cassert(TERRAIN_TILE_SIZE % 2 == 0);
x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE / 2);
z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE / 2);
}
inline void NavcellCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
{
x = entity_pos_t::FromInt(i * 2 + 1).Multiply(NAVCELL_SIZE / 2);
z = entity_pos_t::FromInt(j * 2 + 1).Multiply(NAVCELL_SIZE / 2);
}
/*
* Checks that the line (x0,z0)-(x1,z1) does not intersect any impassable navcells.
*/
inline bool CheckLineMovement(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1,
pass_class_t passClass, const Grid<NavcellData>& grid)
{
// We shouldn't allow lines between diagonally-adjacent navcells.
// It doesn't matter whether we allow lines precisely along the edge
// of an impassable navcell.
// To rasterise the line:
// If the line is (e.g.) aiming up-right, then we start at the navcell
// containing the start point and the line must either end in that navcell
// or else exit along the top edge or the right edge (or through the top-right corner,
// which we'll arbitrary treat as the horizontal edge).
// So we jump into the adjacent navcell across that edge, and continue.
// To handle the special case of units that are stuck on impassable cells,
// we allow them to move from an impassable to a passable cell (but not
// vice versa).
u16 i0, j0, i1, j1;
NearestNavcell(x0, z0, i0, j0, grid.m_W, grid.m_H);
NearestNavcell(x1, z1, i1, j1, grid.m_W, grid.m_H);
// Find which direction the line heads in
int di = (i0 < i1 ? +1 : i1 < i0 ? -1 : 0);
int dj = (j0 < j1 ? +1 : j1 < j0 ? -1 : 0);
u16 i = i0;
u16 j = j0;
bool currentlyOnImpassable = !IS_PASSABLE(grid.get(i0, j0), passClass);
while (true)
{
// Make sure we are still in the limits
ENSURE(
((di > 0 && i0 <= i && i <= i1) || (di < 0 && i1 <= i && i <= i0) || (di == 0 && i == i0)) &&
((dj > 0 && j0 <= j && j <= j1) || (dj < 0 && j1 <= j && j <= j0) || (dj == 0 && j == j0)));
// Fail if we're moving onto an impassable navcell
bool passable = IS_PASSABLE(grid.get(i, j), passClass);
if (passable)
currentlyOnImpassable = false;
else if (!currentlyOnImpassable)
return false;
// Succeed if we're at the target
if (i == i1 && j == j1)
return true;
// If we can only move horizontally/vertically, then just move in that direction
// If we are reaching the limits, we can go straight to the end
if (di == 0 || i == i1)
{
j += dj;
continue;
}
else if (dj == 0 || j == j1)
{
i += di;
continue;
}
// Otherwise we need to check which cell to move into:
// Check whether the line intersects the horizontal (top/bottom) edge of
// the current navcell.
// Horizontal edge is (i, j + (dj>0?1:0)) .. (i + 1, j + (dj>0?1:0))
// Since we already know the line is moving from this navcell into a different
// navcell, we simply need to test that the edge's endpoints are not both on the
// same side of the line.
// If we are crossing exactly a vertex of the grid, we will get dota or dotb equal
// to 0. In that case we arbitrarily choose to move of dj.
// This only works because we handle the case (i == i1 || j == j1) beforehand.
// Otherwise we could go outside the j limits and never reach the final navcell.
entity_pos_t xia = entity_pos_t::FromInt(i).Multiply(Pathfinding::NAVCELL_SIZE);
entity_pos_t xib = entity_pos_t::FromInt(i+1).Multiply(Pathfinding::NAVCELL_SIZE);
entity_pos_t zj = entity_pos_t::FromInt(j + (dj+1)/2).Multiply(Pathfinding::NAVCELL_SIZE);
CFixedVector2D perp = CFixedVector2D(x1 - x0, z1 - z0).Perpendicular();
entity_pos_t dota = (CFixedVector2D(xia, zj) - CFixedVector2D(x0, z0)).Dot(perp);
entity_pos_t dotb = (CFixedVector2D(xib, zj) - CFixedVector2D(x0, z0)).Dot(perp);
// If the horizontal edge is fully on one side of the line, so the line doesn't
// intersect it, we should move across the vertical edge instead
if ((dota < entity_pos_t::Zero() && dotb < entity_pos_t::Zero()) ||
(dota > entity_pos_t::Zero() && dotb > entity_pos_t::Zero()))
i += di;
else
j += dj;
}
}
}
/*
* For efficient pathfinding we want to try hard to minimise the per-tile search cost,
* so we precompute the tile passability flags and movement costs for the various different
* types of unit.
* We also want to minimise memory usage (there can easily be 100K tiles so we don't want
* to store many bytes for each).
*
* To handle passability efficiently, we have a small number of passability classes
* (e.g. "infantry", "ship"). Each unit belongs to a single passability class, and
* uses that for all its pathfinding.
* Passability is determined by water depth, terrain slope, forestness, buildingness.
* We need at least one bit per class per tile to represent passability.
*
* Not all pass classes are used for actual pathfinding. The pathfinder calls
* CCmpObstructionManager's Rasterize() to add shapes onto the passability grid.
* Which shapes are rasterized depend on the value of the m_Obstructions of each passability
* class.
*
* Passabilities not used for unit pathfinding should not use the Clearance attribute, and
* will get a zero clearance value.
*/
class PathfinderPassability
{
public:
PathfinderPassability(pass_class_t mask, const CParamNode& node) :
m_Mask(mask)
{
if (node.GetChild("MinWaterDepth").IsOk())
m_MinDepth = node.GetChild("MinWaterDepth").ToFixed();
else
m_MinDepth = std::numeric_limits<fixed>::min();
if (node.GetChild("MaxWaterDepth").IsOk())
m_MaxDepth = node.GetChild("MaxWaterDepth").ToFixed();
else
m_MaxDepth = std::numeric_limits<fixed>::max();
if (node.GetChild("MaxTerrainSlope").IsOk())
m_MaxSlope = node.GetChild("MaxTerrainSlope").ToFixed();
else
m_MaxSlope = std::numeric_limits<fixed>::max();
if (node.GetChild("MinShoreDistance").IsOk())
m_MinShore = node.GetChild("MinShoreDistance").ToFixed();
else
m_MinShore = std::numeric_limits<fixed>::min();
if (node.GetChild("MaxShoreDistance").IsOk())
m_MaxShore = node.GetChild("MaxShoreDistance").ToFixed();
else
m_MaxShore = std::numeric_limits<fixed>::max();
if (node.GetChild("Clearance").IsOk())
{
m_Clearance = node.GetChild("Clearance").ToFixed();
/* According to Philip who designed the original doc (in docs/pathfinder.pdf),
* clearance should usually be integer to ensure consistent behavior when rasterizing
* the passability map.
* This seems doubtful to me and my pathfinder fix makes having a clearance of 0.8 quite convenient
* so I comment out this check, but leave it here for the purpose of documentation should a bug arise.
if (!(m_Clearance % Pathfinding::NAVCELL_SIZE).IsZero())
{
// If clearance isn't an integer number of navcells then we'll
// probably get weird behaviour when expanding the navcell grid
// by clearance, vs expanding static obstructions by clearance
LOGWARNING("Pathfinder passability class has clearance %f, should be multiple of %f",
m_Clearance.ToFloat(), Pathfinding::NAVCELL_SIZE.ToFloat());
}*/
}
else
m_Clearance = fixed::Zero();
if (node.GetChild("Obstructions").IsOk())
{
std::wstring obstructions = node.GetChild("Obstructions").ToString();
if (obstructions == L"none")
m_Obstructions = NONE;
else if (obstructions == L"pathfinding")
m_Obstructions = PATHFINDING;
else if (obstructions == L"foundation")
m_Obstructions = FOUNDATION;
else
{
LOGERROR("Invalid value for Obstructions in pathfinder.xml for pass class %d", mask);
m_Obstructions = NONE;
}
}
else
m_Obstructions = NONE;
}
bool IsPassable(fixed waterdepth, fixed steepness, fixed shoredist)
{
return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope) && (m_MinShore <= shoredist && shoredist <= m_MaxShore));
}
pass_class_t m_Mask;
fixed m_Clearance; // min distance from static obstructions
enum ObstructionHandling
{
NONE,
PATHFINDING,
FOUNDATION
};
ObstructionHandling m_Obstructions;
private:
fixed m_MinDepth;
fixed m_MaxDepth;
fixed m_MaxSlope;
fixed m_MinShore;
fixed m_MaxShore;
};
#endif // INCLUDED_PATHFINDING
|