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

Pathfinding: Precomputeportal nodes for A Star #1708

Merged
merged 1 commit into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libopenage/pathfinding/demo/demo_1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ void path_demo_1(const util::Path &path) {
start,
target,
};
grid->init_portal_nodes();
timer.start();
Path path_result = pathfinder->get_path(path_request);
timer.stop();
Expand Down
23 changes: 23 additions & 0 deletions libopenage/pathfinding/grid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,27 @@ void Grid::init_portals() {
}
}

const nodemap_t &Grid::get_portal_map() {
return portal_nodes;
}

void Grid::init_portal_nodes() {
// create portal_nodes
for (auto &sector : this->sectors) {
for (auto &portal : sector->get_portals()) {
if (!this->portal_nodes.contains(portal->get_id())) {
auto portal_node = std::make_shared<PortalNode>(portal);
portal_node->node_sector_0 = sector->get_id();
portal_node->node_sector_1 = portal_node->portal->get_exit_sector(sector->get_id());
this->portal_nodes[portal->get_id()] = portal_node;
}
}
}

// init portal_node exits
for (auto &[id, node] : this->portal_nodes) {
node->init_exits(this->portal_nodes);
}
}

} // namespace openage::path
18 changes: 18 additions & 0 deletions libopenage/pathfinding/grid.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <utility>
#include <vector>

#include "pathfinding/pathfinder.h"
#include "pathfinding/types.h"
#include "util/vector.h"

Expand Down Expand Up @@ -95,6 +96,16 @@ class Grid {
*/
void init_portals();

/**
* returns map of portal ids to portal nodes
*/
const nodemap_t &get_portal_map();

/**
* Initialize the portal nodes of the grid with neigbouring nodes and distance costs.
*/
void init_portal_nodes();

private:
/**
* ID of the grid.
Expand All @@ -115,6 +126,13 @@ class Grid {
* Sectors of the grid.
*/
std::vector<std::shared_ptr<Sector>> sectors;

/**
* maps portal_ids to portal nodes, which store their neigbouring nodes and associated distance costs
* for pathfinding
*/

nodemap_t portal_nodes;
};


Expand Down
85 changes: 55 additions & 30 deletions libopenage/pathfinding/pathfinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "coord/chunk.h"
#include "coord/phys.h"
#include "error/error.h"
#include "pathfinding/cost_field.h"
#include "pathfinding/flow_field.h"
#include "pathfinding/grid.h"
Expand Down Expand Up @@ -201,6 +202,7 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req
std::vector<std::shared_ptr<Portal>> result;

auto grid = this->grids.at(request.grid_id);
auto &portal_map = grid->get_portal_map();
auto sector_size = grid->get_sector_size();

auto start_sector_x = request.start.ne / sector_size;
Expand All @@ -210,8 +212,7 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req
// path node storage, always provides cheapest next node.
heap_t node_candidates;

// list of known portals and corresponding node.
nodemap_t visited_portals;
std::unordered_set<portal_id_t> visited_portals;

// TODO: Compute cost to travel from one portal to another when creating portals
// const int distance_cost = 1;
Expand All @@ -223,7 +224,8 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req
continue;
}

auto portal_node = std::make_shared<PortalNode>(portal, start_sector->get_id(), nullptr);
auto &portal_node = portal_map.at(portal->get_id());
portal_node->entry_sector = start_sector->get_id();

auto sector_pos = grid->get_sector(portal->get_exit_sector(start_sector->get_id()))->get_position().to_tile(sector_size);
auto portal_pos = portal->get_exit_center(start_sector->get_id());
Expand All @@ -235,7 +237,9 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req
portal_node->future_cost = portal_node->current_cost + heuristic_cost;

portal_node->heap_node = node_candidates.push(portal_node);
visited_portals[portal->get_id()] = portal_node;
portal_node->prev_portal = nullptr;
portal_node->was_best = false;
visited_portals.insert(portal->get_id());
}

// track the closest we can get to the end position
Expand Down Expand Up @@ -266,20 +270,21 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req
}

// get the exits of the current node
auto exits = current_node->get_exits(visited_portals, current_node->entry_sector);
const auto &exits = current_node->get_exits(current_node->entry_sector);

// evaluate all neighbors of the current candidate for further progress
for (auto &exit : exits) {
if (exit->was_best) {
for (auto &[exit, distance_cost] : exits) {
exit->entry_sector = current_node->portal->get_exit_sector(current_node->entry_sector);
bool not_visited = !visited_portals.contains(exit->portal->get_id());

if (not_visited) {
exit->was_best = false;
}
else if (exit->was_best) {
continue;
}

// Get distance cost (from current node to exit node)
auto distance_cost = Pathfinder::distance_cost(
current_node->portal->get_exit_center(current_node->entry_sector),
exit->portal->get_entry_center(exit->entry_sector));

bool not_visited = !visited_portals.contains(exit->portal->get_id());
auto tentative_cost = current_node->current_cost + distance_cost;

if (not_visited or tentative_cost < exit->current_cost) {
Expand All @@ -300,7 +305,7 @@ const Pathfinder::portal_star_t Pathfinder::portal_a_star(const PathRequest &req

if (not_visited) {
exit->heap_node = node_candidates.push(exit);
visited_portals[exit->portal->get_id()] = exit;
visited_portals.insert(exit->portal->get_id());
}
else {
node_candidates.decrease(exit->heap_node);
Expand Down Expand Up @@ -463,6 +468,16 @@ int Pathfinder::distance_cost(const coord::tile_delta &portal1_pos,
}


PortalNode::PortalNode(const std::shared_ptr<Portal> &portal) :
portal{portal},
entry_sector{NULL},
future_cost{std::numeric_limits<int>::max()},
current_cost{std::numeric_limits<int>::max()},
heuristic_cost{std::numeric_limits<int>::max()},
was_best{false},
prev_portal{nullptr},
heap_node{nullptr} {}

PortalNode::PortalNode(const std::shared_ptr<Portal> &portal,
sector_id_t entry_sector,
const node_t &prev_portal) :
Expand Down Expand Up @@ -511,27 +526,37 @@ std::vector<node_t> PortalNode::generate_backtrace() {
return waypoints;
}

std::vector<node_t> PortalNode::get_exits(const nodemap_t &nodes,
sector_id_t entry_sector) {
auto &exits = this->portal->get_exits(entry_sector);
std::vector<node_t> exit_nodes;
exit_nodes.reserve(exits.size());
void PortalNode::init_exits(const nodemap_t &node_map) {
auto exits = this->portal->get_exits(this->node_sector_0);
for (auto &exit : exits) {
int distance_cost = Pathfinder::distance_cost(
this->portal->get_exit_center(this->node_sector_0),
exit->get_entry_center(this->node_sector_1));

auto exit_node = node_map.at(exit->get_id());
this->exits_1[exit_node] = distance_cost;
}

auto exit_sector = this->portal->get_exit_sector(entry_sector);
exits = this->portal->get_exits(this->node_sector_1);
for (auto &exit : exits) {
auto exit_id = exit->get_id();
int distance_cost = Pathfinder::distance_cost(
this->portal->get_exit_center(this->node_sector_1),
exit->get_entry_center(this->node_sector_0));

auto exit_node = nodes.find(exit_id);
if (exit_node != nodes.end()) {
exit_nodes.push_back(exit_node->second);
}
else {
exit_nodes.push_back(std::make_shared<PortalNode>(exit,
exit_sector,
this->shared_from_this()));
}
auto exit_node = node_map.at(exit->get_id());
this->exits_0[exit_node] = distance_cost;
}
}

const PortalNode::exits_t &PortalNode::get_exits(sector_id_t entry_sector) {
ENSURE(entry_sector == this->node_sector_0 || entry_sector == this->node_sector_1, "Invalid entry sector");

if (this->node_sector_0 == entry_sector) {
return exits_1;
}
else {
return exits_0;
}
return exit_nodes;
}


Expand Down
63 changes: 51 additions & 12 deletions libopenage/pathfinding/pathfinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

#pragma once

#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>

#include "coord/tile.h"
#include "datastructure/pairing_heap.h"
Expand Down Expand Up @@ -62,6 +62,17 @@ class Pathfinder {
*/
const Path get_path(const PathRequest &request);


/**
* Calculate the distance cost between two portals.
*
* @param portal1_pos Center of the first portal (relative to sector origin).
* @param portal2_pos Center of the second portal (relative to sector origin).
*
* @return Distance cost between the portal centers.
*/
static int distance_cost(const coord::tile_delta &portal1_pos, const coord::tile_delta &portal2_pos);

private:
using portal_star_t = std::pair<PathResult, std::vector<std::shared_ptr<Portal>>>;

Expand Down Expand Up @@ -100,15 +111,6 @@ class Pathfinder {
*/
static int heuristic_cost(const coord::tile &portal_pos, const coord::tile &target_pos);

/**
* Calculate the distance cost between two portals.
*
* @param portal1_pos Center of the first portal (relative to sector origin).
* @param portal2_pos Center of the second portal (relative to sector origin).
*
* @return Distance cost between the portal centers.
*/
static int distance_cost(const coord::tile_delta &portal1_pos, const coord::tile_delta &portal2_pos);

/**
* Grids managed by this pathfinder.
Expand Down Expand Up @@ -147,6 +149,7 @@ using nodemap_t = std::unordered_map<portal_id_t, node_t>;
*/
class PortalNode : public std::enable_shared_from_this<PortalNode> {
public:
PortalNode(const std::shared_ptr<Portal> &portal);
PortalNode(const std::shared_ptr<Portal> &portal,
sector_id_t entry_sector,
const node_t &prev_portal);
Expand Down Expand Up @@ -178,9 +181,25 @@ class PortalNode : public std::enable_shared_from_this<PortalNode> {
std::vector<node_t> generate_backtrace();

/**
* Get all exits of a node.
* init PortalNode::exits.
*/
void init_exits(const nodemap_t &node_map);


/**
* maps node_t of a neigbhour portal to the distance cost to travel between the portals
*/
std::vector<node_t> get_exits(const nodemap_t &nodes, sector_id_t entry_sector);
using exits_t = std::map<const node_t, int>;


/**
* Get the exit portals reachable via the portal when entering from a specified sector.
*
* @param entry_sector Sector from which the portal is entered.
*
* @return Exit portals nodes reachable from the portal.
*/
const exits_t &get_exits(sector_id_t entry_sector);

/**
* The portal this node is associated to.
Expand Down Expand Up @@ -226,6 +245,26 @@ class PortalNode : public std::enable_shared_from_this<PortalNode> {
* Priority queue node that contains this path node.
*/
heap_t::element_t heap_node;

/**
* First sector connected by the portal.
*/
sector_id_t node_sector_0;

/**
* Second sector connected by the portal.
*/
sector_id_t node_sector_1;

/**
* Exits in sector 0 reachable from the portal.
*/
exits_t exits_0;

/**
* Exits in sector 1 reachable from the portal.
*/
exits_t exits_1;
};


Expand Down
Loading