PolyVox  0.3.0-dev
Open source voxel management library
AStarPathfinder.inl
Go to the documentation of this file.
1 /*******************************************************************************
2 Copyright (c) 2005-2009 David Williams
3 
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
7 
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely, subject to the following restrictions:
11 
12  1. The origin of this software must not be misrepresented; you must not
13  claim that you wrote the original software. If you use this software
14  in a product, an acknowledgment in the product documentation would be
15  appreciated but is not required.
16 
17  2. Altered source versions must be plainly marked as such, and must not be
18  misrepresented as being the original software.
19 
20  3. This notice may not be removed or altered from any source
21  distribution.
22 *******************************************************************************/
23 
25 
26 namespace PolyVox
27 {
33  template<typename VolumeType>
34  bool aStarDefaultVoxelValidator(const VolumeType* volData, const Vector3DInt32& v3dPos)
35  {
36  //Voxels are considered valid candidates for the path if they are inside the volume...
37  if(volData->getEnclosingRegion().containsPoint(v3dPos) == false)
38  {
39  return false;
40  }
41 
42  return true;
43  }
44 
46  // AStarPathfinder Class
48  template<typename VolumeType>
50  :m_params(params)
51  {
52  }
53 
54  template<typename VolumeType>
56  {
57  //Clear any existing nodes
58  allNodes.clear();
59  openNodes.clear();
60  closedNodes.clear();
61 
62  //Clear the result
63  m_params.result->clear();
64 
65  //Iterators to start and end node.
66  AllNodesContainer::iterator startNode = allNodes.insert(Node(m_params.start.getX(), m_params.start.getY(), m_params.start.getZ())).first;
67  AllNodesContainer::iterator endNode = allNodes.insert(Node(m_params.end.getX(), m_params.end.getY(), m_params.end.getZ())).first;
68 
69  //Regarding the const_cast - normally you should not modify an object which is in an sdt::set.
70  //The reason is that objects in a set are stored sorted in a tree so they can be accessed quickly,
71  //and changing the object directly can break the sorting. However, in our case we have provided a
72  //custom sort operator for the set which we know only uses the position to sort. Hence we can safely
73  //modify other properties of the object while it is in the set.
74  Node* tempStart = const_cast<Node*>(&(*startNode));
75  tempStart->gVal = 0;
76  tempStart->hVal = computeH(startNode->position, endNode->position);
77 
78  Node* tempEnd = const_cast<Node*>(&(*endNode));
79  tempEnd->hVal = 0.0f;
80 
81  openNodes.insert(startNode);
82 
83  float fDistStartToEnd = (endNode->position - startNode->position).length();
84  m_fProgress = 0.0f;
85  if(m_params.progressCallback)
86  {
87  m_params.progressCallback(m_fProgress);
88  }
89 
90  while((openNodes.empty() == false) && (openNodes.getFirst() != endNode))
91  {
92  //Move the first node from open to closed.
93  current = openNodes.getFirst();
94  openNodes.removeFirst();
95  closedNodes.insert(current);
96 
97  //Update the user on our progress
98  if(m_params.progressCallback)
99  {
100  const float fMinProgresIncreament = 0.001f;
101  float fDistCurrentToEnd = (endNode->position - current->position).length();
102  float fDistNormalised = fDistCurrentToEnd / fDistStartToEnd;
103  float fProgress = 1.0f - fDistNormalised;
104  if(fProgress >= m_fProgress + fMinProgresIncreament)
105  {
106  m_fProgress = fProgress;
107  m_params.progressCallback(m_fProgress);
108  }
109  }
110 
111  //The distance from one cell to another connected by face, edge, or corner.
112  const float fFaceCost = sqrt_1;
113  const float fEdgeCost = sqrt_2;
114  const float fCornerCost = sqrt_3;
115 
116  //Process the neighbours. Note the deliberate lack of 'break'
117  //statements, larger connectivities include smaller ones.
118  switch(m_params.connectivity)
119  {
120  case TwentySixConnected:
121  processNeighbour(current->position + arrayPathfinderCorners[0], current->gVal + fCornerCost);
122  processNeighbour(current->position + arrayPathfinderCorners[1], current->gVal + fCornerCost);
123  processNeighbour(current->position + arrayPathfinderCorners[2], current->gVal + fCornerCost);
124  processNeighbour(current->position + arrayPathfinderCorners[3], current->gVal + fCornerCost);
125  processNeighbour(current->position + arrayPathfinderCorners[4], current->gVal + fCornerCost);
126  processNeighbour(current->position + arrayPathfinderCorners[5], current->gVal + fCornerCost);
127  processNeighbour(current->position + arrayPathfinderCorners[6], current->gVal + fCornerCost);
128  processNeighbour(current->position + arrayPathfinderCorners[7], current->gVal + fCornerCost);
129 
130  case EighteenConnected:
131  processNeighbour(current->position + arrayPathfinderEdges[ 0], current->gVal + fEdgeCost);
132  processNeighbour(current->position + arrayPathfinderEdges[ 1], current->gVal + fEdgeCost);
133  processNeighbour(current->position + arrayPathfinderEdges[ 2], current->gVal + fEdgeCost);
134  processNeighbour(current->position + arrayPathfinderEdges[ 3], current->gVal + fEdgeCost);
135  processNeighbour(current->position + arrayPathfinderEdges[ 4], current->gVal + fEdgeCost);
136  processNeighbour(current->position + arrayPathfinderEdges[ 5], current->gVal + fEdgeCost);
137  processNeighbour(current->position + arrayPathfinderEdges[ 6], current->gVal + fEdgeCost);
138  processNeighbour(current->position + arrayPathfinderEdges[ 7], current->gVal + fEdgeCost);
139  processNeighbour(current->position + arrayPathfinderEdges[ 8], current->gVal + fEdgeCost);
140  processNeighbour(current->position + arrayPathfinderEdges[ 9], current->gVal + fEdgeCost);
141  processNeighbour(current->position + arrayPathfinderEdges[10], current->gVal + fEdgeCost);
142  processNeighbour(current->position + arrayPathfinderEdges[11], current->gVal + fEdgeCost);
143 
144  case SixConnected:
145  processNeighbour(current->position + arrayPathfinderFaces[0], current->gVal + fFaceCost);
146  processNeighbour(current->position + arrayPathfinderFaces[1], current->gVal + fFaceCost);
147  processNeighbour(current->position + arrayPathfinderFaces[2], current->gVal + fFaceCost);
148  processNeighbour(current->position + arrayPathfinderFaces[3], current->gVal + fFaceCost);
149  processNeighbour(current->position + arrayPathfinderFaces[4], current->gVal + fFaceCost);
150  processNeighbour(current->position + arrayPathfinderFaces[5], current->gVal + fFaceCost);
151  }
152 
153  if(allNodes.size() > m_params.maxNumberOfNodes)
154  {
155  //We've reached the specified maximum number
156  //of nodes. Just give up on the search.
157  break;
158  }
159  }
160 
161  if((openNodes.empty()) || (openNodes.getFirst() != endNode))
162  {
163  //In this case we failed to find a valid path.
164  POLYVOX_THROW(std::runtime_error, "No path found");
165  }
166  else
167  {
168  //Regarding the const_cast - normally you should not modify an object which is in an sdt::set.
169  //The reason is that objects in a set are stored sorted in a tree so they can be accessed quickly,
170  //and changing the object directly can break the sorting. However, in our case we have provided a
171  //custom sort operator for the set which we know only uses the position to sort. Hence we can safely
172  //modify other properties of the object while it is in the set.
173  Node* n = const_cast<Node*>(&(*endNode));
174  while(n != 0)
175  {
176  m_params.result->push_front(n->position);
177  n = n->parent;
178  }
179  }
180 
181  if(m_params.progressCallback)
182  {
183  m_params.progressCallback(1.0f);
184  }
185  }
186 
187  template<typename VolumeType>
188  void AStarPathfinder<VolumeType>::processNeighbour(const Vector3DInt32& neighbourPos, float neighbourGVal)
189  {
190  bool bIsVoxelValidForPath = m_params.isVoxelValidForPath(m_params.volume, neighbourPos);
191  if(!bIsVoxelValidForPath)
192  {
193  return;
194  }
195 
196  float cost = neighbourGVal;
197 
198  std::pair<AllNodesContainer::iterator, bool> insertResult = allNodes.insert(Node(neighbourPos.getX(), neighbourPos.getY(), neighbourPos.getZ()));
199  AllNodesContainer::iterator neighbour = insertResult.first;
200 
201  if(insertResult.second == true) //New node, compute h.
202  {
203  Node* tempNeighbour = const_cast<Node*>(&(*neighbour));
204  tempNeighbour -> hVal = computeH(neighbour->position, m_params.end);
205  }
206 
207  OpenNodesContainer::iterator openIter = openNodes.find(neighbour);
208  if(openIter != openNodes.end())
209  {
210  if(cost < neighbour->gVal)
211  {
212  openNodes.remove(openIter);
213  openIter = openNodes.end();
214  }
215  }
216 
217  //TODO - Nodes could keep track of if they are in open or closed? And a pointer to where they are?
218  ClosedNodesContainer::iterator closedIter = closedNodes.find(neighbour);
219  if(closedIter != closedNodes.end())
220  {
221  if(cost < neighbour->gVal)
222  {
223  //Probably shouldn't happen?
224  closedNodes.remove(closedIter);
225  closedIter = closedNodes.end();
226  }
227  }
228 
229  if((openIter == openNodes.end()) && (closedIter == closedNodes.end()))
230  {
231  //Regarding the const_cast - normally you should not modify an object which is in an sdt::set.
232  //The reason is that objects in a set are stored sorted in a tree so they can be accessed quickly,
233  //and changing the object directly can break the sorting. However, in our case we have provided a
234  //custom sort operator for the set which we know only uses the position to sort. Hence we can safely
235  //modify other properties of the object while it is in the set.
236  Node* temp = const_cast<Node*>(&(*neighbour));
237  temp->gVal = cost;
238  openNodes.insert(neighbour);
239  temp->parent = const_cast<Node*>(&(*current));
240  }
241  }
242 
243  template<typename VolumeType>
244  float AStarPathfinder<VolumeType>::SixConnectedCost(const Vector3DInt32& a, const Vector3DInt32& b)
245  {
246  //This is the only heuristic I'm sure of - just use the manhatten distance for the 6-connected case.
247  uint32_t faceSteps = std::abs(a.getX()-b.getX()) + std::abs(a.getY()-b.getY()) + std::abs(a.getZ()-b.getZ());
248 
249  return faceSteps * 1.0f;
250  }
251 
252  template<typename VolumeType>
253  float AStarPathfinder<VolumeType>::EighteenConnectedCost(const Vector3DInt32& a, const Vector3DInt32& b)
254  {
255  //I'm not sure of the correct heuristic for the 18-connected case, so I'm just letting it fall through to the
256  //6-connected case. This means 'h' will be bigger than it should be, resulting in a faster path which may not
257  //actually be the shortest one. If you have a correct heuristic for the 18-connected case then please let me know.
258 
259  return SixConnectedCost(a,b);
260  }
261 
262  template<typename VolumeType>
263  float AStarPathfinder<VolumeType>::TwentySixConnectedCost(const Vector3DInt32& a, const Vector3DInt32& b)
264  {
265  //Can't say I'm certain about this heuristic - if anyone has
266  //a better idea of what it should be then please let me know.
267  uint32_t array[3];
268  array[0] = std::abs(a.getX() - b.getX());
269  array[1] = std::abs(a.getY() - b.getY());
270  array[2] = std::abs(a.getZ() - b.getZ());
271 
272  //Maybe this is better implemented directly
273  //using three compares and two swaps... but not
274  //until the profiler says so.
275  std::sort(&array[0], &array[3]);
276 
277  uint32_t cornerSteps = array[0];
278  uint32_t edgeSteps = array[1] - array[0];
279  uint32_t faceSteps = array[2] - array[1];
280 
281  return cornerSteps * sqrt_3 + edgeSteps * sqrt_2 + faceSteps * sqrt_1;
282  }
283 
284  template<typename VolumeType>
285  float AStarPathfinder<VolumeType>::computeH(const Vector3DInt32& a, const Vector3DInt32& b)
286  {
287  float hVal;
288 
289  switch(m_params.connectivity)
290  {
291  case TwentySixConnected:
292  hVal = TwentySixConnectedCost(a, b);
293  break;
294  case EighteenConnected:
295  hVal = EighteenConnectedCost(a, b);
296  break;
297  case SixConnected:
298  hVal = SixConnectedCost(a, b);
299  break;
300  default:
301  POLYVOX_ASSERT(false, "Invalid case");
302  }
303 
304  //Sanity checks in debug mode. These can come out eventually, but I
305  //want to make sure that the heuristics I've come up with make sense.
306  POLYVOX_ASSERT((a-b).length() <= TwentySixConnectedCost(a,b), "A* heuristic error.");
307  POLYVOX_ASSERT(TwentySixConnectedCost(a,b) <= EighteenConnectedCost(a,b), "A* heuristic error.");
308  POLYVOX_ASSERT(EighteenConnectedCost(a,b) <= SixConnectedCost(a,b), "A* heuristic error.");
309 
310  //Apply the bias to the computed h value;
311  hVal *= m_params.hBias;
312 
313  //Having computed hVal, we now apply some random bias to break ties.
314  //This needs to be deterministic on the input position. This random
315  //bias means it is much les likely that two paths are exactly the same
316  //length, and so far fewer nodes must be expanded to find the shortest path.
317  //See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#S12
318 
319  //Note that if the hash is zero we can have differences between the Linux vs. Windows
320  //(or perhaps GCC vs. VS) versions of the code. This is probably because of the way
321  //sorting inside the std::set works (i.e. one system swaps values which are identical
322  //while the other one doesn't - both approaches are valid). For the same reason we want
323  //to make sure that position (x,y,z) has a differnt hash from e.g. position (x,z,y).
324  uint32_t aX = (a.getX() << 16) & 0x00FF0000;
325  uint32_t aY = (a.getY() << 8) & 0x0000FF00;
326  uint32_t aZ = (a.getZ() ) & 0x000000FF;
327  uint32_t hashVal = hash(aX | aY | aZ);
328 
329  //Stop hashVal going over 65535, and divide by 1000000 to make sure it is small.
330  hashVal &= 0x0000FFFF;
331  float fHash = hashVal / 1000000.0f;
332 
333  //Apply the hash and return
334  hVal += fHash;
335  return hVal;
336  }
337 
338  // Robert Jenkins' 32 bit integer hash function
339  // http://www.burtleburtle.net/bob/hash/integer.html
340  template<typename VolumeType>
341  uint32_t AStarPathfinder<VolumeType>::hash( uint32_t a)
342  {
343  a = (a+0x7ed55d16) + (a<<12);
344  a = (a^0xc761c23c) ^ (a>>19);
345  a = (a+0x165667b1) + (a<<5);
346  a = (a+0xd3a2646c) ^ (a<<9);
347  a = (a+0xfd7046c5) + (a<<3);
348  a = (a^0xb55a4f09) ^ (a>>16);
349  return a;
350  }
351 }