From 6b7154892f0f6de776dcf37f8f9ac0f478663ebb Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 17 Mar 2024 18:35:28 +0100 Subject: [PATCH] path: Separate grid into sectors. --- libopenage/pathfinding/CMakeLists.txt | 1 + libopenage/pathfinding/grid.cpp | 30 +++++-- libopenage/pathfinding/grid.h | 49 ++++++++--- libopenage/pathfinding/portal.cpp | 33 +++++--- libopenage/pathfinding/portal.h | 55 +++++++------ libopenage/pathfinding/sector.cpp | 114 ++++++++++++++++++++++++++ libopenage/pathfinding/sector.h | 108 ++++++++++++++++++++++++ libopenage/pathfinding/types.h | 6 ++ 8 files changed, 341 insertions(+), 55 deletions(-) create mode 100644 libopenage/pathfinding/sector.cpp create mode 100644 libopenage/pathfinding/sector.h diff --git a/libopenage/pathfinding/CMakeLists.txt b/libopenage/pathfinding/CMakeLists.txt index c5d1b7aaa4..55a3f19ee0 100644 --- a/libopenage/pathfinding/CMakeLists.txt +++ b/libopenage/pathfinding/CMakeLists.txt @@ -9,6 +9,7 @@ add_sources(libopenage integrator.cpp path.cpp portal.cpp + sector.cpp tests.cpp types.cpp ) diff --git a/libopenage/pathfinding/grid.cpp b/libopenage/pathfinding/grid.cpp index fc3424b333..86b71d8681 100644 --- a/libopenage/pathfinding/grid.cpp +++ b/libopenage/pathfinding/grid.cpp @@ -2,19 +2,39 @@ #include "grid.h" +#include "pathfinding/sector.h" + namespace openage::path { -CostGrid::CostGrid(size_t width, size_t height, size_t field_size) : - width{width}, height{height}, fields{width * height, CostField{field_size}} { +Grid::Grid(size_t width, size_t height, size_t sector_size) : + width{width}, + height{height} { + for (size_t y = 0; y < height; y++) { + for (size_t x = 0; x < width; x++) { + this->sectors.push_back(std::make_shared(x + y * width, sector_size)); + } + } +} + +Grid::Grid(size_t width, + size_t height, + std::vector> &§ors) : + width{width}, + height{height}, + sectors{std::move(sectors)} { } -std::pair CostGrid::get_size() const { +std::pair Grid::get_size() const { return {this->width, this->height}; } -CostField &CostGrid::get_field(size_t x, size_t y) { - return this->fields[y * this->width + x]; +const std::shared_ptr &Grid::get_sector(size_t x, size_t y) { + return this->sectors.at(x + y * this->width); +} + +const std::shared_ptr &Grid::get_sector(sector_id_t id) const { + return this->sectors.at(id); } } // namespace openage::path diff --git a/libopenage/pathfinding/grid.h b/libopenage/pathfinding/grid.h index 19eb7d682c..22c551066c 100644 --- a/libopenage/pathfinding/grid.h +++ b/libopenage/pathfinding/grid.h @@ -3,29 +3,42 @@ #pragma once #include +#include #include #include -#include "pathfinding/cost_field.h" +#include "pathfinding/types.h" namespace openage::path { +class Sector; /** - * Grid of cost fields for flow field pathfinding. + * Grid for flow field pathfinding. */ -class CostGrid { +class Grid { public: /** - * Create a grid with a specified size and field size. + * Create a new empty grid of width x height sectors with a specified size. * * @param width Width of the grid. * @param height Height of the grid. - * @param field_size Size of the cost fields. + * @param sector_size Side length of each sector. */ - CostGrid(size_t width, - size_t height, - size_t field_size); + Grid(size_t width, + size_t height, + size_t sector_size); + + /** + * Create a grid of width x height sectors from a list of existing sectors. + * + * @param width Width of the grid. + * @param height Height of the grid. + * @param sectors Existing sectors. + */ + Grid(size_t width, + size_t height, + std::vector> &§ors); /** * Get the size of the grid. @@ -35,13 +48,23 @@ class CostGrid { std::pair get_size() const; /** - * Get the cost field at a specified position. + * Get the sector at a specified position. * * @param x X coordinate. * @param y Y coordinate. - * @return Cost field at the specified position. + * + * @return Sector at the specified position. + */ + const std::shared_ptr &get_sector(size_t x, size_t y); + + /** + * Get the sector with a specified ID + * + * @param id ID of the sector. + * + * @return Sector with the specified ID. */ - CostField &get_field(size_t x, size_t y); + const std::shared_ptr &get_sector(sector_id_t id) const; private: /** @@ -55,9 +78,9 @@ class CostGrid { size_t height; /** - * Cost fields. + * Sectors of the grid. */ - std::vector fields; + std::vector> sectors; }; diff --git a/libopenage/pathfinding/portal.cpp b/libopenage/pathfinding/portal.cpp index b959cbdc6c..40111e0b98 100644 --- a/libopenage/pathfinding/portal.cpp +++ b/libopenage/pathfinding/portal.cpp @@ -7,10 +7,8 @@ namespace openage::path { -Portal::Portal(std::shared_ptr sector0, - std::shared_ptr sector1, - std::vector> sector0_exits, - std::vector> sector1_exits, +Portal::Portal(sector_id_t sector0, + sector_id_t sector1, PortalDirection direction, size_t cell_start_x, size_t cell_start_y, @@ -18,8 +16,8 @@ Portal::Portal(std::shared_ptr sector0, size_t cell_end_y) : sector0{sector0}, sector1{sector1}, - sector0_exits{sector0_exits}, - sector1_exits{sector1_exits}, + sector0_exits{}, + sector1_exits{}, direction{direction}, cell_start_x{cell_start_x}, cell_start_y{cell_start_y}, @@ -27,7 +25,7 @@ Portal::Portal(std::shared_ptr sector0, cell_end_y{cell_end_y} { } -const std::vector> &Portal::get_exits(const std::shared_ptr &entry_sector) const { +const std::vector> &Portal::get_exits(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); if (entry_sector == this->sector0) { @@ -36,7 +34,18 @@ const std::vector> &Portal::get_exits(const std::shared_ return this->sector0_exits; } -const std::shared_ptr &Portal::get_exit_sector(const std::shared_ptr &entry_sector) const { +void Portal::set_exits(sector_id_t sector, const std::vector> &exits) { + ENSURE(sector == this->sector0 || sector == this->sector1, "Portal does not connect to sector"); + + if (sector == this->sector0) { + this->sector0_exits = exits; + } + else { + this->sector1_exits = exits; + } +} + +sector_id_t Portal::get_exit_sector(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); if (entry_sector == this->sector0) { @@ -45,7 +54,7 @@ const std::shared_ptr &Portal::get_exit_sector(const std::shared_ptr< return this->sector0; } -std::pair Portal::get_entry_start(const std::shared_ptr &entry_sector) const { +std::pair Portal::get_entry_start(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); if (entry_sector == this->sector0) { @@ -55,7 +64,7 @@ std::pair Portal::get_entry_start(const std::shared_ptrget_sector1_start(); } -std::pair Portal::get_entry_end(const std::shared_ptr &entry_sector) const { +std::pair Portal::get_entry_end(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); if (entry_sector == this->sector0) { @@ -65,7 +74,7 @@ std::pair Portal::get_entry_end(const std::shared_ptr return this->get_sector1_end(); } -std::pair Portal::get_exit_start(const std::shared_ptr &entry_sector) const { +std::pair Portal::get_exit_start(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); if (entry_sector == this->sector0) { @@ -75,7 +84,7 @@ std::pair Portal::get_exit_start(const std::shared_ptrget_sector0_start(); } -std::pair Portal::get_exit_end(const std::shared_ptr &entry_sector) const { +std::pair Portal::get_exit_end(sector_id_t entry_sector) const { ENSURE(entry_sector == this->sector0 || entry_sector == this->sector1, "Invalid entry sector"); if (entry_sector == this->sector0) { diff --git a/libopenage/pathfinding/portal.h b/libopenage/pathfinding/portal.h index 3632e3cecd..dd6e474931 100644 --- a/libopenage/pathfinding/portal.h +++ b/libopenage/pathfinding/portal.h @@ -2,10 +2,11 @@ #pragma once - #include +#include +#include -#include "pathfinding/cost_field.h" +#include "pathfinding/types.h" namespace openage::path { @@ -45,18 +46,14 @@ class Portal { * Must be north or east on the grid in relation to sector 1. * @param sector1 Second sector connected by the portal. * Must be south or west on the grid in relation to sector 0. - * @param sector0_exits Portals reachable from this portal in sector 0. - * @param sector1_exits Portals reachable from this portal in sector 1. * @param direction Direction of the portal from sector 0 to sector 1. * @param cell_start_x Start cell x coordinate in sector 0. * @param cell_start_y Start cell y coordinate in sector 0. * @param cell_end_x End cell x coordinate in sector 0. * @param cell_end_y End cell y coordinate in sector 0. */ - Portal(std::shared_ptr sector0, - std::shared_ptr sector1, - std::vector> sector0_exits, - std::vector> sector1_exits, + Portal(sector_id_t sector0, + sector_id_t sector1, PortalDirection direction, size_t cell_start_x, size_t cell_start_y, @@ -72,7 +69,15 @@ class Portal { * * @return Exit portals reachable from the portal. */ - const std::vector> &get_exits(const std::shared_ptr &entry_sector) const; + const std::vector> &get_exits(sector_id_t entry_sector) const; + + /** + * Set the exit portals reachable for a specified sector. + * + * @param sector Sector for which the exit portals are set. + * @param exits Exit portals reachable from the portal. + */ + void set_exits(sector_id_t sector, const std::vector> &exits); /** * Get the cost field of the sector where the portal is exited. @@ -81,7 +86,7 @@ class Portal { * * @return Cost field of the sector where the portal is exited. */ - const std::shared_ptr &get_exit_sector(const std::shared_ptr &entry_sector) const; + sector_id_t get_exit_sector(sector_id_t entry_sector) const; /** * Get the cost field of the sector from which the portal is entered. @@ -90,7 +95,7 @@ class Portal { * * @return Cost field of the sector from which the portal is entered. */ - std::pair get_entry_start(const std::shared_ptr &entry_sector) const; + std::pair get_entry_start(sector_id_t entry_sector) const; /** * Get the cell coordinates of the start of the portal in the entry sector. @@ -99,7 +104,7 @@ class Portal { * * @return Cell coordinates of the start of the portal in the entry sector. */ - std::pair get_entry_end(const std::shared_ptr &entry_sector) const; + std::pair get_entry_end(sector_id_t entry_sector) const; /** * Get the cell coordinates of the start of the portal in the exit sector. @@ -108,7 +113,7 @@ class Portal { * * @return Cell coordinates of the start of the portal in the exit sector. */ - std::pair get_exit_start(const std::shared_ptr &entry_sector) const; + std::pair get_exit_start(sector_id_t entry_sector) const; /** * Get the cell coordinates of the end of the portal in the exit sector. @@ -117,7 +122,7 @@ class Portal { * * @return Cell coordinates of the end of the portal in the exit sector. */ - std::pair get_exit_end(const std::shared_ptr &entry_sector) const; + std::pair get_exit_end(sector_id_t entry_sector) const; private: /** @@ -149,47 +154,47 @@ class Portal { std::pair get_sector1_end() const; /** - * Sector 0 + * First sector connected by the portal. */ - std::shared_ptr sector0; + sector_id_t sector0; /** - * Sector 1 + * Second sector connected by the portal. */ - std::shared_ptr sector1; + sector_id_t sector1; /** - * Exits from sector 0 + * Exits in sector 0 reachable from the portal. */ std::vector> sector0_exits; /** - * Exits from sector 1 + * Exits in sector 1 reachable from the portal. */ std::vector> sector1_exits; /** - * Direction of the portal + * Direction of the portal from sector 0 to sector 1. */ PortalDirection direction; /** - * Start cell x coordinate + * Start cell x coordinate. */ size_t cell_start_x; /** - * Start cell y coordinate + * Start cell y coordinate. */ size_t cell_start_y; /** - * End cell x coordinate + * End cell x coordinate. */ size_t cell_end_x; /** - * End cell y coordinate + * End cell y coordinate. */ size_t cell_end_y; }; diff --git a/libopenage/pathfinding/sector.cpp b/libopenage/pathfinding/sector.cpp new file mode 100644 index 0000000000..26fdfb5ef1 --- /dev/null +++ b/libopenage/pathfinding/sector.cpp @@ -0,0 +1,114 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "sector.h" + +#include "error/error.h" + +#include "pathfinding/cost_field.h" +#include "pathfinding/definitions.h" + + +namespace openage::path { + +Sector::Sector(sector_id_t id, size_t field_size) : + id{id}, + cost_field{std::make_shared(field_size)} { +} + +Sector::Sector(sector_id_t id, const std::shared_ptr &cost_field) : + id{id}, + cost_field{cost_field} { +} + +const sector_id_t &Sector::get_id() const { + return this->id; +} + +const std::shared_ptr &Sector::get_cost_field() const { + return this->cost_field; +} + +const std::vector> &Sector::get_portals() const { + return this->portals; +} + +void Sector::add_portal(const std::shared_ptr &portal) { + this->portals.push_back(portal); +} + +std::vector> Sector::find_portals(const std::shared_ptr &other, + PortalDirection direction) const { + ENSURE(this->cost_field->get_size() == other->get_cost_field()->get_size(), "Sector size mismatch"); + + std::vector> result; + + // cost field of the other sector + auto other_cost = other->get_cost_field(); + + // compare the edges of the sectors + if (direction == PortalDirection::NORTH_SOUTH) { + // search from left to right + size_t start = 0; + bool passable_edge = false; + for (size_t x = 0; x < this->cost_field->get_size(); x++) { + if (this->cost_field->get_cost(x, this->cost_field->get_size() - 1) != COST_IMPASSABLE + and other_cost->get_cost(x, 0) != COST_IMPASSABLE) { + if (not passable_edge) { + start = x; + passable_edge = true; + } + } + else { + if (passable_edge) { + result.push_back( + std::make_shared( + this->id, + other->get_id(), + direction, + start, + this->cost_field->get_size() - 1, + x - 1, + 0)); + passable_edge = false; + } + } + } + } + else if (direction == PortalDirection::EAST_WEST) { + // search from top to bottom + size_t start = 0; + bool passable_edge = false; + for (size_t y = 0; y < this->cost_field->get_size(); y++) { + if (this->cost_field->get_cost(this->cost_field->get_size() - 1, y) != COST_IMPASSABLE + and other_cost->get_cost(0, y) != COST_IMPASSABLE) { + if (not passable_edge) { + start = y; + passable_edge = true; + } + } + else { + if (passable_edge) { + result.push_back( + std::make_shared( + this->id, + other->get_id(), + direction, + this->cost_field->get_size() - 1, + start, + 0, + y - 1)); + passable_edge = false; + } + } + } + } + + return result; +} + +void Sector::connect_portals() { + // TODO: Flood fill to find connected sectors +} + + +} // namespace openage::path diff --git a/libopenage/pathfinding/sector.h b/libopenage/pathfinding/sector.h new file mode 100644 index 0000000000..b0dbd2bc18 --- /dev/null +++ b/libopenage/pathfinding/sector.h @@ -0,0 +1,108 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "pathfinding/portal.h" +#include "pathfinding/types.h" + + +namespace openage::path { +class CostField; +class Portal; + +/** + * Sector in a grid for flow field pathfinding. + * + * Sectors consist of a cost field and a list of portals connecting them to adjacent + * sectors. + */ +class Sector { +public: + /** + * Create a new sector with a specified ID and an uninitialized cost field. + * + * @param id ID of the sector. + * @param field_size Size of the cost field. + */ + Sector(sector_id_t id, + size_t field_size); + + /** + * Create a new sector with a specified ID and an existing cost field. + * + * @param id ID of the sector. + * @param cost_field Cost field of the sector. + */ + Sector(sector_id_t id, + const std::shared_ptr &cost_field); + + /** + * Get the ID of this sector. + * + * @return ID of the sector. + */ + const sector_id_t &get_id() const; + + /** + * Get the cost field of this sector. + * + * @return Cost field of this sector. + */ + const std::shared_ptr &get_cost_field() const; + + /** + * Get the portals connecting this sector to other sectors. + * + * @return Outgoing portals of this sector. + */ + const std::vector> &get_portals() const; + + /** + * Add a portal to another sector. + * + * @param portal Portal to another sector. + */ + void add_portal(const std::shared_ptr &portal); + + /** + * Find portals connecting this sector to another sector. + * + * @param other Sector to which the portals should connect. + * @param direction Direction from this sector to the other sector. + * + * @return Portals connecting this sector to the other sector. + */ + std::vector> find_portals(const std::shared_ptr &other, + PortalDirection direction) const; + + /** + * Connect all portals that are mutually reachable. + * + * This method should be called after all sectors and portals have + * been created and initialized. + */ + void connect_portals(); + +private: + /** + * ID of the sector. + */ + sector_id_t id; + + /** + * Cost field of the sector. + */ + std::shared_ptr cost_field; + + /** + * Portals of the sector. + */ + std::vector> portals; +}; + + +} // namespace openage::path diff --git a/libopenage/pathfinding/types.h b/libopenage/pathfinding/types.h index 8280dfcdf8..118c8c116d 100644 --- a/libopenage/pathfinding/types.h +++ b/libopenage/pathfinding/types.h @@ -2,6 +2,7 @@ #pragma once +#include #include @@ -55,4 +56,9 @@ struct integrate_t { */ using flow_t = uint8_t; +/** + * Sector identifier on a grid. + */ +using sector_id_t = size_t; + } // namespace openage::path