File: Pathfinding.h

package info (click to toggle)
0ad 0.0.21-2~bpo8%2B1
  • links: PTS, VCS
  • area: main
  • in suites: jessie-backports
  • size: 54,068 kB
  • sloc: cpp: 230,527; ansic: 23,115; python: 13,559; perl: 2,499; sh: 948; xml: 776; makefile: 696; java: 533; ruby: 229; erlang: 53; sql: 21
file content (384 lines) | stat: -rw-r--r-- 13,181 bytes parent folder | download | duplicates (2)
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