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