Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring the A* walkpath algorithm to use c++ features instead of magic words #8127

Closed
wants to merge 12 commits into from
132 changes: 30 additions & 102 deletions src/map/navi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,109 +27,43 @@

std::string filePrefix = "generated/clientside/data/luafiles514/lua files/navigation/";

#define SET_OPEN 0
#define SET_CLOSED 1

#define PATH_DIR_NORTH 1
#define PATH_DIR_WEST 2
#define PATH_DIR_SOUTH 4
#define PATH_DIR_EAST 8

// @name Structures and defines for A* pathfinding
// @{

// Path node
struct path_node {
struct path_node *parent; // pointer to parent
short x; // x coord
short y; // y coord
short g_cost; // Actual cost from start to this node
short f_cost; // g_cost + heuristic(this, goal)
short flag; // SET_OPEN / SET_CLOSED
};

/// Binary heap of path nodes
BHEAP_STRUCT_DECL(node_heap, struct path_node*);
static BHEAP_STRUCT_VAR(node_heap, g_open_set); // use static heap for all path calculations
// it get's initialized in do_init_path, freed in do_final_path.

/// Comparator for binary heap of path nodes (minimum cost at top)
#define NODE_MINTOPCMP(i,j) ((i)->f_cost - (j)->f_cost)
/// Binary Heap
static open_heap open_set;

#define calc_index(x,y) (((x)+(y)*MAX_WALKPATH_NAVI) & (MAX_WALKPATH_NAVI*MAX_WALKPATH_NAVI-1))

/// Estimates the cost from (x0,y0) to (x1,y1).
/// This is inadmissible (overestimating) heuristic used by game client.
#define heuristic(x0, y0, x1, y1) (MOVE_COST * (abs((x1) - (x0)) + abs((y1) - (y0)))) // Manhattan distance
/// @}

// Translates dx,dy into walking direction
static enum directions walk_choices [3][3] =
{
{DIR_NORTHWEST,DIR_NORTH,DIR_NORTHEAST},
{DIR_WEST,DIR_CENTER,DIR_EAST},
{DIR_SOUTHWEST,DIR_SOUTH,DIR_SOUTHEAST},
};


/// @name A* pathfinding related functions
/// @{

/// Pushes path_node to the binary node_heap.
/// Ensures there is enough space in array to store new element.

static void heap_push_node(struct node_heap *heap, struct path_node *node)
AoShinRO marked this conversation as resolved.
Show resolved Hide resolved
{
#ifndef __clang_analyzer__ // TODO: Figure out why clang's static analyzer doesn't like this
BHEAP_ENSURE2(*heap, 1, 256, struct path_node **);
BHEAP_PUSH2(*heap, node, NODE_MINTOPCMP);
#endif // __clang_analyzer__
}

/// Updates path_node in the binary node_heap.
static int heap_update_node(struct node_heap *heap, struct path_node *node)
{
int i;
ARR_FIND(0, BHEAP_LENGTH(*heap), i, BHEAP_DATA(*heap)[i] == node);
if (i == BHEAP_LENGTH(*heap)) {
ShowError("heap_update_node: node not found\n");
return 1;
}
BHEAP_UPDATE(*heap, i, NODE_MINTOPCMP);
return 0;
}
// end 1:1 copy of definitions from path.cpp


// So we don't have to allocate every time, use static structures
static struct path_node tp[MAX_WALKPATH_NAVI * MAX_WALKPATH_NAVI + 1];
static int tpused[MAX_WALKPATH_NAVI * MAX_WALKPATH_NAVI + 1];
static std::vector<path_node> tp(MAX_WALKPATH_NAVI * MAX_WALKPATH_NAVI + 1);

/// Path_node processing in A* pathfinding.
/// Adds new node to heap and updates/re-adds old ones if necessary.
static int add_path(struct node_heap *heap, int16 x, int16 y, int g_cost, struct path_node *parent, int h_cost)
static short add_path(int16 x, int16 y, int g_cost, struct path_node *parent, int h_cost)
{
int i = calc_index(x, y);

if (tpused[i] && tpused[i] == 1 + (x << 16 | y)) { // We processed this node before
if (tp[i].x == x && tp[i].y == y) { // We processed this node before
if (g_cost < tp[i].g_cost) { // New path to this node is better than old one
// Update costs and parent
tp[i].g_cost = g_cost;
tp[i].parent = parent;
tp[i].f_cost = g_cost + h_cost;
if (tp[i].flag == SET_CLOSED) {
heap_push_node(heap, &tp[i]); // Put it in open set again
}
else if (heap_update_node(heap, &tp[i])) {
return 1;
}
if (tp[i].flag == SET_CLOSED)
open_set.push_node(&tp[i]); // Put node to 'open' set
else if (open_set.update_node(&tp[i]))
return 1; // return error if node not found on heap
tp[i].flag = SET_OPEN;
}
return 0;
}

if (tpused[i]) // Index is already taken; see `tp` array FIXME for details
return 1;
if (tp[i].x || tp[i].y) // Index is already taken
return open_set.update_node(&tp[i], true);

// New node
tp[i].x = x;
Expand All @@ -138,8 +72,7 @@ static int add_path(struct node_heap *heap, int16 x, int16 y, int g_cost, struct
tp[i].parent = parent;
tp[i].f_cost = g_cost + h_cost;
tp[i].flag = SET_OPEN;
tpused[i] = 1 + (x << 16 | y);
heap_push_node(heap, &tp[i]);
open_set.push_node(&tp[i]); // Put node to 'open' set
return 0;
}
///@}
Expand All @@ -151,7 +84,7 @@ static int add_path(struct node_heap *heap, int16 x, int16 y, int g_cost, struct
*
* Note: uses global g_open_set, therefore this method can't be called in parallel or recursivly.
*------------------------------------------*/
bool navi_path_search(struct navi_walkpath_data *wpd, const struct navi_pos *from, const struct navi_pos *dest, cell_chk cell) {
static bool navi_path_search(struct navi_walkpath_data *wpd, const struct navi_pos *from, const struct navi_pos *dest, cell_chk cell) {
int i, x, y, dx = 0, dy = 0;
struct map_data *mapdata = map_getmapdata(from->m);
struct navi_walkpath_data s_wpd;
Expand Down Expand Up @@ -180,7 +113,7 @@ bool navi_path_search(struct navi_walkpath_data *wpd, const struct navi_pos *fro
return true;
}

struct path_node *current, *it;
struct path_node *current = nullptr, *it;
int xs = mapdata->xs - 1;
int ys = mapdata->ys - 1;
int len = 0;
Expand All @@ -189,9 +122,8 @@ bool navi_path_search(struct navi_walkpath_data *wpd, const struct navi_pos *fro
// A* (A-star) pathfinding
// We always use A* for finding walkpaths because it is what game client uses.
// Easy pathfinding cuts corners of non-walkable cells, but client always walks around it.
BHEAP_RESET(g_open_set);

memset(tpused, 0, sizeof(tpused));
open_set.clear(); // Clear 'open' set
tp.clear();

// Start node
i = calc_index(from->x, from->y);
Expand All @@ -201,12 +133,11 @@ bool navi_path_search(struct navi_walkpath_data *wpd, const struct navi_pos *fro
tp[i].g_cost = 0;
tp[i].f_cost = heuristic(from->x, from->y, dest->x, dest->y);
tp[i].flag = SET_OPEN;
tpused[i] = 1 + (from->x << 16 | from->y);

heap_push_node(&g_open_set, &tp[i]); // Put start node to 'open' set
open_set.push_node(&tp[i]); // Put node to 'open' set

for (;;) {
int e = 0; // error flag
short e = 0; // error flag

// Saves allowed directions for the current cell. Diagonal directions
// are only allowed if both directions around it are allowed. This is
Expand All @@ -218,12 +149,11 @@ bool navi_path_search(struct navi_walkpath_data *wpd, const struct navi_pos *fro

int g_cost;

if (BHEAP_LENGTH(g_open_set) == 0) {
if (open_set.empty())
return false;
}

current = BHEAP_PEEK(g_open_set); // Look for the lowest f_cost node in the 'open' set
BHEAP_POP2(g_open_set, NODE_MINTOPCMP); // Remove it from 'open' set
current = open_set.front();
open_set.pop_heap(); // Remove it from 'open' set

x = current->x;
y = current->y;
Expand All @@ -243,21 +173,21 @@ bool navi_path_search(struct navi_walkpath_data *wpd, const struct navi_pos *fro
#define chk_dir(d) ((allowed_dirs & (d)) == (d))
// Process neighbors of current node
if (chk_dir(PATH_DIR_SOUTH|PATH_DIR_EAST) && !map_getcellp(mapdata, x+1, y-1, cell))
e += add_path(&g_open_set, x+1, y-1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x+1, y-1, dest->x, dest->y)); // (x+1, y-1) 5
e += add_path(x+1, y-1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x+1, y-1, dest->x, dest->y)); // (x+1, y-1) 5
if (chk_dir(PATH_DIR_EAST))
e += add_path(&g_open_set, x+1, y, g_cost + MOVE_COST, current, heuristic(x+1, y, dest->x, dest->y)); // (x+1, y) 6
e += add_path(x+1, y, g_cost + MOVE_COST, current, heuristic(x+1, y, dest->x, dest->y)); // (x+1, y) 6
if (chk_dir(PATH_DIR_NORTH|PATH_DIR_EAST) && !map_getcellp(mapdata, x+1, y+1, cell))
e += add_path(&g_open_set, x+1, y+1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x+1, y+1, dest->x, dest->y)); // (x+1, y+1) 7
e += add_path(x+1, y+1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x+1, y+1, dest->x, dest->y)); // (x+1, y+1) 7
if (chk_dir(PATH_DIR_NORTH))
e += add_path(&g_open_set, x, y+1, g_cost + MOVE_COST, current, heuristic(x, y+1, dest->x, dest->y)); // (x, y+1) 0
e += add_path(x, y+1, g_cost + MOVE_COST, current, heuristic(x, y+1, dest->x, dest->y)); // (x, y+1) 0
if (chk_dir(PATH_DIR_NORTH|PATH_DIR_WEST) && !map_getcellp(mapdata, x-1, y+1, cell))
e += add_path(&g_open_set, x-1, y+1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x-1, y+1, dest->x, dest->y)); // (x-1, y+1) 1
e += add_path(x-1, y+1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x-1, y+1, dest->x, dest->y)); // (x-1, y+1) 1
if (chk_dir(PATH_DIR_WEST))
e += add_path(&g_open_set, x-1, y, g_cost + MOVE_COST, current, heuristic(x-1, y, dest->x, dest->y)); // (x-1, y) 2
e += add_path(x-1, y, g_cost + MOVE_COST, current, heuristic(x-1, y, dest->x, dest->y)); // (x-1, y) 2
if (chk_dir(PATH_DIR_SOUTH|PATH_DIR_WEST) && !map_getcellp(mapdata, x-1, y-1, cell))
e += add_path(&g_open_set, x-1, y-1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x-1, y-1, dest->x, dest->y)); // (x-1, y-1) 3
e += add_path(x-1, y-1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x-1, y-1, dest->x, dest->y)); // (x-1, y-1) 3
if (chk_dir(PATH_DIR_SOUTH))
e += add_path(&g_open_set, x, y-1, g_cost + MOVE_COST, current, heuristic(x, y-1, dest->x, dest->y)); // (x, y-1) 4
e += add_path(x, y-1, g_cost + MOVE_COST, current, heuristic(x, y-1, dest->x, dest->y)); // (x, y-1) 4
#undef chk_dir
if (e) {
return false;
Expand Down Expand Up @@ -631,7 +561,6 @@ void write_map_distances() {


void navi_create_lists() {
BHEAP_INIT(g_open_set);

auto starttime = std::chrono::system_clock::now();

Expand All @@ -649,7 +578,6 @@ void navi_create_lists() {
currenttime = std::chrono::system_clock::now();
ShowInfo("Link Distances took %ums\n", std::chrono::duration_cast<std::chrono::milliseconds>(currenttime - starttime));

BHEAP_CLEAR(g_open_set);
}

#endif
Loading