1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-11-25 09:44:02 +00:00

Update JPS.h & fix typos in incremental pathfinding Lua API that prevented things from ever working

This commit is contained in:
fgenesis 2016-04-07 13:05:57 +02:00
parent 361915947b
commit a88ca0a90d
3 changed files with 208 additions and 51 deletions

View file

@ -217,11 +217,10 @@ void PathFinding::beginFindPath(PathFinding::State *state, const Vector& start,
bool PathFinding::updateFindPath(PathFinding::State *state, int limit)
{
int oldres = state->result;
if(oldres == JPS::NEED_MORE_STEPS)
if(state->result == JPS::NEED_MORE_STEPS)
{
state->result = state->searcher.findPathStep(limit);
return oldres != state->result;
return state->result != JPS::NEED_MORE_STEPS;
}
return true; // done
}
@ -229,7 +228,7 @@ bool PathFinding::updateFindPath(PathFinding::State *state, int limit)
bool PathFinding::finishFindPath(PathFinding::State *state, VectorPath& path, unsigned step /* = 0 */)
{
if(state->result != JPS::FOUND_PATH)
return false;
return state->result == JPS::EMPTY_PATH;
JPS::PathVector rawpath;
state->searcher.findPathFinish(rawpath, step);

View file

@ -8996,9 +8996,9 @@ luaFunc(createFindPath)
luaFunc(findPathBegin)
{
PathFinding::State *state = *(PathFinding::State**)luaL_checkudata(L, 1, "pathfinder");
Vector start(lua_tonumber(L, 1), lua_tonumber(L, 2));
Vector end(lua_tonumber(L, 3), lua_tonumber(L, 4));
ObsType obs = ObsType(lua_tointeger(L, 8));
Vector start(lua_tonumber(L, 2), lua_tonumber(L, 3));
Vector end(lua_tonumber(L, 4), lua_tonumber(L, 5));
ObsType obs = ObsType(lua_tointeger(L, 6));
PathFinding::beginFindPath(state, start, end, obs);
luaReturnNil();
}
@ -9015,12 +9015,12 @@ luaFunc(findPathFinish)
{
PathFinding::State *state = *(PathFinding::State**)luaL_checkudata(L, 1, "pathfinder");
VectorPath path;
bool found = PathFinding::finishFindPath(state, path);
bool found = PathFinding::finishFindPath(state, path, lua_tointeger(L, 2));
if(!found)
luaReturnBool(false);
lua_pushinteger(L, (int)path.getNumPathNodes());
_fillPathfindTables(L, path, 2, 3);
_fillPathfindTables(L, path, 3, 4);
return 3;
}
@ -9041,20 +9041,38 @@ luaFunc(castLine)
int tiletype = lua_tointeger(L, 5);
if(!tiletype)
tiletype = OT_BLOCKING;
bool invert = getBool(L, 6);
Vector step = end - v;
int steps = step.getLength2D() / TILE_SIZE;
step.setLength2D(TILE_SIZE);
for(int i = 0; i < steps; ++i)
if(!invert)
{
if(dsq->game->getGridRaw(TileVector(v)) & tiletype)
for(int i = 0; i < steps; ++i)
{
lua_pushinteger(L, dsq->game->getGrid(TileVector(v)));
lua_pushnumber(L, v.x);
lua_pushnumber(L, v.y);
return 3;
if(dsq->game->getGridRaw(TileVector(v)) & tiletype)
{
lua_pushinteger(L, dsq->game->getGrid(TileVector(v)));
lua_pushnumber(L, v.x);
lua_pushnumber(L, v.y);
return 3;
}
v += step;
}
}
else
{
for(int i = 0; i < steps; ++i)
{
if(!(dsq->game->getGridRaw(TileVector(v)) & tiletype))
{
lua_pushinteger(L, dsq->game->getGrid(TileVector(v)));
lua_pushnumber(L, v.x);
lua_pushnumber(L, v.y);
return 3;
}
v += step;
}
v += step;
}
lua_pushboolean(L, false);

View file

@ -18,6 +18,13 @@
// If this is defined, compare all jumps against recursive reference implementation (only if _DEBUG is defined)
//#define JPS_VERIFY
// If this is defined, use standard A* instead of JPS (e.g. if you want to compare performance in your scenario)
//#define JPS_ASTAR_ONLY
// If this is defined, disable the greedy direct-short-path check that avoids the large area scanning that JPS does.
// Does not change optimality of results when left enabled
//#define JPS_DISABLE_GREEDY
// ============================
// Usage:
@ -58,11 +65,12 @@ while(true)
// ..stuff happening ...
// build path incrementally from waypoints
JPS::Position a, b, c, d; // some waypoints
JPS::Position a, b, c, d = <...>; // set some waypoints
search.findPath(path, a, b);
search.findPath(path, b, c);
search.findPath(path, c, d);
// re-use existing pathfinder instance
if(!search.findPath(path2, JPS::Pos(startx, starty), JPS::Pos(endx, endy), step))
{
// ...handle failure...
@ -70,7 +78,7 @@ while(true)
// ... more stuff happening ...
// At convenient times, you can clean up accumulated nodes to reclaim memory.
// This is never necessary, but performance will drop if too many cached nodes exist.
// This is never necessary for correct function, but performance will drop if too many cached nodes exist.
if(mapWasReloaded)
search.freeMemory();
}
@ -81,9 +89,11 @@ while(true)
// --- Incremental pathfinding ---
// -------------------------------
First, call findPathInit(Position start, Position end). Don't forget to check the return value,
as it may return NO_PATH if one or both of the points are obstructed,
or FOUND_PATH if the points are equal and not obstructed.
First, call findPathInit(Position start, Position end).
Don't forget to check the return value, as it may return:
- NO_PATH if one or both of the points are obstructed
- EMPTY_PATH if the points are equal and not obstructed
- FOUND_PATH if the initial greedy heuristic could find a path quickly.
If it returns NEED_MORE_STEPS then the next part can start.
Repeatedly call findPathStep(int limit) until it returns NO_PATH or FOUND_PATH.
@ -103,9 +113,10 @@ Returns false if the pathfinding did not finish or generating the path failed.
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <map>
#include <cmath>
#include <map>
#ifdef _DEBUG
#include <cassert>
#define JPS_ASSERT(cond) assert(cond)
@ -118,9 +129,10 @@ namespace JPS {
enum Result
{
NO_PATH = 0,
FOUND_PATH = 1,
NEED_MORE_STEPS = 2
NO_PATH,
FOUND_PATH,
NEED_MORE_STEPS,
EMPTY_PATH
};
struct Position
@ -170,12 +182,12 @@ public:
inline void setOpen() { flags |= 1; }
inline void setClosed() { flags |= 2; }
inline unsigned char isOpen() const { return flags & 1; }
inline unsigned char isClosed() const { return flags & 2; }
inline unsigned isOpen() const { return flags & 1; }
inline unsigned isClosed() const { return flags & 2; }
inline void clearState() { f = 0; g = 0, parent = 0; flags = 0; }
private:
unsigned char flags;
unsigned flags;
bool operator==(const Node& o); // not implemented, nodes should not be compared
};
@ -224,7 +236,7 @@ public:
{
nodes.clear();
}
inline void fixup(const Node *item)
inline void fixup()
{
std::make_heap(nodes.begin(), nodes.end(), _compare);
}
@ -245,12 +257,12 @@ public:
{}
// single-call
bool findPath(PathVector& path, Position start, Position end, unsigned step = 0);
bool findPath(PathVector& path, Position start, Position end, unsigned step);
// incremental pathfinding
Result findPathInit(Position start, Position end);
Result findPathStep(int limit);
bool findPathFinish(PathVector& path, unsigned step = 0);
bool findPathFinish(PathVector& path, unsigned step);
// misc
void freeMemory();
@ -273,12 +285,20 @@ private:
Node *getNode(const Position& pos);
void identifySuccessors(const Node *n);
bool generatePath(PathVector& path, unsigned step) const;
#ifndef JPS_DISABLE_GREEDY
bool findPathGreedy(Node *start);
#endif
#ifdef JPS_ASTAR_ONLY
unsigned findNeighborsAStar(const Node *n, Position *wptr);
#else
unsigned findNeighbors(const Node *n, Position *wptr) const;
Position jumpP(const Position& p, const Position& src);
Position jumpD(Position p, int dx, int dy);
Position jumpX(Position p, int dx);
Position jumpY(Position p, int dy);
bool generatePath(PathVector& path, unsigned step) const;
#endif
#ifdef JPS_VERIFY
Position jumpPRec(const Position& p, const Position& src) const;
#endif
@ -290,6 +310,7 @@ template <typename GRID> inline Node *Searcher<GRID>::getNode(const Position& po
return &nodegrid.insert(std::make_pair(pos, Node(pos))).first->second;
}
#ifndef JPS_ASTAR_ONLY
template <typename GRID> Position Searcher<GRID>::jumpP(const Position &p, const Position& src)
{
JPS_ASSERT(grid(p.x, p.y));
@ -423,6 +444,7 @@ template <typename GRID> inline Position Searcher<GRID>::jumpY(Position p, int d
stepsRemain -= steps;
return p;
}
#endif // JPS_ASTAR_ONLY
#ifdef JPS_VERIFY
// Recursive reference implementation -- for comparison only
@ -468,19 +490,21 @@ template <typename GRID> Position Searcher<GRID>::jumpPRec(const Position& p, co
return npos;
}
#endif
template <typename GRID> unsigned Searcher<GRID>::findNeighbors(const Node *n, Position *wptr) const
{
Position *w = wptr;
const unsigned x = n->pos.x;
const unsigned y = n->pos.y;
#endif // JPS_VERIFY
#define JPS_CHECKGRID(dx, dy) (grid(x+(dx), y+(dy)))
#define JPS_ADDPOS(dx, dy) do { *w++ = Pos(x+(dx), y+(dy)); } while(0)
#define JPS_ADDPOS_CHECK(dx, dy) do { if(JPS_CHECKGRID(dx, dy)) JPS_ADDPOS(dx, dy); } while(0)
#define JPS_ADDPOS_NO_TUNNEL(dx, dy) do { if(grid(x+(dx),y) || grid(x,y+(dy))) JPS_ADDPOS_CHECK(dx, dy); } while(0)
#ifndef JPS_ASTAR_ONLY
template <typename GRID> unsigned Searcher<GRID>::findNeighbors(const Node *n, Position *wptr) const
{
Position *w = wptr;
const unsigned x = n->pos.x;
const unsigned y = n->pos.y;
const int skip = this->skip;
if(!n->parent)
{
// straight moves
@ -557,28 +581,58 @@ template <typename GRID> unsigned Searcher<GRID>::findNeighbors(const Node *n, P
JPS_ADDPOS_CHECK(-skip,dy);
}
}
return unsigned(w - wptr);
}
#else
//-------------- Plain old A* search ----------------
template <typename GRID> unsigned Searcher<GRID>::findNeighborsAStar(const Node *n, Position *wptr)
{
Position *w = wptr;
const int x = n->pos.x;
const int y = n->pos.y;
const int d = skip;
JPS_ADDPOS_NO_TUNNEL(-d, -d);
JPS_ADDPOS_CHECK ( 0, -d);
JPS_ADDPOS_NO_TUNNEL(+d, -d);
JPS_ADDPOS_CHECK (-d, 0);
JPS_ADDPOS_CHECK (+d, 0);
JPS_ADDPOS_NO_TUNNEL(-d, +d);
JPS_ADDPOS_CHECK ( 0, +d);
JPS_ADDPOS_NO_TUNNEL(+d, +d);
stepsDone += 8;
return unsigned(w - wptr);
}
#endif // JPS_ASTAR_ONLY
//-------------------------------------------------
#undef JPS_ADDPOS
#undef JPS_ADDPOS_CHECK
#undef JPS_ADDPOS_NO_TUNNEL
#undef JPS_CHECKGRID
return unsigned(w - wptr);
}
template <typename GRID> void Searcher<GRID>::identifySuccessors(const Node *n)
{
Position buf[8];
#ifdef JPS_ASTAR_ONLY
const int num = findNeighborsAStar(n, &buf[0]);
#else
const int num = findNeighbors(n, &buf[0]);
#endif
for(int i = num-1; i >= 0; --i)
{
// Invariant: A node is only a valid neighbor if the corresponding grid position is walkable (asserted in jumpP)
#ifdef JPS_ASTAR_ONLY
Position jp = buf[i];
#else
Position jp = jumpP(buf[i], n->pos);
#ifdef JPS_VERIFY
#ifdef JPS_VERIFY
JPS_ASSERT(jp == jumpPRec(buf[i], n->pos));
#endif
#endif
if(!jp.isValid())
continue;
#endif
// Now that the grid position is definitely a valid jump point, we have to create the actual node.
Node *jn = getNode(jp);
JPS_ASSERT(jn && jn != n);
@ -597,7 +651,7 @@ template <typename GRID> void Searcher<GRID>::identifySuccessors(const Node *n)
jn->setOpen();
}
else
open.fixup(jn);
open.fixup();
}
}
}
@ -644,6 +698,7 @@ template <typename GRID> bool Searcher<GRID>::generatePath(PathVector& path, uns
return false;
do
{
JPS_ASSERT(next != next->parent);
path.push_back(next->pos);
next = next->parent;
}
@ -653,12 +708,12 @@ template <typename GRID> bool Searcher<GRID>::generatePath(PathVector& path, uns
return true;
}
template <typename GRID> bool Searcher<GRID>::findPath(PathVector& path, Position start, Position end, unsigned step /* = 0 */)
template <typename GRID> bool Searcher<GRID>::findPath(PathVector& path, Position start, Position end, unsigned step)
{
Result res = findPathInit(start, end);
// If this is true, the resulting path is empty (findPathFinish() would fail, so this needs to be checked before)
if(res == FOUND_PATH)
if(res == EMPTY_PATH)
return true;
while(true)
@ -697,16 +752,24 @@ template <typename GRID> Result Searcher<GRID>::findPathInit(Position start, Pos
{
// There is only a path if this single position is walkable.
// But since the starting position is omitted, there is nothing to do here.
return grid(end.x, end.y) ? FOUND_PATH : NO_PATH;
return grid(end.x, end.y) ? EMPTY_PATH : NO_PATH;
}
// If start or end point are obstructed, don't even start
if(!grid(start.x, start.y) || !grid(end.x, end.y))
return NO_PATH;
open.push(getNode(start));
endNode = getNode(end);
JPS_ASSERT(endNode);
Node *startNode = getNode(start);
JPS_ASSERT(startNode && endNode);
#ifndef JPS_DISABLE_GREEDY
// Try the quick way out first
if(findPathGreedy(startNode))
return FOUND_PATH;
#endif
open.push(startNode);
return NEED_MORE_STEPS;
}
@ -728,11 +791,88 @@ template <typename GRID> Result Searcher<GRID>::findPathStep(int limit)
return NEED_MORE_STEPS;
}
template<typename GRID> bool Searcher<GRID>::findPathFinish(PathVector& path, unsigned step /* = 0 */)
template<typename GRID> bool Searcher<GRID>::findPathFinish(PathVector& path, unsigned step)
{
return generatePath(path, step);
}
#ifndef JPS_DISABLE_GREEDY
template<typename GRID> bool Searcher<GRID>::findPathGreedy(Node *n)
{
Position midpos = npos;
int x = n->pos.x;
int y = n->pos.y;
int ex = endNode->pos.x;
int ey = endNode->pos.y;
JPS_ASSERT(x != ex || y != ey); // must not be called when start==end
JPS_ASSERT(n != endNode);
const int skip = this->skip;
int dx = int(ex - x);
int dy = int(ey - y);
const int adx = abs(dx);
const int ady = abs(dy);
dx /= std::max(adx, 1);
dy /= std::max(ady, 1);
dx *= skip;
dy *= skip;
// go diagonally first
if(x != ex && y != ey)
{
JPS_ASSERT(dx && dy);
const int minlen = (int)std::min(adx, ady);
const int tx = x + dx * minlen;
for( ; x != tx; )
{
if(grid(x, y) && (grid(x+dx, y) || grid(x, y+dy))) // prevent tunneling as well
{
x += dx;
y += dy;
}
else
return false;
}
if(!grid(x, y))
return false;
midpos = Pos(x, y);
}
// at this point, we're aligned to at least one axis
JPS_ASSERT(x == ex || y == ey);
if(!(x == ex && y == ey))
{
while(x != ex)
if(!grid(x += dx, y))
return false;
while(y != ey)
if(!grid(x, y += dy))
return false;
JPS_ASSERT(x == ex && y == ey);
}
if(midpos.isValid())
{
Node *mid = getNode(midpos);
JPS_ASSERT(mid && mid != n);
mid->parent = n;
if(mid != endNode)
endNode->parent = mid;
}
else
endNode->parent = n;
return true;
}
#endif
template<typename GRID> void Searcher<GRID>::freeMemory()
{
NodeGrid v;