From 267855c44e7a6225d58e2575207d53c494ce585f Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 27 Mar 2014 08:37:05 +1100 Subject: [PATCH] Prevent NPC suicides off silt the strider platform in Seyda Neen. Added some comments as well. There may be opportunities for some optimization but left that out for now. --- apps/openmw/mwmechanics/pathfinding.cpp | 208 +++++++++++------------- 1 file changed, 98 insertions(+), 110 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 3ecd40743..c0b78f3c7 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -57,97 +57,6 @@ namespace return closestIndex; } - /*std::list reconstructPath(const std::vector& graph,const ESM::Pathgrid* pathgrid, int lastNode,float xCell, float yCell) - { - std::list path; - while(graph[lastNode].parent != -1) - { - //std::cout << "not empty" << xCell; - ESM::Pathgrid::Point pt = pathgrid->mPoints[lastNode]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - lastNode = graph[lastNode].parent; - } - return path; - }*/ - - - - /*std::list buildPath2(const ESM::Pathgrid* pathgrid,int start,int goal,float xCell = 0, float yCell = 0) - { - std::vector graph; - for(unsigned int i = 0; i < pathgrid->mPoints.size(); i++) - { - Node node; - node.label = i; - node.parent = -1; - graph.push_back(node); - } - for(unsigned int i = 0; i < pathgrid->mEdges.size(); i++) - { - Edge edge; - edge.destination = pathgrid->mEdges[i].mV1; - edge.cost = distance(pathgrid->mPoints[pathgrid->mEdges[i].mV0],pathgrid->mPoints[pathgrid->mEdges[i].mV1]); - graph[pathgrid->mEdges[i].mV0].edges.push_back(edge); - edge.destination = pathgrid->mEdges[i].mV0; - graph[pathgrid->mEdges[i].mV1].edges.push_back(edge); - } - - std::vector g_score(pathgrid->mPoints.size(),-1.); - std::vector f_score(pathgrid->mPoints.size(),-1.); - - g_score[start] = 0; - f_score[start] = distance(pathgrid->mPoints[start],pathgrid->mPoints[goal]); - - std::list openset; - std::list closedset; - openset.push_back(start); - - int current = -1; - - while(!openset.empty()) - { - current = openset.front(); - openset.pop_front(); - - if(current == goal) break; - - closedset.push_back(current); - - for(int j = 0;jmPoints[dest],pathgrid->mPoints[goal]); - if(!isInOpenSet) - { - std::list::iterator it = openset.begin(); - for(it = openset.begin();it!= openset.end();it++) - { - if(g_score[*it]>g_score[dest]) - break; - } - openset.insert(it,dest); - } - } - } - } - - } - return reconstructPath(graph,pathgrid,current,xCell,yCell); - - }*/ - } namespace MWMechanics @@ -166,37 +75,83 @@ namespace MWMechanics mIsPathConstructed = false; } + /* + * NOTE: based on buildPath2(), please check git history if interested + * + * Populate mGraph with the cost of each allowed edge (measured in distance ^2) + * Any existing data in mGraph is wiped clean first. The node's parent is + * set with initial value of -1. The parent values are populated by aStarSearch(). + * mGSore and mFScore are also resized. + * + * + * mGraph[f].edges[n].destination = t + * + * f = point index of location "from" + * t = point index of location "to" + * n = index of edges from point f + * + * + * Example: (note from p(0) to p(2) not allowed) + * + * mGraph[0].edges[0].destination = 1 + * .edges[1].destination = 3 + * + * mGraph[1].edges[0].destination = 0 + * .edges[1].destination = 2 + * .edges[2].destination = 3 + * + * mGraph[2].edges[0].destination = 1 + * + * (etc, etc) + * + * + * low + * cost + * p(0) <---> p(1) <------------> p(2) + * ^ ^ + * | | + * | +-----> p(3) + * +----------------> + * high cost + */ void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid) { mGraph.clear(); - mGScore.resize(pathGrid->mPoints.size(),-1); - mFScore.resize(pathGrid->mPoints.size(),-1); + // resize lists + mGScore.resize(pathGrid->mPoints.size(), -1); + mFScore.resize(pathGrid->mPoints.size(), -1); Node defaultNode; defaultNode.label = -1; defaultNode.parent = -1; mGraph.resize(pathGrid->mPoints.size(),defaultNode); + // initialise mGraph for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++) { Node node; node.label = i; node.parent = -1; - mGraph[i] = node; + mGraph[i] = node; // TODO: old code used push_back(node), check if any difference } + // store the costs (measured in distance ^2) of each edge, in both directions for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) { Edge edge; + edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0], + pathGrid->mPoints[pathGrid->mEdges[i].mV1]); + // forward path of the edge edge.destination = pathGrid->mEdges[i].mV1; - edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0],pathGrid->mPoints[pathGrid->mEdges[i].mV1]); mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); - edge.destination = pathGrid->mEdges[i].mV0; - mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); + // reverse path of the edge + // NOTE: These are redundant, ESM already contains the required reverse paths + //edge.destination = pathGrid->mEdges[i].mV0; + //mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); } mIsGraphConstructed = true; } void PathFinder::cleanUpAStar() { - for(int i=0;i (mGraph.size());i++) + for(int i = 0; i < static_cast (mGraph.size()); i++) { mGraph[i].parent = -1; mGScore[i] = -1; @@ -204,6 +159,25 @@ namespace MWMechanics } } + /* + * NOTE: based on buildPath2(), please check git history if interested + * + * Find the shortest path to the target goal using a well known algorithm. + * Uses mGraph which has pre-computed costs for allowed edges. It is assumed + * that mGraph is already constructed. The caller, i.e. buildPath(), needs + * to ensure this. + * + * Returns path (a list of pathgrid point indexes) which may be empty. + * + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed + * + * mGScore - past accumulated costs vector indexed by point index + * mFScore - future estimated costs vector indexed by point index + * these are resized by buildPathgridGraph() + * + * The heuristics used is distance^2 from current position to the final goal. + */ std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell) { cleanUpAStar(); @@ -218,18 +192,19 @@ namespace MWMechanics while(!openset.empty()) { - current = openset.front(); + current = openset.front(); // front has the lowest cost openset.pop_front(); if(current == goal) break; closedset.push_back(current); - for(int j = 0;j (mGraph[current].edges.size());j++) + // check all edges for the "current" point index + for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) { - //int next = mGraph[current].edges[j].destination if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end()) { + // not in closedset - i.e. have not traversed this edge destination int dest = mGraph[current].edges[j].destination; float tentative_g = mGScore[current] + mGraph[current].edges[j].cost; bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end(); @@ -241,20 +216,22 @@ namespace MWMechanics mFScore[dest] = tentative_g + distance(pathGrid->mPoints[dest],pathGrid->mPoints[goal]); if(!isInOpenSet) { + // add this edge to openset, lowest cost goes to the front + // TODO: if this causes performance problems a hash table may help (apparently) std::list::iterator it = openset.begin(); for(it = openset.begin();it!= openset.end();it++) { - if(mGScore[*it]>mGScore[dest]) + if(mGScore[*it] > mGScore[dest]) break; } - openset.insert(it,dest); + openset.insert(it, dest); } } - } + } // if in closedset, i.e. traversed this edge already, try the next edge } - } + // reconstruct path to return std::list path; while(mGraph[current].parent != -1) { @@ -266,6 +243,9 @@ namespace MWMechanics current = mGraph[current].parent; } + // TODO: Is this a bug? If path is empty the destination is inserted. + // Commented out pending further testing. +#if 0 if(path.empty()) { ESM::Pathgrid::Point pt = pathGrid->mPoints[goal]; @@ -273,10 +253,19 @@ namespace MWMechanics pt.mY += yCell; path.push_front(pt); } - +#endif return path; } + /* + * NOTE: This method may fail to find a path. The caller must check the result before using it. + * If there is no path the AI routies need to implement some other heuristics to reach the target. + * + * Updates mPath using aStarSearch(). + * mPathConstructed is set true if successful, false if not + * + * May update mGraph by calling buildPathgridGraph() if it isn't constructed yet. + */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, const MWWorld::CellStore* cell, bool allowShortcuts) { @@ -310,11 +299,10 @@ namespace MWMechanics { if(!mIsGraphConstructed) buildPathgridGraph(pathGrid); - mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell);//findPath(startNode, endNode, mGraph); + mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell); if(!mPath.empty()) { - mPath.push_back(endPoint); mIsPathConstructed = true; } } @@ -331,8 +319,8 @@ namespace MWMechanics float PathFinder::getZAngleToNext(float x, float y) const { - // This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call - // if otherwise). + // This should never happen (programmers should have an if statement checking + // mIsPathConstructed that prevents this call if otherwise). if(mPath.empty()) return 0.;