diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d1f7c45f31..ea4756ac4d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -68,7 +68,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow - aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting + aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting disease pickpocket levelledlist combat steering ) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 9fcd335e41..a4166d83b9 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -1,7 +1,5 @@ #include "pathfinding.hpp" -#include - #include "OgreMath.h" #include "OgreVector3.h" @@ -37,44 +35,6 @@ namespace return sqrt(x * x + y * y + z * z); } - // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html - // - // One of the smallest cost in Seyda Neen is between points 77 & 78: - // pt x y - // 77 = 8026, 4480 - // 78 = 7986, 4218 - // - // Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300 - // (again ignoring z). Using a value of about 300 for D seems like a reasonable - // starting point for experiments. If in doubt, just use value 1. - // - // The distance between 3 & 4 are pretty small, too. - // 3 = 5435, 223 - // 4 = 5948, 193 - // - // Approx. 514 Euclidean distance and 533 Manhattan distance. - // - float manhattan(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) - { - return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); - } - - // Choose a heuristics - Note that these may not be the best for directed - // graphs with non-uniform edge costs. - // - // distance: - // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) - // - slower but more accurate - // - // Manhattan: - // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| - // - faster but not the shortest path - float costAStar(const ESM::Pathgrid::Point a, const ESM::Pathgrid::Point b) - { - //return distance(a, b); - return manhattan(a, b); - } - // Slightly cheaper version for comparisons. // Caller needs to be careful for very short distances (i.e. less than 1) // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 @@ -368,299 +328,4 @@ namespace MWMechanics } - // TODO: Any multi threading concerns? - PathgridGraph::PathgridGraph() - : mCell(NULL) - , mIsGraphConstructed(false) - , mPathgrid(NULL) - , mGraph(0) - , mSCCId(0) - , mSCCIndex(0) - { - } - - /* - * mGraph is populated with the cost of each allowed edge. - * - * The data structure is based on the code in buildPath2() but modified. - * Please check git history if interested. - * - * mGraph[v].edges[i].index = w - * - * v = point index of location "from" - * i = index of edges from point v - * w = point index of location "to" - * - * - * Example: (notice from p(0) to p(2) is not allowed in this example) - * - * mGraph[0].edges[0].index = 1 - * .edges[1].index = 3 - * - * mGraph[1].edges[0].index = 0 - * .edges[1].index = 2 - * .edges[2].index = 3 - * - * mGraph[2].edges[0].index = 1 - * - * (etc, etc) - * - * - * low - * cost - * p(0) <---> p(1) <------------> p(2) - * ^ ^ - * | | - * | +-----> p(3) - * +----------------> - * high cost - */ - bool PathgridGraph::initPathgridGraph(const ESM::Cell* cell) - { - if(!cell) - return false; - - mCell = cell; - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); - - if(!mPathgrid) - return false; - - mGraph.resize(mPathgrid->mPoints.size()); - for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) - { - ConnectedPoint neighbour; - neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], - mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); - // forward path of the edge - neighbour.index = mPathgrid->mEdges[i].mV1; - mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); - // reverse path of the edge - // NOTE: These are redundant, ESM already contains the required reverse paths - //neighbour.index = mPathgrid->mEdges[i].mV0; - //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); - } - buildConnectedPoints(); - mIsGraphConstructed = true; -#if 0 - std::cout << "loading pathgrid " << - +"\""+ mPathgrid->mCell +"\"" - +", "+ std::to_string(mPathgrid->mData.mX) - +", "+ std::to_string(mPathgrid->mData.mY) - << std::endl; -#endif - return true; - } - - // v is the pathgrid point index (some call them vertices) - void PathgridGraph::recursiveStrongConnect(int v) - { - mSCCPoint[v].first = mSCCIndex; // index - mSCCPoint[v].second = mSCCIndex; // lowlink - mSCCIndex++; - mSCCStack.push_back(v); - int w; - - for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) - { - w = mGraph[v].edges[i].index; - if(mSCCPoint[w].first == -1) // not visited - { - recursiveStrongConnect(w); // recurse - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].second); - } - else - { - if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) - mSCCPoint[v].second = std::min(mSCCPoint[v].second, - mSCCPoint[w].first); - } - } - - if(mSCCPoint[v].second == mSCCPoint[v].first) - { // new component - do - { - w = mSCCStack.back(); - mSCCStack.pop_back(); - mGraph[w].componentId = mSCCId; - } - while(w != v); - mSCCId++; - } - return; - } - - /* - * mGraph contains the strongly connected component group id's along - * with pre-calculated edge costs. - * - * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 - * - * mGraph for Seyda Neen will therefore have 3 different values. When - * selecting a random pathgrid point for AiWander, mGraph can be checked - * for quickly finding whether the destination is reachable. - * - * Otherwise, buildPath can automatically select a closest reachable end - * pathgrid point (reachable from the closest start point). - * - * Using Tarjan's algorithm: - * - * mGraph | graph G | - * mSCCPoint | V | derived from mPoints - * mGraph[v].edges | E (for v) | - * mSCCIndex | index | tracking smallest unused index - * mSCCStack | S | - * mGraph[v].edges[i].index | w | - * - */ - void PathgridGraph::buildConnectedPoints() - { - // both of these are set to zero in the constructor - //mSCCId = 0; // how many strongly connected components in this cell - //mSCCIndex = 0; - int pointsSize = mPathgrid->mPoints.size(); - mSCCPoint.resize(pointsSize, std::pair (-1, -1)); - mSCCStack.reserve(pointsSize); - - for(int v = 0; v < static_cast (pointsSize); v++) - { - if(mSCCPoint[v].first == -1) // undefined (haven't visited) - recursiveStrongConnect(v); - } -#if 0 - std::cout << "components: " << std::to_string(mSCCId) - +", "+ mPathgrid->mCell - << std::endl; -#endif - } - - bool PathgridGraph::isPointConnected(const int start, const int end) const - { - return (mGraph[start].componentId == mGraph[end].componentId); - } - - /* - * NOTE: Based on buildPath2(), please check git history if interested - * Should consider using a 3rd party library version (e.g. boost) - * - * 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. - * - * Should be possible to make this MT safe. - * - * Returns path (a list of pathgrid point indexes) which may be empty. - * - * Input params: - * start, goal - pathgrid point indexes (for this cell) - * isExterior - used to determine whether to convert to world co-ordinates - * - * Variables: - * openset - point indexes to be traversed, lowest cost at the front - * closedset - point indexes already traversed - * gScore - past accumulated costs vector indexed by point index - * fScore - future estimated costs vector indexed by point index - * - * TODO: An intersting exercise might be to cache the paths created for a - * start/goal pair. To cache the results the paths need to be in - * pathgrid points form (currently they are converted to world - * co-ordinates). Essentially trading speed w/ memory. - */ - std::list PathgridGraph::aStarSearch(const int start, - const int goal, - bool isExterior) const - { - std::list path; - if(!isPointConnected(start, goal)) - { - return path; // there is no path, return an empty path - } - - int graphSize = mGraph.size(); - std::vector gScore; - gScore.resize(graphSize, -1); - std::vector fScore; - fScore.resize(graphSize, -1); - std::vector graphParent; - graphParent.resize(graphSize, -1); - - // gScore & fScore keep costs for each pathgrid point in mPoints - gScore[start] = 0; - fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); - - std::list openset; - std::list closedset; - openset.push_back(start); - - int current = -1; - - while(!openset.empty()) - { - current = openset.front(); // front has the lowest cost - openset.pop_front(); - - if(current == goal) - break; - - closedset.push_back(current); // remember we've been here - - // check all edges for the current point index - for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) - { - if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == - closedset.end()) - { - // not in closedset - i.e. have not traversed this edge destination - int dest = mGraph[current].edges[j].index; - float tentative_g = gScore[current] + mGraph[current].edges[j].cost; - bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); - if(!isInOpenSet - || tentative_g < gScore[dest]) - { - graphParent[dest] = current; - gScore[dest] = tentative_g; - fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], - mPathgrid->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 - std::list::iterator it = openset.begin(); - for(it = openset.begin(); it!= openset.end(); it++) - { - if(fScore[*it] > fScore[dest]) - break; - } - openset.insert(it, dest); - } - } - } // if in closedset, i.e. traversed this edge already, try the next edge - } - } - - if(current != goal) - return path; // for some reason couldn't build a path - - // reconstruct path to return, using world co-ordinates - float xCell = 0; - float yCell = 0; - if (isExterior) - { - xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; - yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; - } - - while(graphParent[current] != -1) - { - ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; - pt.mX += xCell; - pt.mY += yCell; - path.push_front(pt); - current = graphParent[current]; - } - return path; - } } - diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index fddb293d9f..eb093ad699 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -2,7 +2,6 @@ #define GAME_MWMECHANICS_PATHFINDING_H #include -#include #include #include @@ -83,65 +82,6 @@ namespace MWMechanics const ESM::Pathgrid *mPathgrid; const MWWorld::CellStore* mCell; }; - - class PathgridGraph - { - public: - PathgridGraph(); - - bool isGraphConstructed() const - { - return mIsGraphConstructed; - }; - - bool initPathgridGraph(const ESM::Cell *cell); - - // returns true if end point is strongly connected (i.e. reachable - // from start point) both start and end are pathgrid point indexes - bool isPointConnected(const int start, const int end) const; - - // isOutside is used whether to convert path to world co-ordinates - std::list aStarSearch(const int start, const int end, - const bool isOutside) const; - private: - - const ESM::Cell *mCell; - const ESM::Pathgrid *mPathgrid; - - struct ConnectedPoint // edge - { - int index; // pathgrid point index of neighbour - float cost; - }; - - struct Node // point - { - int componentId; - std::vector edges; // neighbours - }; - - // componentId is an integer indicating the groups of connected - // pathgrid points (all connected points will have the same value) - // - // In Seyda Neen there are 3: - // - // 52, 53 and 54 are one set (enclosed yard) - // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) - // all other pathgrid points are the third set - // - std::vector mGraph; - bool mIsGraphConstructed; - - // variables used to calculate connected components - int mSCCId; - int mSCCIndex; - std::vector mSCCStack; - typedef std::pair VPair; // first is index, second is lowlink - std::vector mSCCPoint; - // methods used to calculate connected components - void recursiveStrongConnect(int v); - void buildConnectedPoints(); - }; } #endif diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp new file mode 100644 index 0000000000..5580e5d3f2 --- /dev/null +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -0,0 +1,348 @@ +#include "pathgrid.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/cellstore.hpp" + +namespace +{ + // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html + // + // One of the smallest cost in Seyda Neen is between points 77 & 78: + // pt x y + // 77 = 8026, 4480 + // 78 = 7986, 4218 + // + // Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300 + // (again ignoring z). Using a value of about 300 for D seems like a reasonable + // starting point for experiments. If in doubt, just use value 1. + // + // The distance between 3 & 4 are pretty small, too. + // 3 = 5435, 223 + // 4 = 5948, 193 + // + // Approx. 514 Euclidean distance and 533 Manhattan distance. + // + float manhattan(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) + { + return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ)); + } + + // Choose a heuristics - Note that these may not be the best for directed + // graphs with non-uniform edge costs. + // + // distance: + // - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2) + // - slower but more accurate + // + // Manhattan: + // - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z| + // - faster but not the shortest path + float costAStar(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b) + { + //return distance(a, b); + return manhattan(a, b); + } +} + +namespace MWMechanics +{ + PathgridGraph::PathgridGraph() + : mCell(NULL) + , mIsGraphConstructed(false) + , mPathgrid(NULL) + , mGraph(0) + , mSCCId(0) + , mSCCIndex(0) + { + } + + /* + * mGraph is populated with the cost of each allowed edge. + * + * The data structure is based on the code in buildPath2() but modified. + * Please check git history if interested. + * + * mGraph[v].edges[i].index = w + * + * v = point index of location "from" + * i = index of edges from point v + * w = point index of location "to" + * + * + * Example: (notice from p(0) to p(2) is not allowed in this example) + * + * mGraph[0].edges[0].index = 1 + * .edges[1].index = 3 + * + * mGraph[1].edges[0].index = 0 + * .edges[1].index = 2 + * .edges[2].index = 3 + * + * mGraph[2].edges[0].index = 1 + * + * (etc, etc) + * + * + * low + * cost + * p(0) <---> p(1) <------------> p(2) + * ^ ^ + * | | + * | +-----> p(3) + * +----------------> + * high cost + */ + bool PathgridGraph::load(const ESM::Cell* cell) + { + if(!cell) + return false; + + mCell = cell; + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); + + if(!mPathgrid) + return false; + + if(mIsGraphConstructed) + return true; + + mGraph.resize(mPathgrid->mPoints.size()); + for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) + { + ConnectedPoint neighbour; + neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0], + mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]); + // forward path of the edge + neighbour.index = mPathgrid->mEdges[i].mV1; + mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour); + // reverse path of the edge + // NOTE: These are redundant, ESM already contains the required reverse paths + //neighbour.index = mPathgrid->mEdges[i].mV0; + //mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour); + } + buildConnectedPoints(); + mIsGraphConstructed = true; +#if 0 + std::cout << "loading pathgrid " << + +"\""+ mPathgrid->mCell +"\"" + +", "+ std::to_string(mPathgrid->mData.mX) + +", "+ std::to_string(mPathgrid->mData.mY) + << std::endl; +#endif + return true; + } + + // v is the pathgrid point index (some call them vertices) + void PathgridGraph::recursiveStrongConnect(int v) + { + mSCCPoint[v].first = mSCCIndex; // index + mSCCPoint[v].second = mSCCIndex; // lowlink + mSCCIndex++; + mSCCStack.push_back(v); + int w; + + for(int i = 0; i < static_cast (mGraph[v].edges.size()); i++) + { + w = mGraph[v].edges[i].index; + if(mSCCPoint[w].first == -1) // not visited + { + recursiveStrongConnect(w); // recurse + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].second); + } + else + { + if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end()) + mSCCPoint[v].second = std::min(mSCCPoint[v].second, + mSCCPoint[w].first); + } + } + + if(mSCCPoint[v].second == mSCCPoint[v].first) + { // new component + do + { + w = mSCCStack.back(); + mSCCStack.pop_back(); + mGraph[w].componentId = mSCCId; + } + while(w != v); + mSCCId++; + } + return; + } + + /* + * mGraph contains the strongly connected component group id's along + * with pre-calculated edge costs. + * + * A cell can have disjointed pathgrids, e.g. Seyda Neen has 3 + * + * mGraph for Seyda Neen will therefore have 3 different values. When + * selecting a random pathgrid point for AiWander, mGraph can be checked + * for quickly finding whether the destination is reachable. + * + * Otherwise, buildPath can automatically select a closest reachable end + * pathgrid point (reachable from the closest start point). + * + * Using Tarjan's algorithm: + * + * mGraph | graph G | + * mSCCPoint | V | derived from mPoints + * mGraph[v].edges | E (for v) | + * mSCCIndex | index | tracking smallest unused index + * mSCCStack | S | + * mGraph[v].edges[i].index | w | + * + */ + void PathgridGraph::buildConnectedPoints() + { + // both of these are set to zero in the constructor + //mSCCId = 0; // how many strongly connected components in this cell + //mSCCIndex = 0; + int pointsSize = mPathgrid->mPoints.size(); + mSCCPoint.resize(pointsSize, std::pair (-1, -1)); + mSCCStack.reserve(pointsSize); + + for(int v = 0; v < static_cast (pointsSize); v++) + { + if(mSCCPoint[v].first == -1) // undefined (haven't visited) + recursiveStrongConnect(v); + } +#if 0 + std::cout << "components: " << std::to_string(mSCCId) + +", "+ mPathgrid->mCell + << std::endl; +#endif + } + + bool PathgridGraph::isPointConnected(const int start, const int end) const + { + return (mGraph[start].componentId == mGraph[end].componentId); + } + + /* + * NOTE: Based on buildPath2(), please check git history if interested + * Should consider using a 3rd party library version (e.g. boost) + * + * 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. + * + * Should be possible to make this MT safe. + * + * Returns path (a list of pathgrid point indexes) which may be empty. + * + * Input params: + * start, goal - pathgrid point indexes (for this cell) + * isExterior - used to determine whether to convert to world co-ordinates + * + * Variables: + * openset - point indexes to be traversed, lowest cost at the front + * closedset - point indexes already traversed + * gScore - past accumulated costs vector indexed by point index + * fScore - future estimated costs vector indexed by point index + * + * TODO: An intersting exercise might be to cache the paths created for a + * start/goal pair. To cache the results the paths need to be in + * pathgrid points form (currently they are converted to world + * co-ordinates). Essentially trading speed w/ memory. + */ + std::list PathgridGraph::aStarSearch(const int start, + const int goal, + bool isExterior) const + { + std::list path; + if(!isPointConnected(start, goal)) + { + return path; // there is no path, return an empty path + } + + int graphSize = mGraph.size(); + std::vector gScore; + gScore.resize(graphSize, -1); + std::vector fScore; + fScore.resize(graphSize, -1); + std::vector graphParent; + graphParent.resize(graphSize, -1); + + // gScore & fScore keep costs for each pathgrid point in mPoints + gScore[start] = 0; + fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]); + + std::list openset; + std::list closedset; + openset.push_back(start); + + int current = -1; + + while(!openset.empty()) + { + current = openset.front(); // front has the lowest cost + openset.pop_front(); + + if(current == goal) + break; + + closedset.push_back(current); // remember we've been here + + // check all edges for the current point index + for(int j = 0; j < static_cast (mGraph[current].edges.size()); j++) + { + if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) == + closedset.end()) + { + // not in closedset - i.e. have not traversed this edge destination + int dest = mGraph[current].edges[j].index; + float tentative_g = gScore[current] + mGraph[current].edges[j].cost; + bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end(); + if(!isInOpenSet + || tentative_g < gScore[dest]) + { + graphParent[dest] = current; + gScore[dest] = tentative_g; + fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest], + mPathgrid->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 + std::list::iterator it = openset.begin(); + for(it = openset.begin(); it!= openset.end(); it++) + { + if(fScore[*it] > fScore[dest]) + break; + } + openset.insert(it, dest); + } + } + } // if in closedset, i.e. traversed this edge already, try the next edge + } + } + + if(current != goal) + return path; // for some reason couldn't build a path + + // reconstruct path to return, using world co-ordinates + float xCell = 0; + float yCell = 0; + if (isExterior) + { + xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE; + yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE; + } + + while(graphParent[current] != -1) + { + ESM::Pathgrid::Point pt = mPathgrid->mPoints[current]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + current = graphParent[current]; + } + return path; + } +} + diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp new file mode 100644 index 0000000000..9f3a997c6e --- /dev/null +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -0,0 +1,75 @@ +#ifndef GAME_MWMECHANICS_PATHGRID_H +#define GAME_MWMECHANICS_PATHGRID_H + +#include +#include + +namespace ESM +{ + class Cell; +} + +namespace MWWorld +{ + class CellStore; +} + +namespace MWMechanics +{ + class PathgridGraph + { + public: + PathgridGraph(); + + bool load(const ESM::Cell *cell); + + // returns true if end point is strongly connected (i.e. reachable + // from start point) both start and end are pathgrid point indexes + bool isPointConnected(const int start, const int end) const; + + // isOutside is used whether to convert path to world co-ordinates + std::list aStarSearch(const int start, + const int end, + const bool isOutside) const; + private: + + const ESM::Cell *mCell; + const ESM::Pathgrid *mPathgrid; + + struct ConnectedPoint // edge + { + int index; // pathgrid point index of neighbour + float cost; + }; + + struct Node // point + { + int componentId; + std::vector edges; // neighbours + }; + + // componentId is an integer indicating the groups of connected + // pathgrid points (all connected points will have the same value) + // + // In Seyda Neen there are 3: + // + // 52, 53 and 54 are one set (enclosed yard) + // 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office) + // all other pathgrid points are the third set + // + std::vector mGraph; + bool mIsGraphConstructed; + + // variables used to calculate connected components + int mSCCId; + int mSCCIndex; + std::vector mSCCStack; + typedef std::pair VPair; // first is index, second is lowlink + std::vector mSCCPoint; + // methods used to calculate connected components + void recursiveStrongConnect(int v); + void buildConnectedPoints(); + }; +} + +#endif diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index dd105a665d..d39f22c3fb 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -363,6 +363,7 @@ namespace MWWorld loadRefs (store, esm); mState = State_Loaded; + mPathgridGraph.load(mCell); } } @@ -683,14 +684,6 @@ namespace MWWorld bool CellStore::isPointConnected(const int start, const int end) const { - if(!mPathgridGraph.isGraphConstructed()) - { - // Ugh... there must be a better way... - MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); - - if(!p->initPathgridGraph(mCell)) - return false; - } return mPathgridGraph.isPointConnected(start, end); } @@ -698,16 +691,6 @@ namespace MWWorld std::list CellStore::aStarSearch(const int start, const int end, const bool isOutside) const { - if(!mPathgridGraph.isGraphConstructed()) - { - MWMechanics::PathgridGraph *p = const_cast (&mPathgridGraph); - - if(!p->initPathgridGraph(mCell)) - { - std::list path; // empty - return path; - } - } return mPathgridGraph.aStarSearch(start, end, isOutside); } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 3cca993f27..c352779b16 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -8,7 +8,7 @@ #include "esmstore.hpp" #include "cellreflist.hpp" -#include "../mwmechanics/pathfinding.hpp" +#include "../mwmechanics/pathgrid.hpp" namespace ESM {