mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-07 18:31:37 +00:00
862 lines
19 KiB
C++
862 lines
19 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
//=============================================================================
|
|
//
|
|
// jump point search grid navigation with navpoint refinement
|
|
// (c) 2018 Martin Sedlak
|
|
//
|
|
//=============================================================================
|
|
|
|
#include "ags/lib/std/queue.h"
|
|
#include "ags/lib/std/vector.h"
|
|
#include "ags/lib/std/algorithm.h"
|
|
#include "ags/lib/std/functional.h"
|
|
#include "ags/lib/std/math.h"
|
|
#include "ags/lib/std/xutility.h"
|
|
|
|
namespace AGS3 {
|
|
|
|
// TODO: this could be cleaned up/simplified ...
|
|
|
|
// further optimizations possible:
|
|
// - forward refinement should use binary search
|
|
|
|
class Navigation {
|
|
public:
|
|
Navigation();
|
|
|
|
void Resize(int width, int height);
|
|
|
|
enum NavResult {
|
|
// unreachable
|
|
NAV_UNREACHABLE,
|
|
// straight line exists
|
|
NAV_STRAIGHT,
|
|
// path used
|
|
NAV_PATH
|
|
};
|
|
|
|
// ncpath = navpoint-compressed path
|
|
// opath = path composed of individual grid elements
|
|
NavResult NavigateRefined(int sx, int sy, int ex, int ey, std::vector<int> &opath,
|
|
std::vector<int> &ncpath);
|
|
|
|
NavResult Navigate(int sx, int sy, int ex, int ey, std::vector<int> &opath);
|
|
|
|
bool TraceLine(int srcx, int srcy, int targx, int targy, int &lastValidX, int &lastValidY) const;
|
|
bool TraceLine(int srcx, int srcy, int targx, int targy, std::vector<int> *rpath = nullptr) const;
|
|
|
|
inline void SetMapRow(int y, const unsigned char *row) {
|
|
map[y] = row;
|
|
}
|
|
|
|
inline static int PackSquare(int x, int y);
|
|
inline static void UnpackSquare(int sq, int &x, int &y);
|
|
|
|
private:
|
|
// priority queue entry
|
|
struct Entry {
|
|
float cost;
|
|
int index;
|
|
|
|
inline Entry() {}
|
|
|
|
inline Entry(float ncost, int nindex)
|
|
: cost(ncost)
|
|
, index(nindex) {
|
|
}
|
|
|
|
inline bool operator <(const Entry &b) const {
|
|
return cost < b.cost;
|
|
}
|
|
|
|
inline bool operator >(const Entry &b) const {
|
|
return cost > b.cost;
|
|
}
|
|
};
|
|
|
|
int mapWidth;
|
|
int mapHeight;
|
|
std::vector<const unsigned char *> map;
|
|
|
|
typedef unsigned short tFrameId;
|
|
typedef int tPrev;
|
|
|
|
struct NodeInfo {
|
|
// quantized min distance from origin
|
|
unsigned short dist;
|
|
// frame id (counter to detect new search)
|
|
tFrameId frameId;
|
|
// previous node index (packed, relative to current node)
|
|
tPrev prev;
|
|
|
|
inline NodeInfo()
|
|
: dist(0)
|
|
, frameId(0)
|
|
, prev(-1) {
|
|
}
|
|
};
|
|
|
|
static const float DIST_SCALE_PACK;
|
|
static const float DIST_SCALE_UNPACK;
|
|
|
|
std::vector<NodeInfo> mapNodes;
|
|
tFrameId frameId;
|
|
|
|
std::priority_queue<Entry, std::vector<Entry>, Common::Less<Entry> > pq;
|
|
|
|
// temporary buffers:
|
|
mutable std::vector<int> fpath;
|
|
std::vector<int> ncpathIndex;
|
|
std::vector<int> rayPath, orayPath;
|
|
|
|
// temps for routing towards unreachable areas
|
|
int cnode;
|
|
int closest;
|
|
|
|
// orthogonal only (this should correspond to what AGS is doing)
|
|
bool nodiag;
|
|
|
|
bool navLock;
|
|
|
|
void IncFrameId();
|
|
|
|
// outside map test
|
|
inline bool Outside(int x, int y) const;
|
|
// stronger inside test
|
|
bool Passable(int x, int y) const;
|
|
// plain access, unchecked
|
|
inline bool Walkable(int x, int y) const;
|
|
|
|
void AddPruned(int *buf, int &bcount, int x, int y) const;
|
|
bool HasForcedNeighbor(int x, int y, int dx, int dy) const;
|
|
int FindJump(int x, int y, int dx, int dy, int ex, int ey);
|
|
int FindOrthoJump(int x, int y, int dx, int dy, int ex, int ey);
|
|
|
|
// neighbor reachable (nodiag only)
|
|
bool Reachable(int x0, int y0, int x1, int y1) const;
|
|
|
|
static inline int sign(int n) {
|
|
return n < 0 ? -1 : (n > 0 ? 1 : 0);
|
|
}
|
|
|
|
static inline int iabs(int n) {
|
|
return n < 0 ? -n : n;
|
|
}
|
|
|
|
static inline int iclamp(int v, int min, int max) {
|
|
return v < min ? min : (v > max ? max : v);
|
|
}
|
|
|
|
static inline int ClosestDist(int dx, int dy) {
|
|
return dx * dx + dy * dy;
|
|
// Manhattan?
|
|
//return iabs(dx) + iabs(dy);
|
|
}
|
|
};
|
|
|
|
// Navigation
|
|
|
|
// scale pack of 2 means we can route up to 32767 units (euclidean distance) from starting point
|
|
// this means that the maximum routing bitmap size we can handle is 23169x23169; should be more than enough!
|
|
const float Navigation::DIST_SCALE_PACK = 2.0f;
|
|
const float Navigation::DIST_SCALE_UNPACK = 1.0f / Navigation::DIST_SCALE_PACK;
|
|
|
|
Navigation::Navigation()
|
|
: mapWidth(0)
|
|
, mapHeight(0)
|
|
, frameId(1)
|
|
, cnode(0)
|
|
, closest(0)
|
|
// no diagonal route - this should correspond to what AGS does
|
|
, nodiag(true)
|
|
, navLock(false) {
|
|
}
|
|
|
|
void Navigation::Resize(int width, int height) {
|
|
mapWidth = width;
|
|
mapHeight = height;
|
|
|
|
int size = mapWidth * mapHeight;
|
|
|
|
map.resize(mapHeight);
|
|
mapNodes.resize(size);
|
|
}
|
|
|
|
void Navigation::IncFrameId() {
|
|
if (++frameId == 0) {
|
|
for (int i = 0; i < (int)mapNodes.size(); i++)
|
|
mapNodes[i].frameId = 0;
|
|
|
|
frameId = 1;
|
|
}
|
|
}
|
|
|
|
inline int Navigation::PackSquare(int x, int y) {
|
|
return (y << 16) + x;
|
|
}
|
|
|
|
inline void Navigation::UnpackSquare(int sq, int &x, int &y) {
|
|
y = sq >> 16;
|
|
x = sq & ((1 << 16) - 1);
|
|
}
|
|
|
|
inline bool Navigation::Outside(int x, int y) const {
|
|
return
|
|
(unsigned)x >= (unsigned)mapWidth ||
|
|
(unsigned)y >= (unsigned)mapHeight;
|
|
}
|
|
|
|
inline bool Navigation::Walkable(int x, int y) const {
|
|
// invert condition because of AGS
|
|
return map[y][x] != 0;
|
|
}
|
|
|
|
bool Navigation::Passable(int x, int y) const {
|
|
return !Outside(x, y) && Walkable(x, y);
|
|
}
|
|
|
|
bool Navigation::Reachable(int x0, int y0, int x1, int y1) const {
|
|
assert(nodiag);
|
|
|
|
return Passable(x1, y1) &&
|
|
(Passable(x1, y0) || Passable(x0, y1));
|
|
}
|
|
|
|
// A* using jump point search (JPS)
|
|
// reference: Online Graph Pruning for Pathfinding on Grid Maps
|
|
// http://users.cecs.anu.edu.au/~dharabor/data/papers/harabor-grastien-aaai11.pdf
|
|
void Navigation::AddPruned(int *buf, int &bcount, int x, int y) const {
|
|
assert(buf && bcount < 8);
|
|
|
|
if (Passable(x, y))
|
|
buf[bcount++] = PackSquare(x, y);
|
|
}
|
|
|
|
bool Navigation::HasForcedNeighbor(int x, int y, int dx, int dy) const {
|
|
if (!dy) {
|
|
return (!Passable(x, y - 1) && Passable(x + dx, y - 1)) ||
|
|
(!Passable(x, y + 1) && Passable(x + dx, y + 1));
|
|
}
|
|
|
|
if (!dx) {
|
|
return (!Passable(x - 1, y) && Passable(x - 1, y + dy)) ||
|
|
(!Passable(x + 1, y) && Passable(x + 1, y + dy));
|
|
}
|
|
|
|
return
|
|
(!Passable(x - dx, y) && Passable(x - dx, y + dy)) ||
|
|
(!Passable(x, y - dy) && Passable(x + dx, y - dy));
|
|
}
|
|
|
|
int Navigation::FindOrthoJump(int x, int y, int dx, int dy, int ex, int ey) {
|
|
assert((!dx || !dy) && (dx || dy));
|
|
|
|
for (;;) {
|
|
x += dx;
|
|
y += dy;
|
|
|
|
if (!Passable(x, y))
|
|
break;
|
|
|
|
int edx = x - ex;
|
|
int edy = y - ey;
|
|
int edist = ClosestDist(edx, edy);
|
|
|
|
if (edist < closest) {
|
|
closest = edist;
|
|
cnode = PackSquare(x, y);
|
|
}
|
|
|
|
if ((x == ex && y == ey) || HasForcedNeighbor(x, y, dx, dy))
|
|
return PackSquare(x, y);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int Navigation::FindJump(int x, int y, int dx, int dy, int ex, int ey) {
|
|
if (!(dx && dy))
|
|
return FindOrthoJump(x, y, dx, dy, ex, ey);
|
|
|
|
if (nodiag && !Reachable(x, y, x + dx, y + dy))
|
|
return -1;
|
|
|
|
x += dx;
|
|
y += dy;
|
|
|
|
if (!Passable(x, y))
|
|
return -1;
|
|
|
|
int edx = x - ex;
|
|
int edy = y - ey;
|
|
int edist = ClosestDist(edx, edy);
|
|
|
|
if (edist < closest) {
|
|
closest = edist;
|
|
cnode = PackSquare(x, y);
|
|
}
|
|
|
|
if ((x == ex && y == ey) || HasForcedNeighbor(x, y, dx, dy))
|
|
return PackSquare(x, y);
|
|
|
|
if (dx && dy) {
|
|
if (FindOrthoJump(x, y, dx, 0, ex, ey) ||
|
|
FindOrthoJump(x, y, 0, dy, ex, ey))
|
|
return PackSquare(x, y);
|
|
}
|
|
|
|
return nodiag ? -1 : FindJump(x, y, dx, dy, ex, ey);
|
|
}
|
|
|
|
Navigation::NavResult Navigation::Navigate(int sx, int sy, int ex, int ey, std::vector<int> &opath) {
|
|
IncFrameId();
|
|
|
|
if (!Passable(sx, sy)) {
|
|
opath.clear();
|
|
return NAV_UNREACHABLE;
|
|
}
|
|
|
|
// try ray first, if reachable, no need for A* at all
|
|
if (!TraceLine(sx, sy, ex, ey, &opath))
|
|
return NAV_STRAIGHT;
|
|
|
|
{
|
|
NodeInfo &node = mapNodes[sy * mapWidth + sx];
|
|
node.dist = 0;
|
|
node.frameId = frameId;
|
|
node.prev = -1;
|
|
}
|
|
|
|
closest = 0x7fffffff;
|
|
cnode = PackSquare(sx, sy);
|
|
|
|
// no clear for priority queue, like, really?!
|
|
while (!pq.empty())
|
|
pq.pop();
|
|
|
|
pq.push(Entry(0.0, cnode));
|
|
|
|
while (!pq.empty()) {
|
|
Entry e = pq.top();
|
|
pq.pop();
|
|
|
|
int x, y;
|
|
UnpackSquare(e.index, x, y);
|
|
|
|
{
|
|
int edx = x - ex;
|
|
int edy = y - ey;
|
|
int edist = ClosestDist(edx, edy);
|
|
|
|
if (edist < closest) {
|
|
closest = edist;
|
|
cnode = e.index;
|
|
}
|
|
}
|
|
|
|
if (x == ex && y == ey) {
|
|
// done
|
|
break;
|
|
}
|
|
|
|
float dist;
|
|
int prev;
|
|
|
|
{
|
|
const NodeInfo &node = mapNodes[y * mapWidth + x];
|
|
dist = node.dist * DIST_SCALE_UNPACK;
|
|
prev = node.prev;
|
|
}
|
|
|
|
int pneig[8];
|
|
int ncount = 0;
|
|
|
|
if (prev < 0) {
|
|
for (int ny = y - 1; ny <= y + 1; ny++) {
|
|
if ((unsigned)ny >= (unsigned)mapHeight)
|
|
continue;
|
|
|
|
for (int nx = x - 1; nx <= x + 1; nx++) {
|
|
if (nx == x && ny == y)
|
|
continue;
|
|
|
|
if ((unsigned)nx >= (unsigned)mapWidth)
|
|
continue;
|
|
|
|
if (!Walkable(nx, ny))
|
|
continue;
|
|
|
|
if (nodiag && !Reachable(x, y, nx, ny))
|
|
continue;
|
|
|
|
pneig[ncount++] = PackSquare(nx, ny);
|
|
}
|
|
}
|
|
} else {
|
|
// filter
|
|
int px, py;
|
|
UnpackSquare(prev, px, py);
|
|
int dx = sign(x - px);
|
|
int dy = sign(y - py);
|
|
assert(dx || dy);
|
|
|
|
if (!dy) {
|
|
AddPruned(pneig, ncount, x + dx, y);
|
|
|
|
// add corners
|
|
if (!nodiag || Passable(x + dx, y)) {
|
|
if (!Passable(x, y + 1))
|
|
AddPruned(pneig, ncount, x + dx, y + 1);
|
|
|
|
if (!Passable(x, y - 1))
|
|
AddPruned(pneig, ncount, x + dx, y - 1);
|
|
}
|
|
} else if (!dx) {
|
|
// same as above but transposed
|
|
AddPruned(pneig, ncount, x, y + dy);
|
|
|
|
// add corners
|
|
if (!nodiag || Passable(x, y + dy)) {
|
|
if (!Passable(x + 1, y))
|
|
AddPruned(pneig, ncount, x + 1, y + dy);
|
|
|
|
if (!Passable(x - 1, y))
|
|
AddPruned(pneig, ncount, x - 1, y + dy);
|
|
}
|
|
} else {
|
|
// diagonal case
|
|
AddPruned(pneig, ncount, x, y + dy);
|
|
AddPruned(pneig, ncount, x + dx, y);
|
|
|
|
if (!nodiag || Reachable(x, y, x + dx, y + dy))
|
|
AddPruned(pneig, ncount, x + dx, y + dy);
|
|
|
|
if (!Passable(x - dx, y) &&
|
|
(nodiag || Reachable(x, y, x - dx, y + dy)))
|
|
AddPruned(pneig, ncount, x - dx, y + dy);
|
|
|
|
if (!Passable(x, y - dy) &&
|
|
(nodiag || Reachable(x, y, x + dx, y - dy)))
|
|
AddPruned(pneig, ncount, x + dx, y - dy);
|
|
}
|
|
}
|
|
|
|
// sort by heuristics
|
|
Entry sort[8];
|
|
|
|
for (int ni = 0; ni < ncount; ni++) {
|
|
int nx, ny;
|
|
UnpackSquare(pneig[ni], nx, ny);
|
|
float edx = (float)(nx - ex);
|
|
float edy = (float)(ny - ey);
|
|
sort[ni].cost = sqrt(edx * edx + edy * edy);
|
|
sort[ni].index = pneig[ni];
|
|
}
|
|
|
|
std::sort(sort, sort + ncount);
|
|
|
|
int succ[8];
|
|
int nsucc = 0;
|
|
|
|
for (int ni = 0; ni < ncount; ni++)
|
|
pneig[ni] = sort[ni].index;
|
|
|
|
for (int ni = 0; ni < ncount; ni++) {
|
|
int nx, ny;
|
|
UnpackSquare(pneig[ni], nx, ny);
|
|
|
|
int dx = nx - x;
|
|
int dy = ny - y;
|
|
int j = FindJump(x, y, dx, dy, ex, ey);
|
|
|
|
if (j < 0)
|
|
continue;
|
|
|
|
succ[nsucc++] = j;
|
|
}
|
|
|
|
for (int ni = 0; ni < nsucc; ni++) {
|
|
int nx, ny;
|
|
UnpackSquare(succ[ni], nx, ny);
|
|
assert(Walkable(nx, ny));
|
|
|
|
NodeInfo &node = mapNodes[ny * mapWidth + nx];
|
|
|
|
float ndist = node.frameId != frameId ? INFINITY : node.dist * DIST_SCALE_UNPACK;
|
|
|
|
float dx = (float)(nx - x);
|
|
float dy = (float)(ny - y);
|
|
// FIXME: can do better here
|
|
float cost = sqrt(dx * dx + dy * dy);
|
|
float ecost = dist + cost;
|
|
|
|
float edx = (float)(nx - ex);
|
|
float edy = (float)(ny - ey);
|
|
float heur = sqrt(edx * edx + edy * edy);
|
|
|
|
if (ecost < ndist) {
|
|
ecost *= DIST_SCALE_PACK;
|
|
|
|
// assert because we use 16-bit quantized min distance from start to save memory
|
|
assert(ecost <= 65535.0f && "distance from start too large");
|
|
|
|
if (ecost > 65535.0f)
|
|
continue;
|
|
|
|
node.dist = (unsigned short)(ecost + 0.5f);
|
|
node.frameId = frameId;
|
|
node.prev = PackSquare(x, y);
|
|
pq.push(Entry(ecost + heur, PackSquare(nx, ny)));
|
|
}
|
|
}
|
|
}
|
|
|
|
opath.clear();
|
|
|
|
// now since we allow approx routing even if dst
|
|
// isn't directly reachable
|
|
// note: not sure if this provides optimal results even if we update
|
|
// cnode during jump search
|
|
int nex, ney;
|
|
UnpackSquare(cnode, nex, ney);
|
|
|
|
if ((nex != sx || ney != sy) && (nex != ex || ney != ey)) {
|
|
// target not directly reachable => move closer to target
|
|
TraceLine(nex, ney, ex, ey, &opath);
|
|
UnpackSquare(opath.back(), nex, ney);
|
|
|
|
NavResult res = NAV_PATH;
|
|
|
|
// note: navLock => better safe than sorry
|
|
// infinite recursion should never happen but... better safe than sorry
|
|
assert(!navLock);
|
|
|
|
if (!navLock) {
|
|
// and re-route
|
|
opath.clear();
|
|
|
|
navLock = true;
|
|
res = Navigate(sx, sy, nex, ney, opath);
|
|
navLock = false;
|
|
}
|
|
|
|
// refine this a bit further; find path point closest
|
|
// to original target and truncate
|
|
|
|
int best = 0x7fffffff;
|
|
int bestSize = (int)opath.size();
|
|
|
|
for (int i = 0; i < (int)opath.size(); i++) {
|
|
int x, y;
|
|
UnpackSquare(opath[i], x, y);
|
|
int dx = x - ex, dy = y - ey;
|
|
int cost = ClosestDist(dx, dy);
|
|
|
|
if (cost < best) {
|
|
best = cost;
|
|
bestSize = i + 1;
|
|
}
|
|
}
|
|
|
|
opath.resize(bestSize);
|
|
|
|
return res;
|
|
}
|
|
|
|
if (ex < 0 || ex >= mapWidth || ey < 0 || ey >= mapHeight ||
|
|
mapNodes[ey * mapWidth + ex].frameId != frameId) {
|
|
// path not found
|
|
return NAV_UNREACHABLE;
|
|
}
|
|
|
|
int tx = ex;
|
|
int ty = ey;
|
|
// add end
|
|
opath.push_back(PackSquare(tx, ty));
|
|
|
|
for (;;) {
|
|
int prev = mapNodes[ty * mapWidth + tx].prev;
|
|
|
|
if (prev < 0)
|
|
break;
|
|
|
|
// unpack because we use JPS
|
|
int px, py;
|
|
UnpackSquare(prev, px, py);
|
|
int dx = sign(px - tx);
|
|
int dy = sign(py - ty);
|
|
|
|
while (tx != px || ty != py) {
|
|
tx += dx;
|
|
ty += dy;
|
|
opath.push_back(PackSquare(tx, ty));
|
|
}
|
|
}
|
|
|
|
std::reverse(opath.begin(), opath.end());
|
|
return NAV_PATH;
|
|
}
|
|
|
|
Navigation::NavResult Navigation::NavigateRefined(int sx, int sy, int ex, int ey,
|
|
std::vector<int> &opath, std::vector<int> &ncpath) {
|
|
ncpath.clear();
|
|
|
|
NavResult res = Navigate(sx, sy, ex, ey, opath);
|
|
|
|
if (res != NAV_PATH) {
|
|
if (res == NAV_STRAIGHT) {
|
|
ncpath.push_back(opath[0]);
|
|
ncpath.push_back(opath.back());
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
fpath.clear();
|
|
ncpathIndex.clear();
|
|
|
|
fpath.reserve(opath.size());
|
|
fpath.push_back(opath[0]);
|
|
ncpath.push_back(opath[0]);
|
|
ncpathIndex.push_back(0);
|
|
|
|
rayPath.clear();
|
|
orayPath.clear();
|
|
|
|
rayPath.reserve(opath.size());
|
|
orayPath.reserve(opath.size());
|
|
|
|
for (int i = 1, fx = sx, fy = sy; i < (int)opath.size(); i++) {
|
|
// trying to optimize path
|
|
int tx, ty;
|
|
UnpackSquare(opath[i], tx, ty);
|
|
|
|
bool last = i == (int)opath.size() - 1;
|
|
|
|
if (!TraceLine(fx, fy, tx, ty, &rayPath)) {
|
|
assert(rayPath.back() == opath[i]);
|
|
std::swap(rayPath, orayPath);
|
|
|
|
if (!last)
|
|
continue;
|
|
}
|
|
|
|
// copy orayPath
|
|
for (int j = 1; j < (int)orayPath.size(); j++)
|
|
fpath.push_back(orayPath[j]);
|
|
|
|
if (!orayPath.empty()) {
|
|
assert(ncpath.back() == orayPath[0]);
|
|
ncpath.push_back(orayPath.back());
|
|
ncpathIndex.push_back((int)fpath.size() - 1);
|
|
|
|
if (!last) {
|
|
UnpackSquare(orayPath.back(), fx, fy);
|
|
orayPath.clear();
|
|
i--;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (fpath.back() != opath[i])
|
|
fpath.push_back(opath[i]);
|
|
|
|
if (ncpath.back() != opath[i]) {
|
|
ncpath.push_back(opath[i]);
|
|
ncpathIndex.push_back((int)fpath.size() - 1);
|
|
}
|
|
|
|
fx = tx;
|
|
fy = ty;
|
|
}
|
|
|
|
std::swap(opath, fpath);
|
|
|
|
// validate cpath
|
|
for (int i = 0; i < (int)ncpath.size() - 1; i++) {
|
|
int fx, fy;
|
|
int tx, ty;
|
|
UnpackSquare(ncpath[i], fx, fy);
|
|
UnpackSquare(ncpath[i + 1], tx, ty);
|
|
assert(!TraceLine(fx, fy, tx, ty, &rayPath));
|
|
}
|
|
|
|
assert(ncpath.size() == ncpathIndex.size());
|
|
|
|
// so now we have opath, ncpath and ncpathIndex
|
|
// we want to gradually move ncpath node towards previous to see
|
|
// if we can raycast from prev ncpath node to moved and from moved
|
|
// to the end
|
|
|
|
bool adjusted = false;
|
|
|
|
for (int i = (int)ncpath.size() - 2; i > 0; i--) {
|
|
int px, py;
|
|
int nx, ny;
|
|
|
|
int pidx = ncpathIndex[i - 1];
|
|
int idx = ncpathIndex[i];
|
|
|
|
UnpackSquare(ncpath[i - 1], px, py);
|
|
UnpackSquare(ncpath[i + 1], nx, ny);
|
|
|
|
for (int j = idx - 1; j >= pidx; j--) {
|
|
int x, y;
|
|
UnpackSquare(opath[j], x, y);
|
|
|
|
// if we can raycast px,py => x,y and x,y => nx,ny,
|
|
// we can move ncPath node!
|
|
if (TraceLine(px, py, x, y))
|
|
continue;
|
|
|
|
if (TraceLine(x, y, nx, ny))
|
|
continue;
|
|
|
|
ncpath[i] = opath[j];
|
|
ncpathIndex[i] = j;
|
|
adjusted = true;
|
|
}
|
|
|
|
if (ncpath[i] == ncpath[i - 1]) {
|
|
// if we get here, we need to remove ncpath[i]
|
|
// because we reached the previous node
|
|
ncpath.erase(ncpath.begin() + i);
|
|
ncpathIndex.erase(ncpathIndex.begin() + i);
|
|
adjusted = true;
|
|
}
|
|
}
|
|
|
|
if (!adjusted)
|
|
return NAV_PATH;
|
|
|
|
// final step (if necessary) is to reconstruct path from compressed path
|
|
|
|
opath.clear();
|
|
opath.push_back(ncpath[0]);
|
|
|
|
for (int i = 1; i < (int)ncpath.size(); i++) {
|
|
int fx, fy;
|
|
int tx, ty;
|
|
|
|
UnpackSquare(ncpath[i - 1], fx, fy);
|
|
UnpackSquare(ncpath[i], tx, ty);
|
|
|
|
TraceLine(fx, fy, tx, ty, &rayPath);
|
|
|
|
for (int j = 1; j < (int)rayPath.size(); j++)
|
|
opath.push_back(rayPath[j]);
|
|
}
|
|
|
|
return NAV_PATH;
|
|
}
|
|
|
|
bool Navigation::TraceLine(int srcx, int srcy, int targx, int targy, int &lastValidX, int &lastValidY) const {
|
|
lastValidX = srcx;
|
|
lastValidY = srcy;
|
|
|
|
bool res = TraceLine(srcx, srcy, targx, targy, &fpath);
|
|
|
|
if (!fpath.empty())
|
|
UnpackSquare(fpath.back(), lastValidX, lastValidY);
|
|
|
|
return res;
|
|
}
|
|
|
|
bool Navigation::TraceLine(int srcx, int srcy, int targx, int targy, std::vector<int> *rpath) const {
|
|
if (rpath)
|
|
rpath->clear();
|
|
|
|
// DDA
|
|
int x0 = (srcx << 16) + 0x8000;
|
|
int y0 = (srcy << 16) + 0x8000;
|
|
int x1 = (targx << 16) + 0x8000;
|
|
int y1 = (targy << 16) + 0x8000;
|
|
|
|
int dx = x1 - x0;
|
|
int dy = y1 - y0;
|
|
|
|
if (!dx && !dy) {
|
|
if (!Passable(srcx, srcy))
|
|
return true;
|
|
|
|
if (rpath)
|
|
rpath->push_back(PackSquare(srcx, srcy));
|
|
|
|
return false;
|
|
}
|
|
|
|
int xinc, yinc;
|
|
|
|
if (iabs(dx) >= iabs(dy)) {
|
|
// step along x
|
|
xinc = sign(dx) * 65536;
|
|
yinc = (int)((double)dy * 65536 / iabs(dx));
|
|
} else {
|
|
// step along y
|
|
yinc = sign(dy) * 65536;
|
|
xinc = (int)((double)dx * 65536 / iabs(dy));
|
|
}
|
|
|
|
int fx = x0;
|
|
int fy = y0;
|
|
int x = x0 >> 16;
|
|
int y = y0 >> 16;
|
|
int ex = x1 >> 16;
|
|
int ey = y1 >> 16;
|
|
|
|
while (x != ex || y != ey) {
|
|
if (!Passable(x, y))
|
|
return true;
|
|
|
|
if (rpath)
|
|
rpath->push_back(PackSquare(x, y));
|
|
|
|
fx += xinc;
|
|
fy += yinc;
|
|
int ox = x;
|
|
int oy = y;
|
|
x = fx >> 16;
|
|
y = fy >> 16;
|
|
|
|
if (nodiag && !Reachable(ox, oy, x, y))
|
|
return true;
|
|
}
|
|
|
|
assert(iabs(x - ex) <= 1 && iabs(y - ey) <= 1);
|
|
|
|
if (nodiag && !Reachable(x, y, ex, ey))
|
|
return false;
|
|
|
|
if (!Passable(ex, ey))
|
|
return true;
|
|
|
|
int sq = PackSquare(ex, ey);
|
|
|
|
if (rpath && (rpath->empty() || rpath->back() != sq))
|
|
rpath->push_back(sq);
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace AGS3
|