Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CCmpPathfinder_Vertex.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2012 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /**
19  * @file
20  * Vertex-based algorithm for CCmpPathfinder.
21  * Computes paths around the corners of rectangular obstructions.
22  *
23  * Useful search term for this algorithm: "points of visibility".
24  *
25  * Since we sometimes want to use this for avoiding moving units, there is no
26  * pre-computation - the whole visibility graph is effectively regenerated for
27  * each path, and it does A* over that graph.
28  *
29  * This scales very poorly in the number of obstructions, so it should be used
30  * with a limited range and not exceedingly frequently.
31  */
32 
33 #include "precompiled.h"
34 
35 #include "CCmpPathfinder_Common.h"
36 
37 #include "lib/timer.h"
38 #include "ps/Profile.h"
42 
43 /* Quadrant optimisation:
44  * (loosely based on GPG2 "Optimizing Points-of-Visibility Pathfinding")
45  *
46  * Consider the vertex ("@") at a corner of an axis-aligned rectangle ("#"):
47  *
48  * TL : TR
49  * :
50  * ####@ - - -
51  * #####
52  * #####
53  * BL ## BR
54  *
55  * The area around the vertex is split into TopLeft, BottomRight etc quadrants.
56  *
57  * If the shortest path reaches this vertex, it cannot continue to a vertex in
58  * the BL quadrant (it would be blocked by the shape).
59  * Since the shortest path is wrapped tightly around the edges of obstacles,
60  * if the path approached this vertex from the TL quadrant,
61  * it cannot continue to the TL or TR quadrants (the path could be shorter if it
62  * skipped this vertex).
63  * Therefore it must continue to a vertex in the BR quadrant (so this vertex is in
64  * *that* vertex's TL quadrant).
65  *
66  * That lets us significantly reduce the search space by quickly discarding vertexes
67  * from the wrong quadrants.
68  *
69  * (This causes badness if the path starts from inside the shape, so we add some hacks
70  * for that case.)
71  *
72  * (For non-axis-aligned rectangles it's harder to do this computation, so we'll
73  * not bother doing any discarding for those.)
74  */
75 static const u8 QUADRANT_NONE = 0;
76 static const u8 QUADRANT_BL = 1;
77 static const u8 QUADRANT_TR = 2;
78 static const u8 QUADRANT_TL = 4;
79 static const u8 QUADRANT_BR = 8;
83 
84 // A vertex around the corners of an obstruction
85 // (paths will be sequences of these vertexes)
86 struct Vertex
87 {
88  enum
89  {
93  };
94 
96  fixed g, h;
99  u8 quadInward : 4; // the quadrant which is inside the shape (or NONE)
100  u8 quadOutward : 4; // the quadrants of the next point on the path which this vertex must be in, given 'pred'
101 };
102 
103 // Obstruction edges (paths will not cross any of these).
104 // When used in the 'edges' list, defines the two points of the edge.
105 // When used in the 'edgesAA' list, defines the opposing corners of an axis-aligned square
106 // (from which four individual edges can be trivially computed), requiring p0 <= p1
107 struct Edge
108 {
110 };
111 
112 // Axis-aligned obstruction edges.
113 // p0 defines one end; c1 is either the X or Y coordinate of the other end,
114 // depending on the context in which this is used.
115 struct EdgeAA
116 {
119 };
120 
121 // When computing vertexes to insert into the search graph,
122 // add a small delta so that the vertexes of an edge don't get interpreted
123 // as crossing the edge (given minor numerical inaccuracies)
125 
126 /**
127  * Check whether a ray from 'a' to 'b' crosses any of the edges.
128  * (Edges are one-sided so it's only considered a cross if going from front to back.)
129  */
130 inline static bool CheckVisibility(CFixedVector2D a, CFixedVector2D b, const std::vector<Edge>& edges)
131 {
132  CFixedVector2D abn = (b - a).Perpendicular();
133 
134  // Edges of general non-axis-aligned shapes
135  for (size_t i = 0; i < edges.size(); ++i)
136  {
137  CFixedVector2D p0 = edges[i].p0;
138  CFixedVector2D p1 = edges[i].p1;
139 
140  CFixedVector2D d = (p1 - p0).Perpendicular();
141 
142  // If 'a' is behind the edge, we can't cross
143  fixed q = (a - p0).Dot(d);
144  if (q < fixed::Zero())
145  continue;
146 
147  // If 'b' is in front of the edge, we can't cross
148  fixed r = (b - p0).Dot(d);
149  if (r > fixed::Zero())
150  continue;
151 
152  // The ray is crossing the infinitely-extended edge from in front to behind.
153  // Check the finite edge is crossing the infinitely-extended ray too.
154  // (Given the previous tests, it can only be crossing in one direction.)
155  fixed s = (p0 - a).Dot(abn);
156  if (s > fixed::Zero())
157  continue;
158 
159  fixed t = (p1 - a).Dot(abn);
160  if (t < fixed::Zero())
161  continue;
162 
163  return false;
164  }
165 
166  return true;
167 }
168 
169 // Handle the axis-aligned shape edges separately (for performance):
170 // (These are specialised versions of the general unaligned edge code.
171 // They assume the caller has already excluded edges for which 'a' is
172 // on the wrong side.)
173 
174 inline static bool CheckVisibilityLeft(CFixedVector2D a, CFixedVector2D b, const std::vector<EdgeAA>& edges)
175 {
176  if (a.X >= b.X)
177  return true;
178 
179  CFixedVector2D abn = (b - a).Perpendicular();
180 
181  for (size_t i = 0; i < edges.size(); ++i)
182  {
183  if (b.X < edges[i].p0.X)
184  continue;
185 
186  CFixedVector2D p0 (edges[i].p0.X, edges[i].c1);
187  fixed s = (p0 - a).Dot(abn);
188  if (s > fixed::Zero())
189  continue;
190 
191  CFixedVector2D p1 (edges[i].p0.X, edges[i].p0.Y);
192  fixed t = (p1 - a).Dot(abn);
193  if (t < fixed::Zero())
194  continue;
195 
196  return false;
197  }
198 
199  return true;
200 }
201 
202 inline static bool CheckVisibilityRight(CFixedVector2D a, CFixedVector2D b, const std::vector<EdgeAA>& edges)
203 {
204  if (a.X <= b.X)
205  return true;
206 
207  CFixedVector2D abn = (b - a).Perpendicular();
208 
209  for (size_t i = 0; i < edges.size(); ++i)
210  {
211  if (b.X > edges[i].p0.X)
212  continue;
213 
214  CFixedVector2D p0 (edges[i].p0.X, edges[i].c1);
215  fixed s = (p0 - a).Dot(abn);
216  if (s > fixed::Zero())
217  continue;
218 
219  CFixedVector2D p1 (edges[i].p0.X, edges[i].p0.Y);
220  fixed t = (p1 - a).Dot(abn);
221  if (t < fixed::Zero())
222  continue;
223 
224  return false;
225  }
226 
227  return true;
228 }
229 
230 inline static bool CheckVisibilityBottom(CFixedVector2D a, CFixedVector2D b, const std::vector<EdgeAA>& edges)
231 {
232  if (a.Y >= b.Y)
233  return true;
234 
235  CFixedVector2D abn = (b - a).Perpendicular();
236 
237  for (size_t i = 0; i < edges.size(); ++i)
238  {
239  if (b.Y < edges[i].p0.Y)
240  continue;
241 
242  CFixedVector2D p0 (edges[i].p0.X, edges[i].p0.Y);
243  fixed s = (p0 - a).Dot(abn);
244  if (s > fixed::Zero())
245  continue;
246 
247  CFixedVector2D p1 (edges[i].c1, edges[i].p0.Y);
248  fixed t = (p1 - a).Dot(abn);
249  if (t < fixed::Zero())
250  continue;
251 
252  return false;
253  }
254 
255  return true;
256 }
257 
258 inline static bool CheckVisibilityTop(CFixedVector2D a, CFixedVector2D b, const std::vector<EdgeAA>& edges)
259 {
260  if (a.Y <= b.Y)
261  return true;
262 
263  CFixedVector2D abn = (b - a).Perpendicular();
264 
265  for (size_t i = 0; i < edges.size(); ++i)
266  {
267  if (b.Y > edges[i].p0.Y)
268  continue;
269 
270  CFixedVector2D p0 (edges[i].p0.X, edges[i].p0.Y);
271  fixed s = (p0 - a).Dot(abn);
272  if (s > fixed::Zero())
273  continue;
274 
275  CFixedVector2D p1 (edges[i].c1, edges[i].p0.Y);
276  fixed t = (p1 - a).Dot(abn);
277  if (t < fixed::Zero())
278  continue;
279 
280  return false;
281  }
282 
283  return true;
284 }
285 
286 
288 {
289  CFixedVector2D g(goal.x, goal.z);
290 
291  switch (goal.type)
292  {
294  {
295  return g;
296  }
297 
299  {
300  CFixedVector2D d = pos - g;
301  if (d.IsZero())
302  d = CFixedVector2D(fixed::FromInt(1), fixed::Zero()); // some arbitrary direction
303  d.Normalize(goal.hw);
304  return g + d;
305  }
306 
308  {
309  CFixedVector2D halfSize(goal.hw, goal.hh);
310  CFixedVector2D d = pos - g;
311  return g + Geometry::NearestPointOnSquare(d, goal.u, goal.v, halfSize);
312  }
313 
314  default:
315  debug_warn(L"invalid type");
316  return CFixedVector2D();
317  }
318 }
319 
321 {
322  return NearestPointOnGoal(pos, goal);
323  // (It's intentional that we don't put the implementation inside this
324  // function, to avoid the (admittedly unmeasured and probably trivial)
325  // cost of a virtual call inside ComputeShortPath)
326 }
327 
329 
330 struct TileEdge
331 {
332  u16 i, j;
333  enum { TOP, BOTTOM, LEFT, RIGHT } dir;
334 };
335 
336 static void AddTerrainEdges(std::vector<Edge>& edgesAA, std::vector<Vertex>& vertexes,
337  u16 i0, u16 j0, u16 i1, u16 j1, fixed r,
338  ICmpPathfinder::pass_class_t passClass, const Grid<TerrainTile>& terrain)
339 {
340  PROFILE("AddTerrainEdges");
341 
342  std::vector<TileEdge> tileEdges;
343 
344  // Find all edges between tiles of differently passability statuses
345  for (u16 j = j0; j <= j1; ++j)
346  {
347  for (u16 i = i0; i <= i1; ++i)
348  {
349  if (!IS_TERRAIN_PASSABLE(terrain.get(i, j), passClass))
350  {
351  bool any = false; // whether we're adding any edges of this tile
352 
353  if (j > 0 && IS_TERRAIN_PASSABLE(terrain.get(i, j-1), passClass))
354  {
355  TileEdge e = { i, j, TileEdge::BOTTOM };
356  tileEdges.push_back(e);
357  any = true;
358  }
359 
360  if (j < terrain.m_H-1 && IS_TERRAIN_PASSABLE(terrain.get(i, j+1), passClass))
361  {
362  TileEdge e = { i, j, TileEdge::TOP };
363  tileEdges.push_back(e);
364  any = true;
365  }
366 
367  if (i > 0 && IS_TERRAIN_PASSABLE(terrain.get(i-1, j), passClass))
368  {
369  TileEdge e = { i, j, TileEdge::LEFT };
370  tileEdges.push_back(e);
371  any = true;
372  }
373 
374  if (i < terrain.m_W-1 && IS_TERRAIN_PASSABLE(terrain.get(i+1, j), passClass))
375  {
376  TileEdge e = { i, j, TileEdge::RIGHT };
377  tileEdges.push_back(e);
378  any = true;
379  }
380 
381  // If we want to add any edge, then add the whole square to the axis-aligned-edges list.
382  // (The inner edges are redundant but it's easier than trying to split the squares apart.)
383  if (any)
384  {
385  CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
386  CFixedVector2D v1 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
387  Edge e = { v0, v1 };
388  edgesAA.push_back(e);
389  }
390  }
391  }
392  }
393 
394  // TODO: maybe we should precompute these terrain edges since they'll rarely change?
395 
396  // TODO: for efficiency (minimising the A* search space), we should coalesce adjoining edges
397 
398  // Add all the tile outer edges to the search vertex lists
399  for (size_t n = 0; n < tileEdges.size(); ++n)
400  {
401  u16 i = tileEdges[n].i;
402  u16 j = tileEdges[n].j;
403  CFixedVector2D v0, v1;
404  Vertex vert;
405  vert.status = Vertex::UNEXPLORED;
406  vert.quadOutward = QUADRANT_ALL;
407 
408  switch (tileEdges[n].dir)
409  {
410  case TileEdge::BOTTOM:
411  {
412  v0 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
413  v1 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
414  vert.p.X = v0.X - EDGE_EXPAND_DELTA; vert.p.Y = v0.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TR; vertexes.push_back(vert);
415  vert.p.X = v1.X + EDGE_EXPAND_DELTA; vert.p.Y = v1.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TL; vertexes.push_back(vert);
416  break;
417  }
418  case TileEdge::TOP:
419  {
420  v0 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
421  v1 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
422  vert.p.X = v0.X + EDGE_EXPAND_DELTA; vert.p.Y = v0.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BL; vertexes.push_back(vert);
423  vert.p.X = v1.X - EDGE_EXPAND_DELTA; vert.p.Y = v1.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BR; vertexes.push_back(vert);
424  break;
425  }
426  case TileEdge::LEFT:
427  {
428  v0 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
429  v1 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
430  vert.p.X = v0.X - EDGE_EXPAND_DELTA; vert.p.Y = v0.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BR; vertexes.push_back(vert);
431  vert.p.X = v1.X - EDGE_EXPAND_DELTA; vert.p.Y = v1.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TR; vertexes.push_back(vert);
432  break;
433  }
434  case TileEdge::RIGHT:
435  {
436  v0 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
437  v1 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
438  vert.p.X = v0.X + EDGE_EXPAND_DELTA; vert.p.Y = v0.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TL; vertexes.push_back(vert);
439  vert.p.X = v1.X + EDGE_EXPAND_DELTA; vert.p.Y = v1.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BL; vertexes.push_back(vert);
440  break;
441  }
442  }
443  }
444 }
445 
447  const std::vector<Edge>& edgesAA,
448  std::vector<EdgeAA>& edgesLeft, std::vector<EdgeAA>& edgesRight,
449  std::vector<EdgeAA>& edgesBottom, std::vector<EdgeAA>& edgesTop)
450 {
451  edgesLeft.reserve(edgesAA.size());
452  edgesRight.reserve(edgesAA.size());
453  edgesBottom.reserve(edgesAA.size());
454  edgesTop.reserve(edgesAA.size());
455 
456  for (size_t i = 0; i < edgesAA.size(); ++i)
457  {
458  if (a.X <= edgesAA[i].p0.X)
459  {
460  EdgeAA e = { edgesAA[i].p0, edgesAA[i].p1.Y };
461  edgesLeft.push_back(e);
462  }
463  if (a.X >= edgesAA[i].p1.X)
464  {
465  EdgeAA e = { edgesAA[i].p1, edgesAA[i].p0.Y };
466  edgesRight.push_back(e);
467  }
468  if (a.Y <= edgesAA[i].p0.Y)
469  {
470  EdgeAA e = { edgesAA[i].p0, edgesAA[i].p1.X };
471  edgesBottom.push_back(e);
472  }
473  if (a.Y >= edgesAA[i].p1.Y)
474  {
475  EdgeAA e = { edgesAA[i].p1, edgesAA[i].p0.X };
476  edgesTop.push_back(e);
477  }
478  }
479 }
480 
481 /**
482  * Functor for sorting edges by approximate proximity to a fixed point.
483  */
484 struct EdgeSort
485 {
487  EdgeSort(CFixedVector2D src) : src(src) { }
488  bool operator()(const Edge& a, const Edge& b)
489  {
490  if ((a.p0 - src).CompareLength(b.p0 - src) < 0)
491  return true;
492  return false;
493  }
494 };
495 
498  entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& path)
499 {
500  UpdateGrid(); // TODO: only need to bother updating if the terrain changed
501 
502  PROFILE3("ComputeShortPath");
503 // ScopeTimer UID__(L"ComputeShortPath");
504 
506 
507  if (m_DebugOverlay)
508  {
509  // Render the goal shape
511  m_DebugOverlayShortPathLines.back().m_Color = CColor(1, 0, 0, 1);
512  switch (goal.type)
513  {
515  {
517  break;
518  }
520  {
522  break;
523  }
525  {
526  float a = atan2f(goal.v.X.ToFloat(), goal.v.Y.ToFloat());
527  SimRender::ConstructSquareOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), goal.hw.ToFloat()*2, goal.hh.ToFloat()*2, a, m_DebugOverlayShortPathLines.back(), true);
528  break;
529  }
530  }
531  }
532 
533  // List of collision edges - paths must never cross these.
534  // (Edges are one-sided so intersections are fine in one direction, but not the other direction.)
535  std::vector<Edge> edges;
536  std::vector<Edge> edgesAA; // axis-aligned squares
537 
538  // Create impassable edges at the max-range boundary, so we can't escape the region
539  // where we're meant to be searching
540  fixed rangeXMin = x0 - range;
541  fixed rangeXMax = x0 + range;
542  fixed rangeZMin = z0 - range;
543  fixed rangeZMax = z0 + range;
544  {
545  // (The edges are the opposite direction to usual, so it's an inside-out square)
546  Edge e0 = { CFixedVector2D(rangeXMin, rangeZMin), CFixedVector2D(rangeXMin, rangeZMax) };
547  Edge e1 = { CFixedVector2D(rangeXMin, rangeZMax), CFixedVector2D(rangeXMax, rangeZMax) };
548  Edge e2 = { CFixedVector2D(rangeXMax, rangeZMax), CFixedVector2D(rangeXMax, rangeZMin) };
549  Edge e3 = { CFixedVector2D(rangeXMax, rangeZMin), CFixedVector2D(rangeXMin, rangeZMin) };
550  edges.push_back(e0);
551  edges.push_back(e1);
552  edges.push_back(e2);
553  edges.push_back(e3);
554  }
555 
556  // List of obstruction vertexes (plus start/end points); we'll try to find paths through
557  // the graph defined by these vertexes
558  std::vector<Vertex> vertexes;
559 
560  // Add the start point to the graph
561  CFixedVector2D posStart(x0, z0);
562  fixed hStart = (posStart - NearestPointOnGoal(posStart, goal)).Length();
563  Vertex start = { posStart, fixed::Zero(), hStart, 0, Vertex::OPEN, QUADRANT_NONE, QUADRANT_ALL };
564  vertexes.push_back(start);
565  const size_t START_VERTEX_ID = 0;
566 
567  // Add the goal vertex to the graph.
568  // Since the goal isn't always a point, this a special magic virtual vertex which moves around - whenever
569  // we look at it from another vertex, it is moved to be the closest point on the goal shape to that vertex.
571  vertexes.push_back(end);
572  const size_t GOAL_VERTEX_ID = 1;
573 
574  // Add terrain obstructions
575  {
576  u16 i0, j0, i1, j1;
577  NearestTile(rangeXMin, rangeZMin, i0, j0);
578  NearestTile(rangeXMax, rangeZMax, i1, j1);
579  AddTerrainEdges(edgesAA, vertexes, i0, j0, i1, j1, r, passClass, *m_Grid);
580  }
581 
582  // Find all the obstruction squares that might affect us
583  CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
584  std::vector<ICmpObstructionManager::ObstructionSquare> squares;
585  cmpObstructionManager->GetObstructionsInRange(filter, rangeXMin - r, rangeZMin - r, rangeXMax + r, rangeZMax + r, squares);
586 
587  // Resize arrays to reduce reallocations
588  vertexes.reserve(vertexes.size() + squares.size()*4);
589  edgesAA.reserve(edgesAA.size() + squares.size()); // (assume most squares are AA)
590 
591  // Convert each obstruction square into collision edges and search graph vertexes
592  for (size_t i = 0; i < squares.size(); ++i)
593  {
594  CFixedVector2D center(squares[i].x, squares[i].z);
595  CFixedVector2D u = squares[i].u;
596  CFixedVector2D v = squares[i].v;
597 
598  // Expand the vertexes by the moving unit's collision radius, to find the
599  // closest we can get to it
600 
601  CFixedVector2D hd0(squares[i].hw + r + EDGE_EXPAND_DELTA, squares[i].hh + r + EDGE_EXPAND_DELTA);
602  CFixedVector2D hd1(squares[i].hw + r + EDGE_EXPAND_DELTA, -(squares[i].hh + r + EDGE_EXPAND_DELTA));
603 
604  // Check whether this is an axis-aligned square
605  bool aa = (u.X == fixed::FromInt(1) && u.Y == fixed::Zero() && v.X == fixed::Zero() && v.Y == fixed::FromInt(1));
606 
607  Vertex vert;
608  vert.status = Vertex::UNEXPLORED;
609  vert.quadInward = QUADRANT_NONE;
610  vert.quadOutward = QUADRANT_ALL;
611  vert.p.X = center.X - hd0.Dot(u); vert.p.Y = center.Y + hd0.Dot(v); if (aa) vert.quadInward = QUADRANT_BR; vertexes.push_back(vert);
612  vert.p.X = center.X - hd1.Dot(u); vert.p.Y = center.Y + hd1.Dot(v); if (aa) vert.quadInward = QUADRANT_TR; vertexes.push_back(vert);
613  vert.p.X = center.X + hd0.Dot(u); vert.p.Y = center.Y - hd0.Dot(v); if (aa) vert.quadInward = QUADRANT_TL; vertexes.push_back(vert);
614  vert.p.X = center.X + hd1.Dot(u); vert.p.Y = center.Y - hd1.Dot(v); if (aa) vert.quadInward = QUADRANT_BL; vertexes.push_back(vert);
615 
616  // Add the edges:
617 
618  CFixedVector2D h0(squares[i].hw + r, squares[i].hh + r);
619  CFixedVector2D h1(squares[i].hw + r, -(squares[i].hh + r));
620 
621  CFixedVector2D ev0(center.X - h0.Dot(u), center.Y + h0.Dot(v));
622  CFixedVector2D ev1(center.X - h1.Dot(u), center.Y + h1.Dot(v));
623  CFixedVector2D ev2(center.X + h0.Dot(u), center.Y - h0.Dot(v));
624  CFixedVector2D ev3(center.X + h1.Dot(u), center.Y - h1.Dot(v));
625  if (aa)
626  {
627  Edge e = { ev1, ev3 };
628  edgesAA.push_back(e);
629  }
630  else
631  {
632  Edge e0 = { ev0, ev1 };
633  Edge e1 = { ev1, ev2 };
634  Edge e2 = { ev2, ev3 };
635  Edge e3 = { ev3, ev0 };
636  edges.push_back(e0);
637  edges.push_back(e1);
638  edges.push_back(e2);
639  edges.push_back(e3);
640  }
641 
642  // TODO: should clip out vertexes and edges that are outside the range,
643  // to reduce the search space
644  }
645 
646  ENSURE(vertexes.size() < 65536); // we store array indexes as u16
647 
648  if (m_DebugOverlay)
649  {
650  // Render the obstruction edges
651  for (size_t i = 0; i < edges.size(); ++i)
652  {
654  m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1);
655  std::vector<float> xz;
656  xz.push_back(edges[i].p0.X.ToFloat());
657  xz.push_back(edges[i].p0.Y.ToFloat());
658  xz.push_back(edges[i].p1.X.ToFloat());
659  xz.push_back(edges[i].p1.Y.ToFloat());
661  }
662 
663  for (size_t i = 0; i < edgesAA.size(); ++i)
664  {
666  m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1);
667  std::vector<float> xz;
668  xz.push_back(edgesAA[i].p0.X.ToFloat());
669  xz.push_back(edgesAA[i].p0.Y.ToFloat());
670  xz.push_back(edgesAA[i].p0.X.ToFloat());
671  xz.push_back(edgesAA[i].p1.Y.ToFloat());
672  xz.push_back(edgesAA[i].p1.X.ToFloat());
673  xz.push_back(edgesAA[i].p1.Y.ToFloat());
674  xz.push_back(edgesAA[i].p1.X.ToFloat());
675  xz.push_back(edgesAA[i].p0.Y.ToFloat());
676  xz.push_back(edgesAA[i].p0.X.ToFloat());
677  xz.push_back(edgesAA[i].p0.Y.ToFloat());
679  }
680  }
681 
682  // Do an A* search over the vertex/visibility graph:
683 
684  // Since we are just measuring Euclidean distance the heuristic is admissible,
685  // so we never have to re-examine a node once it's been moved to the closed set.
686 
687  // To save time in common cases, we don't precompute a graph of valid edges between vertexes;
688  // we do it lazily instead. When the search algorithm reaches a vertex, we examine every other
689  // vertex and see if we can reach it without hitting any collision edges, and ignore the ones
690  // we can't reach. Since the algorithm can only reach a vertex once (and then it'll be marked
691  // as closed), we won't be doing any redundant visibility computations.
692 
693  PROFILE_START("A*");
694 
695  PriorityQueue open;
696  PriorityQueue::Item qiStart = { START_VERTEX_ID, start.h };
697  open.push(qiStart);
698 
699  u16 idBest = START_VERTEX_ID;
700  fixed hBest = start.h;
701 
702  while (!open.empty())
703  {
704  // Move best tile from open to closed
705  PriorityQueue::Item curr = open.pop();
706  vertexes[curr.id].status = Vertex::CLOSED;
707 
708  // If we've reached the destination, stop
709  if (curr.id == GOAL_VERTEX_ID)
710  {
711  idBest = curr.id;
712  break;
713  }
714 
715  // Sort the edges so ones nearer this vertex are checked first by CheckVisibility,
716  // since they're more likely to block the rays
717  std::sort(edgesAA.begin(), edgesAA.end(), EdgeSort(vertexes[curr.id].p));
718 
719  std::vector<EdgeAA> edgesLeft;
720  std::vector<EdgeAA> edgesRight;
721  std::vector<EdgeAA> edgesBottom;
722  std::vector<EdgeAA> edgesTop;
723  SplitAAEdges(vertexes[curr.id].p, edgesAA, edgesLeft, edgesRight, edgesBottom, edgesTop);
724 
725  // Check the lines to every other vertex
726  for (size_t n = 0; n < vertexes.size(); ++n)
727  {
728  if (vertexes[n].status == Vertex::CLOSED)
729  continue;
730 
731  // If this is the magical goal vertex, move it to near the current vertex
732  CFixedVector2D npos;
733  if (n == GOAL_VERTEX_ID)
734  {
735  npos = NearestPointOnGoal(vertexes[curr.id].p, goal);
736 
737  // To prevent integer overflows later on, we need to ensure all vertexes are
738  // 'close' to the source. The goal might be far away (not a good idea but
739  // sometimes it happens), so clamp it to the current search range
740  npos.X = clamp(npos.X, rangeXMin, rangeXMax);
741  npos.Y = clamp(npos.Y, rangeZMin, rangeZMax);
742  }
743  else
744  {
745  npos = vertexes[n].p;
746  }
747 
748  // Work out which quadrant(s) we're approaching the new vertex from
749  u8 quad = 0;
750  if (vertexes[curr.id].p.X <= npos.X && vertexes[curr.id].p.Y <= npos.Y) quad |= QUADRANT_BL;
751  if (vertexes[curr.id].p.X >= npos.X && vertexes[curr.id].p.Y >= npos.Y) quad |= QUADRANT_TR;
752  if (vertexes[curr.id].p.X <= npos.X && vertexes[curr.id].p.Y >= npos.Y) quad |= QUADRANT_TL;
753  if (vertexes[curr.id].p.X >= npos.X && vertexes[curr.id].p.Y <= npos.Y) quad |= QUADRANT_BR;
754 
755  // Check that the new vertex is in the right quadrant for the old vertex
756  if (!(vertexes[curr.id].quadOutward & quad))
757  {
758  // Hack: Always head towards the goal if possible, to avoid missing it if it's
759  // inside another unit
760  if (n != GOAL_VERTEX_ID)
761  {
762  continue;
763  }
764  }
765 
766  bool visible =
767  CheckVisibilityLeft(vertexes[curr.id].p, npos, edgesLeft) &&
768  CheckVisibilityRight(vertexes[curr.id].p, npos, edgesRight) &&
769  CheckVisibilityBottom(vertexes[curr.id].p, npos, edgesBottom) &&
770  CheckVisibilityTop(vertexes[curr.id].p, npos, edgesTop) &&
771  CheckVisibility(vertexes[curr.id].p, npos, edges);
772 
773  /*
774  // Render the edges that we examine
775  m_DebugOverlayShortPathLines.push_back(SOverlayLine());
776  m_DebugOverlayShortPathLines.back().m_Color = visible ? CColor(0, 1, 0, 0.5) : CColor(1, 0, 0, 0.5);
777  std::vector<float> xz;
778  xz.push_back(vertexes[curr.id].p.X.ToFloat());
779  xz.push_back(vertexes[curr.id].p.Y.ToFloat());
780  xz.push_back(npos.X.ToFloat());
781  xz.push_back(npos.Y.ToFloat());
782  SimRender::ConstructLineOnGround(GetSimContext(), xz, m_DebugOverlayShortPathLines.back(), false);
783  //*/
784 
785  if (visible)
786  {
787  fixed g = vertexes[curr.id].g + (vertexes[curr.id].p - npos).Length();
788 
789  // If this is a new tile, compute the heuristic distance
790  if (vertexes[n].status == Vertex::UNEXPLORED)
791  {
792  // Add it to the open list:
793  vertexes[n].status = Vertex::OPEN;
794  vertexes[n].g = g;
795  vertexes[n].h = DistanceToGoal(npos, goal);
796  vertexes[n].pred = curr.id;
797 
798  // If this is an axis-aligned shape, the path must continue in the same quadrant
799  // direction (but not go into the inside of the shape).
800  // Hack: If we started *inside* a shape then perhaps headed to its corner (e.g. the unit
801  // was very near another unit), don't restrict further pathing.
802  if (vertexes[n].quadInward && !(curr.id == START_VERTEX_ID && g < fixed::FromInt(8)))
803  vertexes[n].quadOutward = ((~vertexes[n].quadInward) & quad) & 0xF;
804 
805  if (n == GOAL_VERTEX_ID)
806  vertexes[n].p = npos; // remember the new best goal position
807 
808  PriorityQueue::Item t = { (u16)n, g + vertexes[n].h };
809  open.push(t);
810 
811  // Remember the heuristically best vertex we've seen so far, in case we never actually reach the target
812  if (vertexes[n].h < hBest)
813  {
814  idBest = (u16)n;
815  hBest = vertexes[n].h;
816  }
817  }
818  else // must be OPEN
819  {
820  // If we've already seen this tile, and the new path to this tile does not have a
821  // better cost, then stop now
822  if (g >= vertexes[n].g)
823  continue;
824 
825  // Otherwise, we have a better path, so replace the old one with the new cost/parent
826  vertexes[n].g = g;
827  vertexes[n].pred = curr.id;
828 
829  // If this is an axis-aligned shape, the path must continue in the same quadrant
830  // direction (but not go into the inside of the shape).
831  if (vertexes[n].quadInward)
832  vertexes[n].quadOutward = ((~vertexes[n].quadInward) & quad) & 0xF;
833 
834  if (n == GOAL_VERTEX_ID)
835  vertexes[n].p = npos; // remember the new best goal position
836 
837  open.promote((u16)n, g + vertexes[n].h);
838  }
839  }
840  }
841  }
842 
843  // Reconstruct the path (in reverse)
844  for (u16 id = idBest; id != START_VERTEX_ID; id = vertexes[id].pred)
845  {
846  Waypoint w = { vertexes[id].p.X, vertexes[id].p.Y };
847  path.m_Waypoints.push_back(w);
848  }
849 
850  PROFILE_END("A*");
851 }
852 
855  pass_class_t passClass)
856 {
857  CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
858  if (!cmpObstructionManager)
859  return false;
860 
861  if (cmpObstructionManager->TestLine(filter, x0, z0, x1, z1, r))
862  return false;
863 
864  // Test against terrain:
865 
866  // (TODO: this could probably be a tiny bit faster by not reusing all the vertex computation code)
867 
868  UpdateGrid();
869 
870  std::vector<Edge> edgesAA;
871  std::vector<Vertex> vertexes;
872 
873  u16 i0, j0, i1, j1;
874  NearestTile(std::min(x0, x1) - r, std::min(z0, z1) - r, i0, j0);
875  NearestTile(std::max(x0, x1) + r, std::max(z0, z1) + r, i1, j1);
876  AddTerrainEdges(edgesAA, vertexes, i0, j0, i1, j1, r, passClass, *m_Grid);
877 
878  CFixedVector2D a(x0, z0);
879  CFixedVector2D b(x1, z1);
880 
881  std::vector<EdgeAA> edgesLeft;
882  std::vector<EdgeAA> edgesRight;
883  std::vector<EdgeAA> edgesBottom;
884  std::vector<EdgeAA> edgesTop;
885  SplitAAEdges(a, edgesAA, edgesLeft, edgesRight, edgesBottom, edgesTop);
886 
887  bool visible =
888  CheckVisibilityLeft(a, b, edgesLeft) &&
889  CheckVisibilityRight(a, b, edgesRight) &&
890  CheckVisibilityBottom(a, b, edgesBottom) &&
891  CheckVisibilityTop(a, b, edgesTop);
892 
893  return visible;
894 }
std::vector< SOverlayLine > m_DebugOverlayShortPathLines
static bool CheckVisibility(CFixedVector2D a, CFixedVector2D b, const std::vector< Edge > &edges)
Check whether a ray from &#39;a&#39; to &#39;b&#39; crosses any of the edges.
#define u8
Definition: types.h:39
A simple fixed-point number class.
Definition: Fixed.h:115
Interface for ICmpObstructionManager Test functions to filter out unwanted shapes.
Declares CCmpPathfinder, whose implementation is split into multiple source files, and provides common code needed for more than one of those files.
CFixedVector2D p0
void UpdateGrid()
Regenerates the grid based on the current obstruction list, if necessary.
Line-based overlay, with world-space coordinates, rendered in the world potentially behind other obje...
Definition: Overlay.h:36
CFixedVector2D v
static fixed DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal &goal)
static CFixed Zero()
Definition: Fixed.h:127
const ssize_t TERRAIN_TILE_SIZE
metres [world space units] per tile in x and z
Definition: Terrain.h:40
Functor for sorting edges by approximate proximity to a fixed point.
static void SplitAAEdges(CFixedVector2D a, const std::vector< Edge > &edgesAA, std::vector< EdgeAA > &edgesLeft, std::vector< EdgeAA > &edgesRight, std::vector< EdgeAA > &edgesBottom, std::vector< EdgeAA > &edgesTop)
bool operator()(const Edge &a, const Edge &b)
void Normalize()
Normalize the vector so that length is close to 1.
Definition: Overlay.h:34
bool IsZero() const
CFixedVector2D p0
u16 m_H
Definition: Grid.h:101
virtual bool TestLine(const IObstructionTestFilter &filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r)=0
Collision test a flat-ended thick line against the current set of shapes.
void NearestTile(entity_pos_t x, entity_pos_t z, u16 &i, u16 &j)
Returns the tile containing the given position.
Priority queue implemented as a binary heap.
Definition: PriorityQueue.h:61
CFixedVector2D p
EdgeSort(CFixedVector2D src)
fixed Dot(const CFixedVector2D &v)
Compute the dot product of this vector with another.
#define PROFILE_END(name)
Definition: Profile.h:198
Grid< TerrainTile > * m_Grid
CFixedVector2D NearestPointOnSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize)
Find point closest to the given point on the edge of the given square or rectangle.
Definition: Geometry.cpp:119
static CFixedVector2D NearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinder::Goal &goal)
enum TileEdge::@55 dir
static const u8 QUADRANT_BR
#define IS_TERRAIN_PASSABLE(item, classmask)
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
static bool CheckVisibilityLeft(CFixedVector2D a, CFixedVector2D b, const std::vector< EdgeAA > &edges)
static bool CheckVisibilityRight(CFixedVector2D a, CFixedVector2D b, const std::vector< EdgeAA > &edges)
void ConstructCircleOnGround(const CSimContext &context, float x, float z, float radius, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line as a circle with given center and radius, conforming to terrain.
Definition: Render.cpp:70
static const entity_pos_t EDGE_EXPAND_DELTA
CFixedVector2D src
void ConstructLineOnGround(const CSimContext &context, const std::vector< float > &xz, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line from given points, conforming to terrain.
Definition: Render.cpp:35
CFixedVector2D u
Definition: path.h:75
float ToFloat() const
Convert to float. May be lossy - float can&#39;t represent all values.
Definition: Fixed.h:161
static const u8 QUADRANT_TL
void promote(ID id, R newrank)
Definition: PriorityQueue.h:86
CFixedVector2D p1
#define PROFILE_START(name)
Definition: Profile.h:197
#define PROFILE(name)
Definition: Profile.h:195
static const u8 QUADRANT_TR
const CSimContext & GetSimContext() const
Definition: IComponent.h:52
A simplified syntax for accessing entity components.
Definition: CmpPtr.h:55
virtual void GetObstructionsInRange(const IObstructionTestFilter &filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector< ObstructionSquare > &squares)=0
Find all the obstructions that are inside (or partially inside) the given range.
static const u8 QUADRANT_TLBR
CEntityHandle GetSystemEntity() const
Definition: IComponent.h:50
static CFixed FromInt(int n)
Definition: Fixed.h:136
virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal &goal)
Returns the coordinates of the point on the goal that is closest to pos in a straight line...
Helper functions related to rendering.
PathfinderOverlay * m_DebugOverlay
#define u16
Definition: types.h:40
enum ICmpPathfinder::Goal::Type type
#define PROFILE3(name)
Definition: Profile.h:201
static const u8 QUADRANT_ALL
T & get(int i, int j) const
Definition: Grid.h:93
static const u8 QUADRANT_BL
void ConstructSquareOnGround(const CSimContext &context, float x, float z, float w, float h, float a, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line as rectangle with given center and dimensions, conforming to terrain...
Definition: Render.cpp:121
static const u8 QUADRANT_BLTR
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
u16 m_W
Definition: Grid.h:101
static bool CheckVisibilityTop(CFixedVector2D a, CFixedVector2D b, const std::vector< EdgeAA > &edges)
static float Length(const SVec3 v)
Definition: mikktspace.cpp:112
PriorityQueueHeap< std::pair< u16, u16 >, u32 > PriorityQueue
void push(const Item &item)
Definition: PriorityQueue.h:70
static void AddTerrainEdges(std::vector< Edge > &edgesAA, std::vector< Vertex > &vertexes, u16 i0, u16 j0, u16 i1, u16 j1, fixed r, ICmpPathfinder::pass_class_t passClass, const Grid< TerrainTile > &terrain)
virtual void ComputeShortPath(const IObstructionTestFilter &filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal &goal, pass_class_t passClass, Path &ret)
T clamp(T value, T min, T max)
Definition: MathUtil.h:32
static bool CheckVisibilityBottom(CFixedVector2D a, CFixedVector2D b, const std::vector< EdgeAA > &edges)
virtual bool CheckMovement(const IObstructionTestFilter &filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass)
Check whether the given movement line is valid and doesn&#39;t hit any obstructions or impassable terrain...
static const u8 QUADRANT_NONE